xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision 5112e9b47b0f55eb7d56657154472785b6300298)
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 <boost/container/flat_map.hpp>
19 #include <boost/container/flat_set.hpp>
20 #include <dbus_singleton.hpp>
21 #include <error_messages.hpp>
22 #include <node.hpp>
23 #include <optional>
24 #include <utils/json_utils.hpp>
25 #include <variant>
26 
27 namespace redfish
28 {
29 
30 /**
31  * DBus types primitives for several generic DBus interfaces
32  * TODO(Pawel) consider move this to separate file into boost::dbus
33  */
34 using PropertiesMapType = boost::container::flat_map<
35     std::string, std::variant<std::string, bool, uint8_t, int16_t, uint16_t,
36                               int32_t, uint32_t, int64_t, uint64_t, double>>;
37 
38 using GetManagedObjects = std::vector<std::pair<
39     sdbusplus::message::object_path,
40     std::vector<std::pair<
41         std::string,
42         boost::container::flat_map<
43             std::string, sdbusplus::message::variant<
44                              std::string, bool, uint8_t, int16_t, uint16_t,
45                              int32_t, uint32_t, int64_t, uint64_t, double,
46                              std::vector<std::string>>>>>>>;
47 
48 enum class LinkType
49 {
50     Local,
51     Global
52 };
53 
54 /**
55  * Structure for keeping IPv4 data required by Redfish
56  */
57 struct IPv4AddressData
58 {
59     std::string id;
60     std::string address;
61     std::string domain;
62     std::string gateway;
63     std::string netmask;
64     std::string origin;
65     LinkType linktype;
66 
67     bool operator<(const IPv4AddressData &obj) const
68     {
69         return id < obj.id;
70     }
71 };
72 
73 /**
74  * Structure for keeping basic single Ethernet Interface information
75  * available from DBus
76  */
77 struct EthernetInterfaceData
78 {
79     uint32_t speed;
80     bool auto_neg;
81     bool DHCPEnabled;
82     std::string hostname;
83     std::string default_gateway;
84     std::string ipv6_default_gateway;
85     std::string mac_address;
86     std::vector<std::uint32_t> vlan_id;
87     std::vector<std::string> nameservers;
88 };
89 
90 // Helper function that changes bits netmask notation (i.e. /24)
91 // into full dot notation
92 inline std::string getNetmask(unsigned int bits)
93 {
94     uint32_t value = 0xffffffff << (32 - bits);
95     std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
96                           std::to_string((value >> 16) & 0xff) + "." +
97                           std::to_string((value >> 8) & 0xff) + "." +
98                           std::to_string(value & 0xff);
99     return netmask;
100 }
101 
102 inline std::string
103     translateAddressOriginDbusToRedfish(const std::string &inputOrigin,
104                                         bool isIPv4)
105 {
106     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
107     {
108         return "Static";
109     }
110     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal")
111     {
112         if (isIPv4)
113         {
114             return "IPv4LinkLocal";
115         }
116         else
117         {
118             return "LinkLocal";
119         }
120     }
121     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP")
122     {
123         if (isIPv4)
124         {
125             return "DHCP";
126         }
127         else
128         {
129             return "DHCPv6";
130         }
131     }
132     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC")
133     {
134         return "SLAAC";
135     }
136     return "";
137 }
138 
139 inline std::string
140     translateAddressOriginRedfishToDbus(const std::string &inputOrigin)
141 {
142     if (inputOrigin == "Static")
143     {
144         return "xyz.openbmc_project.Network.IP.AddressOrigin.Static";
145     }
146     if (inputOrigin == "DHCP" || inputOrigin == "DHCPv6")
147     {
148         return "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP";
149     }
150     if (inputOrigin == "IPv4LinkLocal" || inputOrigin == "LinkLocal")
151     {
152         return "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal";
153     }
154     if (inputOrigin == "SLAAC")
155     {
156         return "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC";
157     }
158     return "";
159 }
160 
161 inline void extractEthernetInterfaceData(const std::string &ethiface_id,
162                                          const GetManagedObjects &dbus_data,
163                                          EthernetInterfaceData &ethData)
164 {
165     for (const auto &objpath : dbus_data)
166     {
167         for (const auto &ifacePair : objpath.second)
168         {
169             if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id)
170             {
171                 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress")
172                 {
173                     for (const auto &propertyPair : ifacePair.second)
174                     {
175                         if (propertyPair.first == "MACAddress")
176                         {
177                             const std::string *mac =
178                                 std::get_if<std::string>(&propertyPair.second);
179                             if (mac != nullptr)
180                             {
181                                 ethData.mac_address = *mac;
182                             }
183                         }
184                     }
185                 }
186                 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN")
187                 {
188                     for (const auto &propertyPair : ifacePair.second)
189                     {
190                         if (propertyPair.first == "Id")
191                         {
192                             const uint32_t *id =
193                                 std::get_if<uint32_t>(&propertyPair.second);
194                             if (id != nullptr)
195                             {
196                                 ethData.vlan_id.push_back(*id);
197                             }
198                         }
199                     }
200                 }
201                 else if (ifacePair.first ==
202                          "xyz.openbmc_project.Network.EthernetInterface")
203                 {
204                     for (const auto &propertyPair : ifacePair.second)
205                     {
206                         if (propertyPair.first == "AutoNeg")
207                         {
208                             const bool *auto_neg =
209                                 std::get_if<bool>(&propertyPair.second);
210                             if (auto_neg != nullptr)
211                             {
212                                 ethData.auto_neg = *auto_neg;
213                             }
214                         }
215                         else if (propertyPair.first == "Speed")
216                         {
217                             const uint32_t *speed =
218                                 std::get_if<uint32_t>(&propertyPair.second);
219                             if (speed != nullptr)
220                             {
221                                 ethData.speed = *speed;
222                             }
223                         }
224                         else if (propertyPair.first == "Nameservers")
225                         {
226                             const std::vector<std::string> *nameservers =
227                                 sdbusplus::message::variant_ns::get_if<
228                                     std::vector<std::string>>(
229                                     &propertyPair.second);
230                             if (nameservers != nullptr)
231                             {
232                                 ethData.nameservers = std::move(*nameservers);
233                             }
234                         }
235                         else if (propertyPair.first == "DHCPEnabled")
236                         {
237                             const bool *DHCPEnabled =
238                                 std::get_if<bool>(&propertyPair.second);
239                             if (DHCPEnabled != nullptr)
240                             {
241                                 ethData.DHCPEnabled = *DHCPEnabled;
242                             }
243                         }
244                     }
245                 }
246             }
247             // System configuration shows up in the global namespace, so no need
248             // to check eth number
249             if (ifacePair.first ==
250                 "xyz.openbmc_project.Network.SystemConfiguration")
251             {
252                 for (const auto &propertyPair : ifacePair.second)
253                 {
254                     if (propertyPair.first == "HostName")
255                     {
256                         const std::string *hostname =
257                             sdbusplus::message::variant_ns::get_if<std::string>(
258                                 &propertyPair.second);
259                         if (hostname != nullptr)
260                         {
261                             ethData.hostname = *hostname;
262                         }
263                     }
264                     else if (propertyPair.first == "DefaultGateway")
265                     {
266                         const std::string *defaultGateway =
267                             sdbusplus::message::variant_ns::get_if<std::string>(
268                                 &propertyPair.second);
269                         if (defaultGateway != nullptr)
270                         {
271                             ethData.default_gateway = *defaultGateway;
272                         }
273                     }
274                     else if (propertyPair.first == "DefaultGateway6")
275                     {
276                         const std::string *defaultGateway6 =
277                             sdbusplus::message::variant_ns::get_if<std::string>(
278                                 &propertyPair.second);
279                         if (defaultGateway6 != nullptr)
280                         {
281                             ethData.ipv6_default_gateway = *defaultGateway6;
282                         }
283                     }
284                 }
285             }
286         }
287     }
288 }
289 
290 // Helper function that extracts data for single ethernet ipv4 address
291 inline void
292     extractIPData(const std::string &ethiface_id,
293                   const GetManagedObjects &dbus_data,
294                   boost::container::flat_set<IPv4AddressData> &ipv4_config)
295 {
296     const std::string ipv4PathStart =
297         "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/";
298 
299     // Since there might be several IPv4 configurations aligned with
300     // single ethernet interface, loop over all of them
301     for (const auto &objpath : dbus_data)
302     {
303         // Check if proper pattern for object path appears
304         if (boost::starts_with(objpath.first.str, ipv4PathStart))
305         {
306             for (auto &interface : objpath.second)
307             {
308                 if (interface.first == "xyz.openbmc_project.Network.IP")
309                 {
310                     // Instance IPv4AddressData structure, and set as
311                     // appropriate
312                     std::pair<
313                         boost::container::flat_set<IPv4AddressData>::iterator,
314                         bool>
315                         it = ipv4_config.insert(
316                             {objpath.first.str.substr(ipv4PathStart.size())});
317                     IPv4AddressData &ipv4_address = *it.first;
318                     for (auto &property : interface.second)
319                     {
320                         if (property.first == "Address")
321                         {
322                             const std::string *address =
323                                 std::get_if<std::string>(&property.second);
324                             if (address != nullptr)
325                             {
326                                 ipv4_address.address = *address;
327                             }
328                         }
329                         else if (property.first == "Gateway")
330                         {
331                             const std::string *gateway =
332                                 std::get_if<std::string>(&property.second);
333                             if (gateway != nullptr)
334                             {
335                                 ipv4_address.gateway = *gateway;
336                             }
337                         }
338                         else if (property.first == "Origin")
339                         {
340                             const std::string *origin =
341                                 std::get_if<std::string>(&property.second);
342                             if (origin != nullptr)
343                             {
344                                 ipv4_address.origin =
345                                     translateAddressOriginDbusToRedfish(*origin,
346                                                                         true);
347                             }
348                         }
349                         else if (property.first == "PrefixLength")
350                         {
351                             const uint8_t *mask =
352                                 std::get_if<uint8_t>(&property.second);
353                             if (mask != nullptr)
354                             {
355                                 // convert it to the string
356                                 ipv4_address.netmask = getNetmask(*mask);
357                             }
358                         }
359                         else
360                         {
361                             BMCWEB_LOG_ERROR
362                                 << "Got extra property: " << property.first
363                                 << " on the " << objpath.first.str << " object";
364                         }
365                     }
366                     // Check if given address is local, or global
367                     ipv4_address.linktype =
368                         boost::starts_with(ipv4_address.address, "169.254.")
369                             ? LinkType::Global
370                             : LinkType::Local;
371                 }
372             }
373         }
374     }
375 }
376 
377 /**
378  * @brief Sets given Id on the given VLAN interface through D-Bus
379  *
380  * @param[in] ifaceId       Id of VLAN interface that should be modified
381  * @param[in] inputVlanId   New ID of the VLAN
382  * @param[in] callback      Function that will be called after the operation
383  *
384  * @return None.
385  */
386 template <typename CallbackFunc>
387 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId,
388                   CallbackFunc &&callback)
389 {
390     crow::connections::systemBus->async_method_call(
391         callback, "xyz.openbmc_project.Network",
392         std::string("/xyz/openbmc_project/network/") + ifaceId,
393         "org.freedesktop.DBus.Properties", "Set",
394         "xyz.openbmc_project.Network.VLAN", "Id",
395         std::variant<uint32_t>(inputVlanId));
396 }
397 
398 /**
399  * @brief Helper function that verifies IP address to check if it is in
400  *        proper format. If bits pointer is provided, also calculates active
401  *        bit count for Subnet Mask.
402  *
403  * @param[in]  ip     IP that will be verified
404  * @param[out] bits   Calculated mask in bits notation
405  *
406  * @return true in case of success, false otherwise
407  */
408 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip,
409                                        uint8_t *bits = nullptr)
410 {
411     std::vector<std::string> bytesInMask;
412 
413     boost::split(bytesInMask, ip, boost::is_any_of("."));
414 
415     static const constexpr int ipV4AddressSectionsCount = 4;
416     if (bytesInMask.size() != ipV4AddressSectionsCount)
417     {
418         return false;
419     }
420 
421     if (bits != nullptr)
422     {
423         *bits = 0;
424     }
425 
426     char *endPtr;
427     long previousValue = 255;
428     bool firstZeroInByteHit;
429     for (const std::string &byte : bytesInMask)
430     {
431         if (byte.empty())
432         {
433             return false;
434         }
435 
436         // Use strtol instead of stroi to avoid exceptions
437         long value = std::strtol(byte.c_str(), &endPtr, 10);
438 
439         // endPtr should point to the end of the string, otherwise given string
440         // is not 100% number
441         if (*endPtr != '\0')
442         {
443             return false;
444         }
445 
446         // Value should be contained in byte
447         if (value < 0 || value > 255)
448         {
449             return false;
450         }
451 
452         if (bits != nullptr)
453         {
454             // Mask has to be continuous between bytes
455             if (previousValue != 255 && value != 0)
456             {
457                 return false;
458             }
459 
460             // Mask has to be continuous inside bytes
461             firstZeroInByteHit = false;
462 
463             // Count bits
464             for (int bitIdx = 7; bitIdx >= 0; bitIdx--)
465             {
466                 if (value & (1 << bitIdx))
467                 {
468                     if (firstZeroInByteHit)
469                     {
470                         // Continuity not preserved
471                         return false;
472                     }
473                     else
474                     {
475                         (*bits)++;
476                     }
477                 }
478                 else
479                 {
480                     firstZeroInByteHit = true;
481                 }
482             }
483         }
484 
485         previousValue = value;
486     }
487 
488     return true;
489 }
490 
491 /**
492  * @brief Changes IPv4 address type property (Address, Gateway)
493  *
494  * @param[in] ifaceId     Id of interface whose IP should be modified
495  * @param[in] ipIdx       Index of IP in input array that should be modified
496  * @param[in] ipHash      DBus Hash id of modified IP
497  * @param[in] name        Name of field in JSON representation
498  * @param[in] newValue    New value that should be written
499  * @param[io] asyncResp   Response object that will be returned to client
500  *
501  * @return true if give IP is valid and has been sent do D-Bus, false
502  * otherwise
503  */
504 inline void changeIPv4AddressProperty(
505     const std::string &ifaceId, int ipIdx, const std::string &ipHash,
506     const std::string &name, const std::string &newValue,
507     const std::shared_ptr<AsyncResp> asyncResp)
508 {
509     auto callback = [asyncResp, ipIdx, name{std::string(name)},
510                      newValue{std::move(newValue)}](
511                         const boost::system::error_code ec) {
512         if (ec)
513         {
514             messages::internalError(asyncResp->res);
515         }
516         else
517         {
518             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx][name] = newValue;
519         }
520     };
521 
522     crow::connections::systemBus->async_method_call(
523         std::move(callback), "xyz.openbmc_project.Network",
524         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
525         "org.freedesktop.DBus.Properties", "Set",
526         "xyz.openbmc_project.Network.IP", name,
527         std::variant<std::string>(newValue));
528 }
529 
530 /**
531  * @brief Changes IPv4 address origin property
532  *
533  * @param[in] ifaceId       Id of interface whose IP should be modified
534  * @param[in] ipIdx         Index of IP in input array that should be
535  * modified
536  * @param[in] ipHash        DBus Hash id of modified IP
537  * @param[in] newValue      New value in Redfish format
538  * @param[in] newValueDbus  New value in D-Bus format
539  * @param[io] asyncResp     Response object that will be returned to client
540  *
541  * @return true if give IP is valid and has been sent do D-Bus, false
542  * otherwise
543  */
544 inline void changeIPv4Origin(const std::string &ifaceId, int ipIdx,
545                              const std::string &ipHash,
546                              const std::string &newValue,
547                              const std::string &newValueDbus,
548                              const std::shared_ptr<AsyncResp> asyncResp)
549 {
550     auto callback = [asyncResp, ipIdx, newValue{std::move(newValue)}](
551                         const boost::system::error_code ec) {
552         if (ec)
553         {
554             messages::internalError(asyncResp->res);
555         }
556         else
557         {
558             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["AddressOrigin"] =
559                 newValue;
560         }
561     };
562 
563     crow::connections::systemBus->async_method_call(
564         std::move(callback), "xyz.openbmc_project.Network",
565         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
566         "org.freedesktop.DBus.Properties", "Set",
567         "xyz.openbmc_project.Network.IP", "Origin",
568         std::variant<std::string>(newValueDbus));
569 }
570 
571 /**
572  * @brief Modifies SubnetMask for given IP
573  *
574  * @param[in] ifaceId      Id of interface whose IP should be modified
575  * @param[in] ipIdx        Index of IP in input array that should be
576  * modified
577  * @param[in] ipHash       DBus Hash id of modified IP
578  * @param[in] newValueStr  Mask in dot notation as string
579  * @param[in] newValue     Mask as PrefixLength in bitcount
580  * @param[io] asyncResp   Response object that will be returned to client
581  *
582  * @return None
583  */
584 inline void changeIPv4SubnetMaskProperty(const std::string &ifaceId, int ipIdx,
585                                          const std::string &ipHash,
586                                          const std::string &newValueStr,
587                                          uint8_t &newValue,
588                                          std::shared_ptr<AsyncResp> asyncResp)
589 {
590     auto callback = [asyncResp, ipIdx, newValueStr{std::move(newValueStr)}](
591                         const boost::system::error_code ec) {
592         if (ec)
593         {
594             messages::internalError(asyncResp->res);
595         }
596         else
597         {
598             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["SubnetMask"] =
599                 newValueStr;
600         }
601     };
602 
603     crow::connections::systemBus->async_method_call(
604         std::move(callback), "xyz.openbmc_project.Network",
605         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
606         "org.freedesktop.DBus.Properties", "Set",
607         "xyz.openbmc_project.Network.IP", "PrefixLength",
608         std::variant<uint8_t>(newValue));
609 }
610 
611 /**
612  * @brief Deletes given IPv4
613  *
614  * @param[in] ifaceId     Id of interface whose IP should be deleted
615  * @param[in] ipIdx       Index of IP in input array that should be deleted
616  * @param[in] ipHash      DBus Hash id of IP that should be deleted
617  * @param[io] asyncResp   Response object that will be returned to client
618  *
619  * @return None
620  */
621 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash,
622                        unsigned int ipIdx,
623                        const std::shared_ptr<AsyncResp> asyncResp)
624 {
625     crow::connections::systemBus->async_method_call(
626         [ipIdx, asyncResp](const boost::system::error_code ec) {
627             if (ec)
628             {
629                 messages::internalError(asyncResp->res);
630             }
631             else
632             {
633                 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx] = nullptr;
634             }
635         },
636         "xyz.openbmc_project.Network",
637         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
638         "xyz.openbmc_project.Object.Delete", "Delete");
639 }
640 
641 /**
642  * @brief Creates IPv4 with given data
643  *
644  * @param[in] ifaceId     Id of interface whose IP should be deleted
645  * @param[in] ipIdx       Index of IP in input array that should be deleted
646  * @param[in] ipHash      DBus Hash id of IP that should be deleted
647  * @param[io] asyncResp   Response object that will be returned to client
648  *
649  * @return None
650  */
651 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx,
652                        uint8_t subnetMask, const std::string &gateway,
653                        const std::string &address,
654                        std::shared_ptr<AsyncResp> asyncResp)
655 {
656     auto createIpHandler = [asyncResp](const boost::system::error_code ec) {
657         if (ec)
658         {
659             messages::internalError(asyncResp->res);
660         }
661     };
662 
663     crow::connections::systemBus->async_method_call(
664         std::move(createIpHandler), "xyz.openbmc_project.Network",
665         "/xyz/openbmc_project/network/" + ifaceId,
666         "xyz.openbmc_project.Network.IP.Create", "IP",
667         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, subnetMask,
668         gateway);
669 }
670 using GetAllPropertiesType =
671     boost::container::flat_map<std::string, sdbusplus::message::variant<bool>>;
672 
673 inline void getDHCPConfigData(const std::shared_ptr<AsyncResp> asyncResp)
674 {
675     auto getConfig = [asyncResp](const boost::system::error_code error_code,
676                                  const GetAllPropertiesType &dbus_data) {
677         if (error_code)
678         {
679             BMCWEB_LOG_ERROR << "D-Bus response error: " << error_code;
680             messages::internalError(asyncResp->res);
681             return;
682         }
683         nlohmann::json &DHCPConfigTypeJson = asyncResp->res.jsonValue["DHCPv4"];
684         for (const auto &property : dbus_data)
685         {
686             auto value =
687                 sdbusplus::message::variant_ns::get_if<bool>(&property.second);
688 
689             if (value == nullptr)
690             {
691                 continue;
692             }
693             if (property.first == "DNSEnabled")
694             {
695                 DHCPConfigTypeJson["UseDNSServers"] = *value;
696             }
697             else if (property.first == "HostNameEnabled")
698             {
699                 DHCPConfigTypeJson["UseDomainName"] = *value;
700             }
701             else if (property.first == "NTPEnabled")
702             {
703                 DHCPConfigTypeJson["UseNTPServers"] = *value;
704             }
705         }
706     };
707     crow::connections::systemBus->async_method_call(
708         std::move(getConfig), "xyz.openbmc_project.Network",
709         "/xyz/openbmc_project/network/config/dhcp",
710         "org.freedesktop.DBus.Properties", "GetAll",
711         "xyz.openbmc_project.Network.DHCPConfiguration");
712 }
713 
714 /**
715  * Function that retrieves all properties for given Ethernet Interface
716  * Object
717  * from EntityManager Network Manager
718  * @param ethiface_id a eth interface id to query on DBus
719  * @param callback a function that shall be called to convert Dbus output
720  * into JSON
721  */
722 template <typename CallbackFunc>
723 void getEthernetIfaceData(const std::string &ethiface_id,
724                           CallbackFunc &&callback)
725 {
726     crow::connections::systemBus->async_method_call(
727         [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}](
728             const boost::system::error_code error_code,
729             const GetManagedObjects &resp) {
730             EthernetInterfaceData ethData{};
731             boost::container::flat_set<IPv4AddressData> ipv4Data;
732 
733             if (error_code)
734             {
735                 callback(false, ethData, ipv4Data);
736                 return;
737             }
738 
739             extractEthernetInterfaceData(ethiface_id, resp, ethData);
740             extractIPData(ethiface_id, resp, ipv4Data);
741 
742             // Fix global GW
743             for (IPv4AddressData &ipv4 : ipv4Data)
744             {
745                 if ((ipv4.linktype == LinkType::Global) &&
746                     (ipv4.gateway == "0.0.0.0"))
747                 {
748                     ipv4.gateway = ethData.default_gateway;
749                 }
750             }
751 
752             // Finally make a callback with usefull data
753             callback(true, ethData, ipv4Data);
754         },
755         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
756         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
757 };
758 
759 /**
760  * Function that retrieves all Ethernet Interfaces available through Network
761  * Manager
762  * @param callback a function that shall be called to convert Dbus output
763  * into JSON.
764  */
765 template <typename CallbackFunc>
766 void getEthernetIfaceList(CallbackFunc &&callback)
767 {
768     crow::connections::systemBus->async_method_call(
769         [callback{std::move(callback)}](
770             const boost::system::error_code error_code,
771             GetManagedObjects &resp) {
772             // Callback requires vector<string> to retrieve all available
773             // ethernet interfaces
774             std::vector<std::string> iface_list;
775             iface_list.reserve(resp.size());
776             if (error_code)
777             {
778                 callback(false, iface_list);
779                 return;
780             }
781 
782             // Iterate over all retrieved ObjectPaths.
783             for (const auto &objpath : resp)
784             {
785                 // And all interfaces available for certain ObjectPath.
786                 for (const auto &interface : objpath.second)
787                 {
788                     // If interface is
789                     // xyz.openbmc_project.Network.EthernetInterface, this is
790                     // what we're looking for.
791                     if (interface.first ==
792                         "xyz.openbmc_project.Network.EthernetInterface")
793                     {
794                         // Cut out everyting until last "/", ...
795                         const std::string &iface_id = objpath.first.str;
796                         std::size_t last_pos = iface_id.rfind("/");
797                         if (last_pos != std::string::npos)
798                         {
799                             // and put it into output vector.
800                             iface_list.emplace_back(
801                                 iface_id.substr(last_pos + 1));
802                         }
803                     }
804                 }
805             }
806             // Finally make a callback with useful data
807             callback(true, iface_list);
808         },
809         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
810         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
811 };
812 
813 /**
814  * EthernetCollection derived class for delivering Ethernet Collection Schema
815  */
816 class EthernetCollection : public Node
817 {
818   public:
819     template <typename CrowApp>
820     EthernetCollection(CrowApp &app) :
821         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
822     {
823         entityPrivileges = {
824             {boost::beast::http::verb::get, {{"Login"}}},
825             {boost::beast::http::verb::head, {{"Login"}}},
826             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
827             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
828             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
829             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
830     }
831 
832   private:
833     /**
834      * Functions triggers appropriate requests on DBus
835      */
836     void doGet(crow::Response &res, const crow::Request &req,
837                const std::vector<std::string> &params) override
838     {
839         res.jsonValue["@odata.type"] =
840             "#EthernetInterfaceCollection.EthernetInterfaceCollection";
841         res.jsonValue["@odata.context"] =
842             "/redfish/v1/"
843             "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection";
844         res.jsonValue["@odata.id"] =
845             "/redfish/v1/Managers/bmc/EthernetInterfaces";
846         res.jsonValue["Name"] = "Ethernet Network Interface Collection";
847         res.jsonValue["Description"] =
848             "Collection of EthernetInterfaces for this Manager";
849 
850         // Get eth interface list, and call the below callback for JSON
851         // preparation
852         getEthernetIfaceList(
853             [&res](const bool &success,
854                    const std::vector<std::string> &iface_list) {
855                 if (!success)
856                 {
857                     messages::internalError(res);
858                     res.end();
859                     return;
860                 }
861 
862                 nlohmann::json &iface_array = res.jsonValue["Members"];
863                 iface_array = nlohmann::json::array();
864                 std::string tag = "_";
865                 for (const std::string &iface_item : iface_list)
866                 {
867                     std::size_t found = iface_item.find(tag);
868                     if (found == std::string::npos)
869                     {
870                         iface_array.push_back(
871                             {{"@odata.id",
872                               "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
873                                   iface_item}});
874                     }
875                 }
876 
877                 res.jsonValue["Members@odata.count"] = iface_array.size();
878                 res.jsonValue["@odata.id"] =
879                     "/redfish/v1/Managers/bmc/EthernetInterfaces";
880                 res.end();
881             });
882     }
883 };
884 
885 /**
886  * EthernetInterface derived class for delivering Ethernet Schema
887  */
888 class EthernetInterface : public Node
889 {
890   public:
891     /*
892      * Default Constructor
893      */
894     template <typename CrowApp>
895     EthernetInterface(CrowApp &app) :
896         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/",
897              std::string())
898     {
899         entityPrivileges = {
900             {boost::beast::http::verb::get, {{"Login"}}},
901             {boost::beast::http::verb::head, {{"Login"}}},
902             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
903             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
904             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
905             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
906     }
907 
908   private:
909     void handleHostnamePatch(const std::string &hostname,
910                              const std::shared_ptr<AsyncResp> asyncResp)
911     {
912         asyncResp->res.jsonValue["HostName"] = hostname;
913         crow::connections::systemBus->async_method_call(
914             [asyncResp](const boost::system::error_code ec) {
915                 if (ec)
916                 {
917                     messages::internalError(asyncResp->res);
918                 }
919             },
920             "xyz.openbmc_project.Network",
921             "/xyz/openbmc_project/network/config",
922             "org.freedesktop.DBus.Properties", "Set",
923             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
924             std::variant<std::string>(hostname));
925     }
926 
927     void handleMACAddressPatch(const std::string &ifaceId,
928                                const std::string &macAddress,
929                                const std::shared_ptr<AsyncResp> &asyncResp)
930     {
931         crow::connections::systemBus->async_method_call(
932             [asyncResp, macAddress](const boost::system::error_code ec) {
933                 if (ec)
934                 {
935                     messages::internalError(asyncResp->res);
936                     return;
937                 }
938                 asyncResp->res.jsonValue["MACAddress"] = std::move(macAddress);
939             },
940             "xyz.openbmc_project.Network",
941             "/xyz/openbmc_project/network/" + ifaceId,
942             "org.freedesktop.DBus.Properties", "Set",
943             "xyz.openbmc_project.Network.MACAddress", "MACAddress",
944             std::variant<std::string>(macAddress));
945     }
946 
947     void handleIPv4Patch(
948         const std::string &ifaceId, nlohmann::json &input,
949         const boost::container::flat_set<IPv4AddressData> &ipv4Data,
950         const std::shared_ptr<AsyncResp> asyncResp)
951     {
952         if (!input.is_array())
953         {
954             messages::propertyValueTypeError(asyncResp->res, input.dump(),
955                                              "IPv4Addresses");
956             return;
957         }
958 
959         int entryIdx = 0;
960         boost::container::flat_set<IPv4AddressData>::const_iterator thisData =
961             ipv4Data.begin();
962         for (nlohmann::json &thisJson : input)
963         {
964             std::string pathString =
965                 "IPv4Addresses/" + std::to_string(entryIdx);
966 
967             if (thisJson.is_null())
968             {
969                 if (thisData != ipv4Data.end())
970                 {
971                     deleteIPv4(ifaceId, thisData->id, entryIdx, asyncResp);
972                     thisData++;
973                 }
974                 else
975                 {
976                     messages::propertyValueFormatError(
977                         asyncResp->res, input.dump(), pathString);
978                     return;
979                     // TODO(ratagupt) Not sure about the property where value is
980                     // list and if unable to update one of the
981                     // list value then should we proceed further or
982                     // break there, would ask in the redfish forum
983                     // till then we stop processing the next list item.
984                 }
985                 entryIdx++;
986                 continue; // not an error as per the redfish spec.
987             }
988 
989             if (thisJson.empty())
990             {
991                 if (thisData != ipv4Data.end())
992                 {
993                     thisData++;
994                 }
995                 else
996                 {
997                     messages::propertyMissing(asyncResp->res,
998                                               pathString + "/Address");
999                     return;
1000                     // TODO(ratagupt) Not sure about the property where value is
1001                     // list and if unable to update one of the
1002                     // list value then should we proceed further or
1003                     // break there, would ask in the redfish forum
1004                     // till then we stop processing the next list item.
1005                 }
1006                 entryIdx++;
1007                 continue; // not an error as per the redfish spec.
1008             }
1009 
1010             std::optional<std::string> address;
1011             std::optional<std::string> addressOrigin;
1012             std::optional<std::string> subnetMask;
1013             std::optional<std::string> gateway;
1014 
1015             if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1016                                      address, "AddressOrigin", addressOrigin,
1017                                      "SubnetMask", subnetMask, "Gateway",
1018                                      gateway))
1019             {
1020                 return;
1021             }
1022 
1023             if (address)
1024             {
1025                 if (!ipv4VerifyIpAndGetBitcount(*address))
1026                 {
1027                     messages::propertyValueFormatError(asyncResp->res, *address,
1028                                                        pathString + "/Address");
1029                     return;
1030                 }
1031             }
1032 
1033             uint8_t prefixLength = 0;
1034             if (subnetMask)
1035             {
1036                 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength))
1037                 {
1038                     messages::propertyValueFormatError(
1039                         asyncResp->res, *subnetMask,
1040                         pathString + "/SubnetMask");
1041                     return;
1042                 }
1043             }
1044             std::string addressOriginInDBusFormat;
1045             if (addressOrigin)
1046             {
1047                 // Get Address origin in proper format
1048                 addressOriginInDBusFormat =
1049                     translateAddressOriginRedfishToDbus(*addressOrigin);
1050                 if (addressOriginInDBusFormat.empty())
1051                 {
1052                     messages::propertyValueNotInList(
1053                         asyncResp->res, *addressOrigin,
1054                         pathString + "/AddressOrigin");
1055                     return;
1056                 }
1057             }
1058 
1059             if (gateway)
1060             {
1061                 if (!ipv4VerifyIpAndGetBitcount(*gateway))
1062                 {
1063                     messages::propertyValueFormatError(asyncResp->res, *gateway,
1064                                                        pathString + "/Gateway");
1065                     return;
1066                 }
1067             }
1068 
1069             // if IP address exist then  modify it.
1070             if (thisData != ipv4Data.end())
1071             {
1072                 // Apply changes
1073                 if (address)
1074                 {
1075                     auto callback = [asyncResp, entryIdx,
1076                                      address{std::string(*address)}](
1077                                         const boost::system::error_code ec) {
1078                         if (ec)
1079                         {
1080                             messages::internalError(asyncResp->res);
1081                             return;
1082                         }
1083                         asyncResp->res
1084                             .jsonValue["IPv4Addresses"][entryIdx]["Address"] =
1085                             std::move(address);
1086                     };
1087 
1088                     crow::connections::systemBus->async_method_call(
1089                         std::move(callback), "xyz.openbmc_project.Network",
1090                         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" +
1091                             thisData->id,
1092                         "org.freedesktop.DBus.Properties", "Set",
1093                         "xyz.openbmc_project.Network.IP", "Address",
1094                         std::variant<std::string>(*address));
1095                 }
1096 
1097                 if (subnetMask)
1098                 {
1099                     changeIPv4SubnetMaskProperty(ifaceId, entryIdx,
1100                                                  thisData->id, *subnetMask,
1101                                                  prefixLength, asyncResp);
1102                 }
1103 
1104                 if (addressOrigin)
1105                 {
1106                     changeIPv4Origin(ifaceId, entryIdx, thisData->id,
1107                                      *addressOrigin, addressOriginInDBusFormat,
1108                                      asyncResp);
1109                 }
1110 
1111                 if (gateway)
1112                 {
1113                     auto callback = [asyncResp, entryIdx,
1114                                      gateway{std::string(*gateway)}](
1115                                         const boost::system::error_code ec) {
1116                         if (ec)
1117                         {
1118                             messages::internalError(asyncResp->res);
1119                             return;
1120                         }
1121                         asyncResp->res
1122                             .jsonValue["IPv4Addresses"][entryIdx]["Gateway"] =
1123                             std::move(gateway);
1124                     };
1125 
1126                     crow::connections::systemBus->async_method_call(
1127                         std::move(callback), "xyz.openbmc_project.Network",
1128                         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" +
1129                             thisData->id,
1130                         "org.freedesktop.DBus.Properties", "Set",
1131                         "xyz.openbmc_project.Network.IP", "Gateway",
1132                         std::variant<std::string>(*gateway));
1133                 }
1134 
1135                 thisData++;
1136             }
1137             else
1138             {
1139                 // Create IPv4 with provided data
1140                 if (!gateway)
1141                 {
1142                     messages::propertyMissing(asyncResp->res,
1143                                               pathString + "/Gateway");
1144                     continue;
1145                 }
1146 
1147                 if (!address)
1148                 {
1149                     messages::propertyMissing(asyncResp->res,
1150                                               pathString + "/Address");
1151                     continue;
1152                 }
1153 
1154                 if (!subnetMask)
1155                 {
1156                     messages::propertyMissing(asyncResp->res,
1157                                               pathString + "/SubnetMask");
1158                     continue;
1159                 }
1160 
1161                 createIPv4(ifaceId, entryIdx, prefixLength, *gateway, *address,
1162                            asyncResp);
1163 
1164                 nlohmann::json &ipv4AddressJson =
1165                     asyncResp->res.jsonValue["IPv4Addresses"][entryIdx];
1166                 ipv4AddressJson["Address"] = *address;
1167                 ipv4AddressJson["SubnetMask"] = *subnetMask;
1168                 ipv4AddressJson["Gateway"] = *gateway;
1169             }
1170             entryIdx++;
1171         }
1172     }
1173 
1174     void handleStaticNameServersPatch(
1175         const std::string &ifaceId,
1176         const std::vector<std::string> &updatedStaticNameServers,
1177         const std::shared_ptr<AsyncResp> &asyncResp)
1178     {
1179         crow::connections::systemBus->async_method_call(
1180             [asyncResp,
1181              updatedStaticNameServers](const boost::system::error_code ec) {
1182                 if (ec)
1183                 {
1184                     messages::internalError(asyncResp->res);
1185                     return;
1186                 }
1187                 asyncResp->res.jsonValue["NameServers"] =
1188                     updatedStaticNameServers;
1189                 asyncResp->res.jsonValue["StaticNameServers"] =
1190                     updatedStaticNameServers;
1191             },
1192             "xyz.openbmc_project.Network",
1193             "/xyz/openbmc_project/network/" + ifaceId,
1194             "org.freedesktop.DBus.Properties", "Set",
1195             "xyz.openbmc_project.Network.EthernetInterface", "Nameservers",
1196             std::variant<std::vector<std::string>>{updatedStaticNameServers});
1197     }
1198 
1199     void parseInterfaceData(
1200         nlohmann::json &json_response, const std::string &iface_id,
1201         const EthernetInterfaceData &ethData,
1202         const boost::container::flat_set<IPv4AddressData> &ipv4Data)
1203     {
1204         json_response["Id"] = iface_id;
1205         json_response["@odata.id"] =
1206             "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id;
1207         json_response["InterfaceEnabled"] = true;
1208         if (ethData.speed == 0)
1209         {
1210             json_response["LinkStatus"] = "NoLink";
1211             json_response["Status"] = {
1212                 {"Health", "OK"},
1213                 {"State", "Disabled"},
1214             };
1215         }
1216         else
1217         {
1218             json_response["LinkStatus"] = "LinkUp";
1219             json_response["Status"] = {
1220                 {"Health", "OK"},
1221                 {"State", "Enabled"},
1222             };
1223         }
1224         json_response["SpeedMbps"] = ethData.speed;
1225         json_response["MACAddress"] = ethData.mac_address;
1226         json_response["DHCPv4"]["DHCPEnabled"] = ethData.DHCPEnabled;
1227 
1228         if (!ethData.hostname.empty())
1229         {
1230             json_response["HostName"] = ethData.hostname;
1231         }
1232 
1233         json_response["VLANs"] = {
1234             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1235                               iface_id + "/VLANs"}};
1236 
1237         json_response["NameServers"] = ethData.nameservers;
1238         json_response["StaticNameServers"] = ethData.nameservers;
1239 
1240         if (ipv4Data.size() > 0)
1241         {
1242             nlohmann::json &ipv4_array = json_response["IPv4Addresses"];
1243             ipv4_array = nlohmann::json::array();
1244             for (auto &ipv4_config : ipv4Data)
1245             {
1246 
1247                 std::string gatewayStr = ipv4_config.gateway;
1248                 if (gatewayStr.empty())
1249                 {
1250                     gatewayStr = "0.0.0.0";
1251                 }
1252 
1253                 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin},
1254                                       {"SubnetMask", ipv4_config.netmask},
1255                                       {"Address", ipv4_config.address},
1256                                       {"Gateway", gatewayStr}});
1257             }
1258         }
1259         json_response["IPv6DefaultGateway"] = ethData.ipv6_default_gateway;
1260     }
1261 
1262     /**
1263      * Functions triggers appropriate requests on DBus
1264      */
1265     void doGet(crow::Response &res, const crow::Request &req,
1266                const std::vector<std::string> &params) override
1267     {
1268         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1269         if (params.size() != 1)
1270         {
1271             messages::internalError(asyncResp->res);
1272             return;
1273         }
1274 
1275         getEthernetIfaceData(
1276             params[0],
1277             [this, asyncResp, iface_id{std::string(params[0])}](
1278                 const bool &success, const EthernetInterfaceData &ethData,
1279                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1280                 if (!success)
1281                 {
1282                     // TODO(Pawel)consider distinguish between non existing
1283                     // object, and other errors
1284                     messages::resourceNotFound(asyncResp->res,
1285                                                "EthernetInterface", iface_id);
1286                     return;
1287                 }
1288                 asyncResp->res.jsonValue["@odata.type"] =
1289                     "#EthernetInterface.v1_4_1.EthernetInterface";
1290                 asyncResp->res.jsonValue["@odata.context"] =
1291                     "/redfish/v1/$metadata#EthernetInterface.EthernetInterface";
1292                 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface";
1293                 asyncResp->res.jsonValue["Description"] =
1294                     "Management Network Interface";
1295 
1296                 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData,
1297                                    ipv4Data);
1298             });
1299         getDHCPConfigData(asyncResp);
1300     }
1301 
1302     void doPatch(crow::Response &res, const crow::Request &req,
1303                  const std::vector<std::string> &params) override
1304     {
1305         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1306         if (params.size() != 1)
1307         {
1308             messages::internalError(asyncResp->res);
1309             return;
1310         }
1311 
1312         const std::string &iface_id = params[0];
1313 
1314         std::optional<std::string> hostname;
1315         std::optional<std::string> macAddress;
1316         std::optional<std::string> ipv6DefaultGateway;
1317         std::optional<nlohmann::json> ipv4Addresses;
1318         std::optional<nlohmann::json> ipv6Addresses;
1319         std::optional<std::vector<std::string>> staticNameServers;
1320         std::optional<std::vector<std::string>> nameServers;
1321 
1322         if (!json_util::readJson(
1323                 req, res, "HostName", hostname, "IPv4Addresses", ipv4Addresses,
1324                 "IPv6Addresses", ipv6Addresses, "MACAddress", macAddress,
1325                 "StaticNameServers", staticNameServers, "IPv6DefaultGateway",
1326                 ipv6DefaultGateway, "NameServers", nameServers))
1327         {
1328             return;
1329         }
1330 
1331         // Get single eth interface data, and call the below callback for JSON
1332         // preparation
1333         getEthernetIfaceData(
1334             iface_id,
1335             [this, asyncResp, iface_id, hostname = std::move(hostname),
1336              macAddress = std::move(macAddress),
1337              ipv4Addresses = std::move(ipv4Addresses),
1338              ipv6Addresses = std::move(ipv6Addresses),
1339              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
1340              staticNameServers = std::move(staticNameServers),
1341              nameServers = std::move(nameServers)](
1342                 const bool &success, const EthernetInterfaceData &ethData,
1343                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1344                 if (!success)
1345                 {
1346                     // ... otherwise return error
1347                     // TODO(Pawel)consider distinguish between non existing
1348                     // object, and other errors
1349                     messages::resourceNotFound(asyncResp->res,
1350                                                "Ethernet Interface", iface_id);
1351                     return;
1352                 }
1353 
1354                 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData,
1355                                    ipv4Data);
1356 
1357                 if (hostname)
1358                 {
1359                     handleHostnamePatch(*hostname, asyncResp);
1360                 }
1361 
1362                 if (macAddress)
1363                 {
1364                     handleMACAddressPatch(iface_id, *macAddress, asyncResp);
1365                 }
1366 
1367                 if (ipv4Addresses)
1368                 {
1369                     // TODO(ed) for some reason the capture of ipv4Addresses
1370                     // above is returning a const value, not a non-const value.
1371                     // This doesn't really work for us, as we need to be able to
1372                     // efficiently move out the intermedia nlohmann::json
1373                     // objects. This makes a copy of the structure, and operates
1374                     // on that, but could be done more efficiently
1375                     nlohmann::json ipv4 = std::move(*ipv4Addresses);
1376                     handleIPv4Patch(iface_id, ipv4, ipv4Data, asyncResp);
1377                 }
1378 
1379                 if (nameServers)
1380                 {
1381                     // Data.Permissions is read-only
1382                     messages::propertyNotWritable(asyncResp->res,
1383                                                   "NameServers");
1384                 }
1385 
1386                 if (ipv6Addresses)
1387                 {
1388                     // TODO(kkowalsk) IPv6 Not supported on D-Bus yet
1389                     messages::propertyNotWritable(asyncResp->res,
1390                                                   "IPv6Addresses");
1391                 }
1392 
1393                 if (staticNameServers)
1394                 {
1395                     handleStaticNameServersPatch(iface_id, *staticNameServers,
1396                                                  asyncResp);
1397                 }
1398 
1399                 if (ipv6DefaultGateway)
1400                 {
1401                     messages::propertyNotWritable(asyncResp->res,
1402                                                   "IPv6DefaultGateway");
1403                 }
1404             });
1405     }
1406 };
1407 
1408 /**
1409  * VlanNetworkInterface derived class for delivering VLANNetworkInterface
1410  * Schema
1411  */
1412 class VlanNetworkInterface : public Node
1413 {
1414   public:
1415     /*
1416      * Default Constructor
1417      */
1418     template <typename CrowApp>
1419     VlanNetworkInterface(CrowApp &app) :
1420         Node(app,
1421              "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>",
1422              std::string(), std::string())
1423     {
1424         entityPrivileges = {
1425             {boost::beast::http::verb::get, {{"Login"}}},
1426             {boost::beast::http::verb::head, {{"Login"}}},
1427             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1428             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1429             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1430             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1431     }
1432 
1433   private:
1434     void parseInterfaceData(
1435         nlohmann::json &json_response, const std::string &parent_iface_id,
1436         const std::string &iface_id, const EthernetInterfaceData &ethData,
1437         const boost::container::flat_set<IPv4AddressData> &ipv4Data)
1438     {
1439         // Fill out obvious data...
1440         json_response["Id"] = iface_id;
1441         json_response["@odata.id"] =
1442             "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id +
1443             "/VLANs/" + iface_id;
1444 
1445         json_response["VLANEnable"] = true;
1446         if (!ethData.vlan_id.empty())
1447         {
1448             json_response["VLANId"] = ethData.vlan_id.back();
1449         }
1450     }
1451 
1452     bool verifyNames(const std::string &parent, const std::string &iface)
1453     {
1454         if (!boost::starts_with(iface, parent + "_"))
1455         {
1456             return false;
1457         }
1458         else
1459         {
1460             return true;
1461         }
1462     }
1463 
1464     /**
1465      * Functions triggers appropriate requests on DBus
1466      */
1467     void doGet(crow::Response &res, const crow::Request &req,
1468                const std::vector<std::string> &params) override
1469     {
1470         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1471         // TODO(Pawel) this shall be parameterized call (two params) to get
1472         // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1473         // Check if there is required param, truly entering this shall be
1474         // impossible.
1475         if (params.size() != 2)
1476         {
1477             messages::internalError(res);
1478             res.end();
1479             return;
1480         }
1481 
1482         const std::string &parent_iface_id = params[0];
1483         const std::string &iface_id = params[1];
1484         res.jsonValue["@odata.type"] =
1485             "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
1486         res.jsonValue["@odata.context"] =
1487             "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface";
1488         res.jsonValue["Name"] = "VLAN Network Interface";
1489 
1490         if (!verifyNames(parent_iface_id, iface_id))
1491         {
1492             return;
1493         }
1494 
1495         // Get single eth interface data, and call the below callback for JSON
1496         // preparation
1497         getEthernetIfaceData(
1498             params[1],
1499             [this, asyncResp, parent_iface_id{std::string(params[0])},
1500              iface_id{std::string(params[1])}](
1501                 const bool &success, const EthernetInterfaceData &ethData,
1502                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1503                 if (success && ethData.vlan_id.size() != 0)
1504                 {
1505                     parseInterfaceData(asyncResp->res.jsonValue,
1506                                        parent_iface_id, iface_id, ethData,
1507                                        ipv4Data);
1508                 }
1509                 else
1510                 {
1511                     // ... otherwise return error
1512                     // TODO(Pawel)consider distinguish between non existing
1513                     // object, and other errors
1514                     messages::resourceNotFound(
1515                         asyncResp->res, "VLAN Network Interface", iface_id);
1516                 }
1517             });
1518     }
1519 
1520     void doPatch(crow::Response &res, const crow::Request &req,
1521                  const std::vector<std::string> &params) override
1522     {
1523         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1524         if (params.size() != 2)
1525         {
1526             messages::internalError(asyncResp->res);
1527             return;
1528         }
1529 
1530         const std::string &parentIfaceId = params[0];
1531         const std::string &ifaceId = params[1];
1532 
1533         if (!verifyNames(parentIfaceId, ifaceId))
1534         {
1535             messages::resourceNotFound(asyncResp->res, "VLAN Network Interface",
1536                                        ifaceId);
1537             return;
1538         }
1539 
1540         bool vlanEnable = false;
1541         uint64_t vlanId = 0;
1542 
1543         if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId",
1544                                  vlanId))
1545         {
1546             return;
1547         }
1548 
1549         // Get single eth interface data, and call the below callback for JSON
1550         // preparation
1551         getEthernetIfaceData(params[1], [this, asyncResp,
1552                                          parentIfaceId{std::string(params[0])},
1553                                          ifaceId{std::string(params[1])},
1554                                          &vlanEnable, &vlanId](
1555                                             const bool &success,
1556                                             const EthernetInterfaceData
1557                                                 &ethData,
1558                                             const boost::container::flat_set<
1559                                                 IPv4AddressData> &ipv4Data) {
1560             if (success && !ethData.vlan_id.empty())
1561             {
1562                 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId,
1563                                    ifaceId, ethData, ipv4Data);
1564                 auto callback =
1565                     [asyncResp](const boost::system::error_code ec) {
1566                         if (ec)
1567                         {
1568                             messages::internalError(asyncResp->res);
1569                         }
1570                     };
1571 
1572                 if (vlanEnable == true)
1573                 {
1574                     crow::connections::systemBus->async_method_call(
1575                         std::move(callback), "xyz.openbmc_project.Network",
1576                         "/xyz/openbmc_project/network/" + ifaceId,
1577                         "org.freedesktop.DBus.Properties", "Set",
1578                         "xyz.openbmc_project.Network.VLAN", "Id",
1579                         std::variant<uint32_t>(vlanId));
1580                 }
1581                 else
1582                 {
1583                     BMCWEB_LOG_DEBUG
1584                         << "vlanEnable is false. Deleting the vlan interface";
1585                     crow::connections::systemBus->async_method_call(
1586                         std::move(callback), "xyz.openbmc_project.Network",
1587                         std::string("/xyz/openbmc_project/network/") + ifaceId,
1588                         "xyz.openbmc_project.Object.Delete", "Delete");
1589                 }
1590             }
1591             else
1592             {
1593                 // TODO(Pawel)consider distinguish between non existing
1594                 // object, and other errors
1595                 messages::resourceNotFound(asyncResp->res,
1596                                            "VLAN Network Interface", ifaceId);
1597                 return;
1598             }
1599         });
1600     }
1601 
1602     void doDelete(crow::Response &res, const crow::Request &req,
1603                   const std::vector<std::string> &params) override
1604     {
1605         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1606         if (params.size() != 2)
1607         {
1608             messages::internalError(asyncResp->res);
1609             return;
1610         }
1611 
1612         const std::string &parentIfaceId = params[0];
1613         const std::string &ifaceId = params[1];
1614 
1615         if (!verifyNames(parentIfaceId, ifaceId))
1616         {
1617             messages::resourceNotFound(asyncResp->res, "VLAN Network Interface",
1618                                        ifaceId);
1619             return;
1620         }
1621 
1622         // Get single eth interface data, and call the below callback for JSON
1623         // preparation
1624         getEthernetIfaceData(
1625             params[1],
1626             [this, asyncResp, parentIfaceId{std::string(params[0])},
1627              ifaceId{std::string(params[1])}](
1628                 const bool &success, const EthernetInterfaceData &ethData,
1629                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1630                 if (success && !ethData.vlan_id.empty())
1631                 {
1632                     parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId,
1633                                        ifaceId, ethData, ipv4Data);
1634 
1635                     auto callback =
1636                         [asyncResp](const boost::system::error_code ec) {
1637                             if (ec)
1638                             {
1639                                 messages::internalError(asyncResp->res);
1640                             }
1641                         };
1642                     crow::connections::systemBus->async_method_call(
1643                         std::move(callback), "xyz.openbmc_project.Network",
1644                         std::string("/xyz/openbmc_project/network/") + ifaceId,
1645                         "xyz.openbmc_project.Object.Delete", "Delete");
1646                 }
1647                 else
1648                 {
1649                     // ... otherwise return error
1650                     // TODO(Pawel)consider distinguish between non existing
1651                     // object, and other errors
1652                     messages::resourceNotFound(
1653                         asyncResp->res, "VLAN Network Interface", ifaceId);
1654                 }
1655             });
1656     }
1657 };
1658 
1659 /**
1660  * VlanNetworkInterfaceCollection derived class for delivering
1661  * VLANNetworkInterface Collection Schema
1662  */
1663 class VlanNetworkInterfaceCollection : public Node
1664 {
1665   public:
1666     template <typename CrowApp>
1667     VlanNetworkInterfaceCollection(CrowApp &app) :
1668         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/",
1669              std::string())
1670     {
1671         entityPrivileges = {
1672             {boost::beast::http::verb::get, {{"Login"}}},
1673             {boost::beast::http::verb::head, {{"Login"}}},
1674             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1675             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1676             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1677             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1678     }
1679 
1680   private:
1681     /**
1682      * Functions triggers appropriate requests on DBus
1683      */
1684     void doGet(crow::Response &res, const crow::Request &req,
1685                const std::vector<std::string> &params) override
1686     {
1687         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1688         if (params.size() != 1)
1689         {
1690             // This means there is a problem with the router
1691             messages::internalError(asyncResp->res);
1692             return;
1693         }
1694 
1695         const std::string &rootInterfaceName = params[0];
1696 
1697         // Get eth interface list, and call the below callback for JSON
1698         // preparation
1699         getEthernetIfaceList(
1700             [asyncResp, rootInterfaceName{std::string(rootInterfaceName)}](
1701                 const bool &success,
1702                 const std::vector<std::string> &iface_list) {
1703                 if (!success)
1704                 {
1705                     messages::internalError(asyncResp->res);
1706                     return;
1707                 }
1708                 asyncResp->res.jsonValue["@odata.type"] =
1709                     "#VLanNetworkInterfaceCollection."
1710                     "VLanNetworkInterfaceCollection";
1711                 asyncResp->res.jsonValue["@odata.context"] =
1712                     "/redfish/v1/$metadata"
1713                     "#VLanNetworkInterfaceCollection."
1714                     "VLanNetworkInterfaceCollection";
1715                 asyncResp->res.jsonValue["Name"] =
1716                     "VLAN Network Interface Collection";
1717 
1718                 nlohmann::json iface_array = nlohmann::json::array();
1719 
1720                 for (const std::string &iface_item : iface_list)
1721                 {
1722                     if (boost::starts_with(iface_item, rootInterfaceName + "_"))
1723                     {
1724                         iface_array.push_back(
1725                             {{"@odata.id",
1726                               "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1727                                   rootInterfaceName + "/VLANs/" + iface_item}});
1728                     }
1729                 }
1730 
1731                 asyncResp->res.jsonValue["Members@odata.count"] =
1732                     iface_array.size();
1733                 asyncResp->res.jsonValue["Members"] = std::move(iface_array);
1734                 asyncResp->res.jsonValue["@odata.id"] =
1735                     "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1736                     rootInterfaceName + "/VLANs";
1737             });
1738     }
1739 
1740     void doPost(crow::Response &res, const crow::Request &req,
1741                 const std::vector<std::string> &params) override
1742     {
1743         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1744         if (params.size() != 1)
1745         {
1746             messages::internalError(asyncResp->res);
1747             return;
1748         }
1749         bool vlanEnable = false;
1750         uint32_t vlanId = 0;
1751         if (!json_util::readJson(req, res, "VLANId", vlanId, "VLANEnable",
1752                                  vlanEnable))
1753         {
1754             return;
1755         }
1756         // Need both vlanId and vlanEnable to service this request
1757         if (!vlanId)
1758         {
1759             messages::propertyMissing(asyncResp->res, "VLANId");
1760         }
1761         if (!vlanEnable)
1762         {
1763             messages::propertyMissing(asyncResp->res, "VLANEnable");
1764         }
1765         if (static_cast<bool>(vlanId) ^ static_cast<bool>(vlanEnable))
1766         {
1767             return;
1768         }
1769 
1770         const std::string &rootInterfaceName = params[0];
1771         auto callback = [asyncResp](const boost::system::error_code ec) {
1772             if (ec)
1773             {
1774                 // TODO(ed) make more consistent error messages based on
1775                 // phosphor-network responses
1776                 messages::internalError(asyncResp->res);
1777                 return;
1778             }
1779             messages::created(asyncResp->res);
1780         };
1781         crow::connections::systemBus->async_method_call(
1782             std::move(callback), "xyz.openbmc_project.Network",
1783             "/xyz/openbmc_project/network",
1784             "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
1785             rootInterfaceName, vlanId);
1786     }
1787 };
1788 } // namespace redfish
1789