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