xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision 3927e13ebcd7e4b30b64f7787f76c42b5c35d089)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include <app.hpp>
19 #include <boost/container/flat_set.hpp>
20 #include <dbus_singleton.hpp>
21 #include <dbus_utility.hpp>
22 #include <error_messages.hpp>
23 #include <query.hpp>
24 #include <registries/privilege_registry.hpp>
25 #include <utils/json_utils.hpp>
26 
27 #include <optional>
28 #include <regex>
29 
30 namespace redfish
31 {
32 
33 enum class LinkType
34 {
35     Local,
36     Global
37 };
38 
39 /**
40  * Structure for keeping IPv4 data required by Redfish
41  */
42 struct IPv4AddressData
43 {
44     std::string id;
45     std::string address;
46     std::string domain;
47     std::string gateway;
48     std::string netmask;
49     std::string origin;
50     LinkType linktype;
51     bool isActive;
52 
53     bool operator<(const IPv4AddressData& obj) const
54     {
55         return id < obj.id;
56     }
57 };
58 
59 /**
60  * Structure for keeping IPv6 data required by Redfish
61  */
62 struct IPv6AddressData
63 {
64     std::string id;
65     std::string address;
66     std::string origin;
67     uint8_t prefixLength;
68 
69     bool operator<(const IPv6AddressData& obj) const
70     {
71         return id < obj.id;
72     }
73 };
74 /**
75  * Structure for keeping basic single Ethernet Interface information
76  * available from DBus
77  */
78 struct EthernetInterfaceData
79 {
80     uint32_t speed;
81     size_t mtuSize;
82     bool autoNeg;
83     bool dnsEnabled;
84     bool ntpEnabled;
85     bool hostNameEnabled;
86     bool linkUp;
87     bool nicEnabled;
88     std::string dhcpEnabled;
89     std::string operatingMode;
90     std::string hostName;
91     std::string defaultGateway;
92     std::string ipv6DefaultGateway;
93     std::string macAddress;
94     std::optional<uint32_t> vlanId;
95     std::vector<std::string> nameServers;
96     std::vector<std::string> staticNameServers;
97     std::vector<std::string> domainnames;
98 };
99 
100 struct DHCPParameters
101 {
102     std::optional<bool> dhcpv4Enabled;
103     std::optional<bool> useDnsServers;
104     std::optional<bool> useNtpServers;
105     std::optional<bool> useDomainName;
106     std::optional<std::string> dhcpv6OperatingMode;
107 };
108 
109 // Helper function that changes bits netmask notation (i.e. /24)
110 // into full dot notation
111 inline std::string getNetmask(unsigned int bits)
112 {
113     uint32_t value = 0xffffffff << (32 - bits);
114     std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
115                           std::to_string((value >> 16) & 0xff) + "." +
116                           std::to_string((value >> 8) & 0xff) + "." +
117                           std::to_string(value & 0xff);
118     return netmask;
119 }
120 
121 inline bool translateDhcpEnabledToBool(const std::string& inputDHCP,
122                                        bool isIPv4)
123 {
124     if (isIPv4)
125     {
126         return (
127             (inputDHCP ==
128              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") ||
129             (inputDHCP ==
130              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"));
131     }
132     return ((inputDHCP ==
133              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") ||
134             (inputDHCP ==
135              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"));
136 }
137 
138 inline std::string getDhcpEnabledEnumeration(bool isIPv4, bool isIPv6)
139 {
140     if (isIPv4 && isIPv6)
141     {
142         return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both";
143     }
144     if (isIPv4)
145     {
146         return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4";
147     }
148     if (isIPv6)
149     {
150         return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6";
151     }
152     return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none";
153 }
154 
155 inline std::string
156     translateAddressOriginDbusToRedfish(const std::string& inputOrigin,
157                                         bool isIPv4)
158 {
159     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
160     {
161         return "Static";
162     }
163     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal")
164     {
165         if (isIPv4)
166         {
167             return "IPv4LinkLocal";
168         }
169         return "LinkLocal";
170     }
171     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP")
172     {
173         if (isIPv4)
174         {
175             return "DHCP";
176         }
177         return "DHCPv6";
178     }
179     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC")
180     {
181         return "SLAAC";
182     }
183     return "";
184 }
185 
186 inline bool
187     extractEthernetInterfaceData(const std::string& ethifaceId,
188                                  dbus::utility::ManagedObjectType& dbusData,
189                                  EthernetInterfaceData& ethData)
190 {
191     bool idFound = false;
192     for (auto& objpath : dbusData)
193     {
194         for (auto& ifacePair : objpath.second)
195         {
196             if (objpath.first == "/xyz/openbmc_project/network/" + ethifaceId)
197             {
198                 idFound = true;
199                 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress")
200                 {
201                     for (const auto& propertyPair : ifacePair.second)
202                     {
203                         if (propertyPair.first == "MACAddress")
204                         {
205                             const std::string* mac =
206                                 std::get_if<std::string>(&propertyPair.second);
207                             if (mac != nullptr)
208                             {
209                                 ethData.macAddress = *mac;
210                             }
211                         }
212                     }
213                 }
214                 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN")
215                 {
216                     for (const auto& propertyPair : ifacePair.second)
217                     {
218                         if (propertyPair.first == "Id")
219                         {
220                             const uint32_t* id =
221                                 std::get_if<uint32_t>(&propertyPair.second);
222                             if (id != nullptr)
223                             {
224                                 ethData.vlanId = *id;
225                             }
226                         }
227                     }
228                 }
229                 else if (ifacePair.first ==
230                          "xyz.openbmc_project.Network.EthernetInterface")
231                 {
232                     for (const auto& propertyPair : ifacePair.second)
233                     {
234                         if (propertyPair.first == "AutoNeg")
235                         {
236                             const bool* autoNeg =
237                                 std::get_if<bool>(&propertyPair.second);
238                             if (autoNeg != nullptr)
239                             {
240                                 ethData.autoNeg = *autoNeg;
241                             }
242                         }
243                         else if (propertyPair.first == "Speed")
244                         {
245                             const uint32_t* speed =
246                                 std::get_if<uint32_t>(&propertyPair.second);
247                             if (speed != nullptr)
248                             {
249                                 ethData.speed = *speed;
250                             }
251                         }
252                         else if (propertyPair.first == "MTU")
253                         {
254                             const uint32_t* mtuSize =
255                                 std::get_if<uint32_t>(&propertyPair.second);
256                             if (mtuSize != nullptr)
257                             {
258                                 ethData.mtuSize = *mtuSize;
259                             }
260                         }
261                         else if (propertyPair.first == "LinkUp")
262                         {
263                             const bool* linkUp =
264                                 std::get_if<bool>(&propertyPair.second);
265                             if (linkUp != nullptr)
266                             {
267                                 ethData.linkUp = *linkUp;
268                             }
269                         }
270                         else if (propertyPair.first == "NICEnabled")
271                         {
272                             const bool* nicEnabled =
273                                 std::get_if<bool>(&propertyPair.second);
274                             if (nicEnabled != nullptr)
275                             {
276                                 ethData.nicEnabled = *nicEnabled;
277                             }
278                         }
279                         else if (propertyPair.first == "Nameservers")
280                         {
281                             const std::vector<std::string>* nameservers =
282                                 std::get_if<std::vector<std::string>>(
283                                     &propertyPair.second);
284                             if (nameservers != nullptr)
285                             {
286                                 ethData.nameServers = *nameservers;
287                             }
288                         }
289                         else if (propertyPair.first == "StaticNameServers")
290                         {
291                             const std::vector<std::string>* staticNameServers =
292                                 std::get_if<std::vector<std::string>>(
293                                     &propertyPair.second);
294                             if (staticNameServers != nullptr)
295                             {
296                                 ethData.staticNameServers = *staticNameServers;
297                             }
298                         }
299                         else if (propertyPair.first == "DHCPEnabled")
300                         {
301                             const std::string* dhcpEnabled =
302                                 std::get_if<std::string>(&propertyPair.second);
303                             if (dhcpEnabled != nullptr)
304                             {
305                                 ethData.dhcpEnabled = *dhcpEnabled;
306                             }
307                         }
308                         else if (propertyPair.first == "DomainName")
309                         {
310                             const std::vector<std::string>* domainNames =
311                                 std::get_if<std::vector<std::string>>(
312                                     &propertyPair.second);
313                             if (domainNames != nullptr)
314                             {
315                                 ethData.domainnames = *domainNames;
316                             }
317                         }
318                         else if (propertyPair.first == "DefaultGateway")
319                         {
320                             const std::string* defaultGateway =
321                                 std::get_if<std::string>(&propertyPair.second);
322                             if (defaultGateway != nullptr)
323                             {
324                                 std::string defaultGatewayStr = *defaultGateway;
325                                 if (defaultGatewayStr.empty())
326                                 {
327                                     ethData.defaultGateway = "0.0.0.0";
328                                 }
329                                 else
330                                 {
331                                     ethData.defaultGateway = defaultGatewayStr;
332                                 }
333                             }
334                         }
335                         else if (propertyPair.first == "DefaultGateway6")
336                         {
337                             const std::string* defaultGateway6 =
338                                 std::get_if<std::string>(&propertyPair.second);
339                             if (defaultGateway6 != nullptr)
340                             {
341                                 std::string defaultGateway6Str =
342                                     *defaultGateway6;
343                                 if (defaultGateway6Str.empty())
344                                 {
345                                     ethData.ipv6DefaultGateway =
346                                         "0:0:0:0:0:0:0:0";
347                                 }
348                                 else
349                                 {
350                                     ethData.ipv6DefaultGateway =
351                                         defaultGateway6Str;
352                                 }
353                             }
354                         }
355                     }
356                 }
357             }
358 
359             if (objpath.first == "/xyz/openbmc_project/network/config/dhcp")
360             {
361                 if (ifacePair.first ==
362                     "xyz.openbmc_project.Network.DHCPConfiguration")
363                 {
364                     for (const auto& propertyPair : ifacePair.second)
365                     {
366                         if (propertyPair.first == "DNSEnabled")
367                         {
368                             const bool* dnsEnabled =
369                                 std::get_if<bool>(&propertyPair.second);
370                             if (dnsEnabled != nullptr)
371                             {
372                                 ethData.dnsEnabled = *dnsEnabled;
373                             }
374                         }
375                         else if (propertyPair.first == "NTPEnabled")
376                         {
377                             const bool* ntpEnabled =
378                                 std::get_if<bool>(&propertyPair.second);
379                             if (ntpEnabled != nullptr)
380                             {
381                                 ethData.ntpEnabled = *ntpEnabled;
382                             }
383                         }
384                         else if (propertyPair.first == "HostNameEnabled")
385                         {
386                             const bool* hostNameEnabled =
387                                 std::get_if<bool>(&propertyPair.second);
388                             if (hostNameEnabled != nullptr)
389                             {
390                                 ethData.hostNameEnabled = *hostNameEnabled;
391                             }
392                         }
393                     }
394                 }
395             }
396             // System configuration shows up in the global namespace, so no need
397             // to check eth number
398             if (ifacePair.first ==
399                 "xyz.openbmc_project.Network.SystemConfiguration")
400             {
401                 for (const auto& propertyPair : ifacePair.second)
402                 {
403                     if (propertyPair.first == "HostName")
404                     {
405                         const std::string* hostname =
406                             std::get_if<std::string>(&propertyPair.second);
407                         if (hostname != nullptr)
408                         {
409                             ethData.hostName = *hostname;
410                         }
411                     }
412                 }
413             }
414         }
415     }
416     return idFound;
417 }
418 
419 // Helper function that extracts data for single ethernet ipv6 address
420 inline void
421     extractIPV6Data(const std::string& ethifaceId,
422                     const dbus::utility::ManagedObjectType& dbusData,
423                     boost::container::flat_set<IPv6AddressData>& ipv6Config)
424 {
425     const std::string ipv6PathStart =
426         "/xyz/openbmc_project/network/" + ethifaceId + "/ipv6/";
427 
428     // Since there might be several IPv6 configurations aligned with
429     // single ethernet interface, loop over all of them
430     for (const auto& objpath : dbusData)
431     {
432         // Check if proper pattern for object path appears
433         if (boost::starts_with(objpath.first.str, ipv6PathStart))
434         {
435             for (const auto& interface : objpath.second)
436             {
437                 if (interface.first == "xyz.openbmc_project.Network.IP")
438                 {
439                     // Instance IPv6AddressData structure, and set as
440                     // appropriate
441                     std::pair<
442                         boost::container::flat_set<IPv6AddressData>::iterator,
443                         bool>
444                         it = ipv6Config.insert(IPv6AddressData{});
445                     IPv6AddressData& ipv6Address = *it.first;
446                     ipv6Address.id =
447                         objpath.first.str.substr(ipv6PathStart.size());
448                     for (const auto& property : interface.second)
449                     {
450                         if (property.first == "Address")
451                         {
452                             const std::string* address =
453                                 std::get_if<std::string>(&property.second);
454                             if (address != nullptr)
455                             {
456                                 ipv6Address.address = *address;
457                             }
458                         }
459                         else if (property.first == "Origin")
460                         {
461                             const std::string* origin =
462                                 std::get_if<std::string>(&property.second);
463                             if (origin != nullptr)
464                             {
465                                 ipv6Address.origin =
466                                     translateAddressOriginDbusToRedfish(*origin,
467                                                                         false);
468                             }
469                         }
470                         else if (property.first == "PrefixLength")
471                         {
472                             const uint8_t* prefix =
473                                 std::get_if<uint8_t>(&property.second);
474                             if (prefix != nullptr)
475                             {
476                                 ipv6Address.prefixLength = *prefix;
477                             }
478                         }
479                         else if (property.first == "Type" ||
480                                  property.first == "Gateway")
481                         {
482                             // Type & Gateway is not used
483                         }
484                         else
485                         {
486                             BMCWEB_LOG_ERROR
487                                 << "Got extra property: " << property.first
488                                 << " on the " << objpath.first.str << " object";
489                         }
490                     }
491                 }
492             }
493         }
494     }
495 }
496 
497 // Helper function that extracts data for single ethernet ipv4 address
498 inline void
499     extractIPData(const std::string& ethifaceId,
500                   const dbus::utility::ManagedObjectType& dbusData,
501                   boost::container::flat_set<IPv4AddressData>& ipv4Config)
502 {
503     const std::string ipv4PathStart =
504         "/xyz/openbmc_project/network/" + ethifaceId + "/ipv4/";
505 
506     // Since there might be several IPv4 configurations aligned with
507     // single ethernet interface, loop over all of them
508     for (const auto& objpath : dbusData)
509     {
510         // Check if proper pattern for object path appears
511         if (boost::starts_with(objpath.first.str, ipv4PathStart))
512         {
513             for (const auto& interface : objpath.second)
514             {
515                 if (interface.first == "xyz.openbmc_project.Network.IP")
516                 {
517                     // Instance IPv4AddressData structure, and set as
518                     // appropriate
519                     std::pair<
520                         boost::container::flat_set<IPv4AddressData>::iterator,
521                         bool>
522                         it = ipv4Config.insert(IPv4AddressData{});
523                     IPv4AddressData& ipv4Address = *it.first;
524                     ipv4Address.id =
525                         objpath.first.str.substr(ipv4PathStart.size());
526                     for (const auto& property : interface.second)
527                     {
528                         if (property.first == "Address")
529                         {
530                             const std::string* address =
531                                 std::get_if<std::string>(&property.second);
532                             if (address != nullptr)
533                             {
534                                 ipv4Address.address = *address;
535                             }
536                         }
537                         else if (property.first == "Origin")
538                         {
539                             const std::string* origin =
540                                 std::get_if<std::string>(&property.second);
541                             if (origin != nullptr)
542                             {
543                                 ipv4Address.origin =
544                                     translateAddressOriginDbusToRedfish(*origin,
545                                                                         true);
546                             }
547                         }
548                         else if (property.first == "PrefixLength")
549                         {
550                             const uint8_t* mask =
551                                 std::get_if<uint8_t>(&property.second);
552                             if (mask != nullptr)
553                             {
554                                 // convert it to the string
555                                 ipv4Address.netmask = getNetmask(*mask);
556                             }
557                         }
558                         else if (property.first == "Type" ||
559                                  property.first == "Gateway")
560                         {
561                             // Type & Gateway is not used
562                         }
563                         else
564                         {
565                             BMCWEB_LOG_ERROR
566                                 << "Got extra property: " << property.first
567                                 << " on the " << objpath.first.str << " object";
568                         }
569                     }
570                     // Check if given address is local, or global
571                     ipv4Address.linktype =
572                         boost::starts_with(ipv4Address.address, "169.254.")
573                             ? LinkType::Local
574                             : LinkType::Global;
575                 }
576             }
577         }
578     }
579 }
580 
581 /**
582  * @brief Helper function that verifies IP address to check if it is in
583  *        proper format. If bits pointer is provided, also calculates active
584  *        bit count for Subnet Mask.
585  *
586  * @param[in]  ip     IP that will be verified
587  * @param[out] bits   Calculated mask in bits notation
588  *
589  * @return true in case of success, false otherwise
590  */
591 inline bool ipv4VerifyIpAndGetBitcount(const std::string& ip,
592                                        uint8_t* bits = nullptr)
593 {
594     std::vector<std::string> bytesInMask;
595 
596     boost::split(bytesInMask, ip, boost::is_any_of("."));
597 
598     static const constexpr int ipV4AddressSectionsCount = 4;
599     if (bytesInMask.size() != ipV4AddressSectionsCount)
600     {
601         return false;
602     }
603 
604     if (bits != nullptr)
605     {
606         *bits = 0;
607     }
608 
609     char* endPtr = nullptr;
610     long previousValue = 255;
611     bool firstZeroInByteHit = false;
612     for (const std::string& byte : bytesInMask)
613     {
614         if (byte.empty())
615         {
616             return false;
617         }
618 
619         // Use strtol instead of stroi to avoid exceptions
620         long value = std::strtol(byte.c_str(), &endPtr, 10);
621 
622         // endPtr should point to the end of the string, otherwise given string
623         // is not 100% number
624         if (*endPtr != '\0')
625         {
626             return false;
627         }
628 
629         // Value should be contained in byte
630         if (value < 0 || value > 255)
631         {
632             return false;
633         }
634 
635         if (bits != nullptr)
636         {
637             // Mask has to be continuous between bytes
638             if (previousValue != 255 && value != 0)
639             {
640                 return false;
641             }
642 
643             // Mask has to be continuous inside bytes
644             firstZeroInByteHit = false;
645 
646             // Count bits
647             for (long bitIdx = 7; bitIdx >= 0; bitIdx--)
648             {
649                 if ((value & (1L << bitIdx)) != 0)
650                 {
651                     if (firstZeroInByteHit)
652                     {
653                         // Continuity not preserved
654                         return false;
655                     }
656                     (*bits)++;
657                 }
658                 else
659                 {
660                     firstZeroInByteHit = true;
661                 }
662             }
663         }
664 
665         previousValue = value;
666     }
667 
668     return true;
669 }
670 
671 /**
672  * @brief Deletes given IPv4 interface
673  *
674  * @param[in] ifaceId     Id of interface whose IP should be deleted
675  * @param[in] ipHash      DBus Hash id of IP that should be deleted
676  * @param[io] asyncResp   Response object that will be returned to client
677  *
678  * @return None
679  */
680 inline void deleteIPv4(const std::string& ifaceId, const std::string& ipHash,
681                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
682 {
683     crow::connections::systemBus->async_method_call(
684         [asyncResp](const boost::system::error_code ec) {
685             if (ec)
686             {
687                 messages::internalError(asyncResp->res);
688             }
689         },
690         "xyz.openbmc_project.Network",
691         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
692         "xyz.openbmc_project.Object.Delete", "Delete");
693 }
694 
695 inline void updateIPv4DefaultGateway(
696     const std::string& ifaceId, const std::string& gateway,
697     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
698 {
699     crow::connections::systemBus->async_method_call(
700         [asyncResp](const boost::system::error_code ec) {
701             if (ec)
702             {
703                 messages::internalError(asyncResp->res);
704                 return;
705             }
706             asyncResp->res.result(boost::beast::http::status::no_content);
707         },
708         "xyz.openbmc_project.Network",
709         "/xyz/openbmc_project/network/" + ifaceId,
710         "org.freedesktop.DBus.Properties", "Set",
711         "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway",
712         dbus::utility::DbusVariantType(gateway));
713 }
714 /**
715  * @brief Creates a static IPv4 entry
716  *
717  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
718  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
719  * @param[in] gateway      IPv4 address of this interfaces gateway
720  * @param[in] address      IPv4 address to assign to this interface
721  * @param[io] asyncResp    Response object that will be returned to client
722  *
723  * @return None
724  */
725 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength,
726                        const std::string& gateway, const std::string& address,
727                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
728 {
729     auto createIpHandler = [asyncResp, ifaceId,
730                             gateway](const boost::system::error_code ec) {
731         if (ec)
732         {
733             messages::internalError(asyncResp->res);
734             return;
735         }
736         updateIPv4DefaultGateway(ifaceId, gateway, asyncResp);
737     };
738 
739     crow::connections::systemBus->async_method_call(
740         std::move(createIpHandler), "xyz.openbmc_project.Network",
741         "/xyz/openbmc_project/network/" + ifaceId,
742         "xyz.openbmc_project.Network.IP.Create", "IP",
743         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength,
744         gateway);
745 }
746 
747 /**
748  * @brief Deletes the IPv4 entry for this interface and creates a replacement
749  * static IPv4 entry
750  *
751  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
752  * @param[in] id           The unique hash entry identifying the DBus entry
753  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
754  * @param[in] gateway      IPv4 address of this interfaces gateway
755  * @param[in] address      IPv4 address to assign to this interface
756  * @param[io] asyncResp    Response object that will be returned to client
757  *
758  * @return None
759  */
760 inline void
761     deleteAndCreateIPv4(const std::string& ifaceId, const std::string& id,
762                         uint8_t prefixLength, const std::string& gateway,
763                         const std::string& address,
764                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
765 {
766     crow::connections::systemBus->async_method_call(
767         [asyncResp, ifaceId, address, prefixLength,
768          gateway](const boost::system::error_code ec) {
769             if (ec)
770             {
771                 messages::internalError(asyncResp->res);
772                 return;
773             }
774 
775             crow::connections::systemBus->async_method_call(
776                 [asyncResp, ifaceId,
777                  gateway](const boost::system::error_code ec2) {
778                     if (ec2)
779                     {
780                         messages::internalError(asyncResp->res);
781                         return;
782                     }
783                     updateIPv4DefaultGateway(ifaceId, gateway, asyncResp);
784                 },
785                 "xyz.openbmc_project.Network",
786                 "/xyz/openbmc_project/network/" + ifaceId,
787                 "xyz.openbmc_project.Network.IP.Create", "IP",
788                 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address,
789                 prefixLength, gateway);
790         },
791         "xyz.openbmc_project.Network",
792         +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id,
793         "xyz.openbmc_project.Object.Delete", "Delete");
794 }
795 
796 /**
797  * @brief Deletes given IPv6
798  *
799  * @param[in] ifaceId     Id of interface whose IP should be deleted
800  * @param[in] ipHash      DBus Hash id of IP that should be deleted
801  * @param[io] asyncResp   Response object that will be returned to client
802  *
803  * @return None
804  */
805 inline void deleteIPv6(const std::string& ifaceId, const std::string& ipHash,
806                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
807 {
808     crow::connections::systemBus->async_method_call(
809         [asyncResp](const boost::system::error_code ec) {
810             if (ec)
811             {
812                 messages::internalError(asyncResp->res);
813             }
814         },
815         "xyz.openbmc_project.Network",
816         "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash,
817         "xyz.openbmc_project.Object.Delete", "Delete");
818 }
819 
820 /**
821  * @brief Deletes the IPv6 entry for this interface and creates a replacement
822  * static IPv6 entry
823  *
824  * @param[in] ifaceId      Id of interface upon which to create the IPv6 entry
825  * @param[in] id           The unique hash entry identifying the DBus entry
826  * @param[in] prefixLength IPv6 prefix syntax for the subnet mask
827  * @param[in] address      IPv6 address to assign to this interface
828  * @param[io] asyncResp    Response object that will be returned to client
829  *
830  * @return None
831  */
832 inline void
833     deleteAndCreateIPv6(const std::string& ifaceId, const std::string& id,
834                         uint8_t prefixLength, const std::string& address,
835                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
836 {
837     crow::connections::systemBus->async_method_call(
838         [asyncResp, ifaceId, address,
839          prefixLength](const boost::system::error_code ec) {
840             if (ec)
841             {
842                 messages::internalError(asyncResp->res);
843             }
844             crow::connections::systemBus->async_method_call(
845                 [asyncResp](const boost::system::error_code ec2) {
846                     if (ec2)
847                     {
848                         messages::internalError(asyncResp->res);
849                     }
850                 },
851                 "xyz.openbmc_project.Network",
852                 "/xyz/openbmc_project/network/" + ifaceId,
853                 "xyz.openbmc_project.Network.IP.Create", "IP",
854                 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address,
855                 prefixLength, "");
856         },
857         "xyz.openbmc_project.Network",
858         +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id,
859         "xyz.openbmc_project.Object.Delete", "Delete");
860 }
861 
862 /**
863  * @brief Creates IPv6 with given data
864  *
865  * @param[in] ifaceId      Id of interface whose IP should be added
866  * @param[in] prefixLength Prefix length that needs to be added
867  * @param[in] address      IP address that needs to be added
868  * @param[io] asyncResp    Response object that will be returned to client
869  *
870  * @return None
871  */
872 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength,
873                        const std::string& address,
874                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
875 {
876     auto createIpHandler = [asyncResp](const boost::system::error_code ec) {
877         if (ec)
878         {
879             messages::internalError(asyncResp->res);
880         }
881     };
882     // Passing null for gateway, as per redfish spec IPv6StaticAddresses object
883     // does not have associated gateway property
884     crow::connections::systemBus->async_method_call(
885         std::move(createIpHandler), "xyz.openbmc_project.Network",
886         "/xyz/openbmc_project/network/" + ifaceId,
887         "xyz.openbmc_project.Network.IP.Create", "IP",
888         "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength,
889         "");
890 }
891 
892 /**
893  * Function that retrieves all properties for given Ethernet Interface
894  * Object
895  * from EntityManager Network Manager
896  * @param ethiface_id a eth interface id to query on DBus
897  * @param callback a function that shall be called to convert Dbus output
898  * into JSON
899  */
900 template <typename CallbackFunc>
901 void getEthernetIfaceData(const std::string& ethifaceId,
902                           CallbackFunc&& callback)
903 {
904     crow::connections::systemBus->async_method_call(
905         [ethifaceId{std::string{ethifaceId}},
906          callback{std::forward<CallbackFunc>(callback)}](
907             const boost::system::error_code errorCode,
908             dbus::utility::ManagedObjectType& resp) {
909             EthernetInterfaceData ethData{};
910             boost::container::flat_set<IPv4AddressData> ipv4Data;
911             boost::container::flat_set<IPv6AddressData> ipv6Data;
912 
913             if (errorCode)
914             {
915                 callback(false, ethData, ipv4Data, ipv6Data);
916                 return;
917             }
918 
919             bool found =
920                 extractEthernetInterfaceData(ethifaceId, resp, ethData);
921             if (!found)
922             {
923                 callback(false, ethData, ipv4Data, ipv6Data);
924                 return;
925             }
926 
927             extractIPData(ethifaceId, resp, ipv4Data);
928             // Fix global GW
929             for (IPv4AddressData& ipv4 : ipv4Data)
930             {
931                 if (((ipv4.linktype == LinkType::Global) &&
932                      (ipv4.gateway == "0.0.0.0")) ||
933                     (ipv4.origin == "DHCP") || (ipv4.origin == "Static"))
934                 {
935                     ipv4.gateway = ethData.defaultGateway;
936                 }
937             }
938 
939             extractIPV6Data(ethifaceId, resp, ipv6Data);
940             // Finally make a callback with useful data
941             callback(true, ethData, ipv4Data, ipv6Data);
942         },
943         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
944         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
945 }
946 
947 /**
948  * Function that retrieves all Ethernet Interfaces available through Network
949  * Manager
950  * @param callback a function that shall be called to convert Dbus output
951  * into JSON.
952  */
953 template <typename CallbackFunc>
954 void getEthernetIfaceList(CallbackFunc&& callback)
955 {
956     crow::connections::systemBus->async_method_call(
957         [callback{std::forward<CallbackFunc>(callback)}](
958             const boost::system::error_code errorCode,
959             dbus::utility::ManagedObjectType& resp) {
960             // Callback requires vector<string> to retrieve all available
961             // ethernet interfaces
962             boost::container::flat_set<std::string> ifaceList;
963             ifaceList.reserve(resp.size());
964             if (errorCode)
965             {
966                 callback(false, ifaceList);
967                 return;
968             }
969 
970             // Iterate over all retrieved ObjectPaths.
971             for (const auto& objpath : resp)
972             {
973                 // And all interfaces available for certain ObjectPath.
974                 for (const auto& interface : objpath.second)
975                 {
976                     // If interface is
977                     // xyz.openbmc_project.Network.EthernetInterface, this is
978                     // what we're looking for.
979                     if (interface.first ==
980                         "xyz.openbmc_project.Network.EthernetInterface")
981                     {
982                         std::string ifaceId = objpath.first.filename();
983                         if (ifaceId.empty())
984                         {
985                             continue;
986                         }
987                         // and put it into output vector.
988                         ifaceList.emplace(ifaceId);
989                     }
990                 }
991             }
992             // Finally make a callback with useful data
993             callback(true, ifaceList);
994         },
995         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
996         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
997 }
998 
999 inline void
1000     handleHostnamePatch(const std::string& hostname,
1001                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1002 {
1003     // SHOULD handle host names of up to 255 characters(RFC 1123)
1004     if (hostname.length() > 255)
1005     {
1006         messages::propertyValueFormatError(asyncResp->res, hostname,
1007                                            "HostName");
1008         return;
1009     }
1010     crow::connections::systemBus->async_method_call(
1011         [asyncResp](const boost::system::error_code ec) {
1012             if (ec)
1013             {
1014                 messages::internalError(asyncResp->res);
1015             }
1016         },
1017         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config",
1018         "org.freedesktop.DBus.Properties", "Set",
1019         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
1020         dbus::utility::DbusVariantType(hostname));
1021 }
1022 
1023 inline void
1024     handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize,
1025                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1026 {
1027     sdbusplus::message::object_path objPath =
1028         "/xyz/openbmc_project/network/" + ifaceId;
1029     crow::connections::systemBus->async_method_call(
1030         [asyncResp](const boost::system::error_code ec) {
1031             if (ec)
1032             {
1033                 messages::internalError(asyncResp->res);
1034             }
1035         },
1036         "xyz.openbmc_project.Network", objPath,
1037         "org.freedesktop.DBus.Properties", "Set",
1038         "xyz.openbmc_project.Network.EthernetInterface", "MTU",
1039         std::variant<size_t>(mtuSize));
1040 }
1041 
1042 inline void
1043     handleDomainnamePatch(const std::string& ifaceId,
1044                           const std::string& domainname,
1045                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1046 {
1047     std::vector<std::string> vectorDomainname = {domainname};
1048     crow::connections::systemBus->async_method_call(
1049         [asyncResp](const boost::system::error_code ec) {
1050             if (ec)
1051             {
1052                 messages::internalError(asyncResp->res);
1053             }
1054         },
1055         "xyz.openbmc_project.Network",
1056         "/xyz/openbmc_project/network/" + ifaceId,
1057         "org.freedesktop.DBus.Properties", "Set",
1058         "xyz.openbmc_project.Network.EthernetInterface", "DomainName",
1059         dbus::utility::DbusVariantType(vectorDomainname));
1060 }
1061 
1062 inline bool isHostnameValid(const std::string& hostname)
1063 {
1064     // A valid host name can never have the dotted-decimal form (RFC 1123)
1065     if (std::all_of(hostname.begin(), hostname.end(), ::isdigit))
1066     {
1067         return false;
1068     }
1069     // Each label(hostname/subdomains) within a valid FQDN
1070     // MUST handle host names of up to 63 characters (RFC 1123)
1071     // labels cannot start or end with hyphens (RFC 952)
1072     // labels can start with numbers (RFC 1123)
1073     const std::regex pattern(
1074         "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
1075 
1076     return std::regex_match(hostname, pattern);
1077 }
1078 
1079 inline bool isDomainnameValid(const std::string& domainname)
1080 {
1081     // Can have multiple subdomains
1082     // Top Level Domain's min length is 2 character
1083     const std::regex pattern(
1084         "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$");
1085 
1086     return std::regex_match(domainname, pattern);
1087 }
1088 
1089 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn,
1090                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1091 {
1092     // Total length of FQDN must not exceed 255 characters(RFC 1035)
1093     if (fqdn.length() > 255)
1094     {
1095         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1096         return;
1097     }
1098 
1099     size_t pos = fqdn.find('.');
1100     if (pos == std::string::npos)
1101     {
1102         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1103         return;
1104     }
1105 
1106     std::string hostname;
1107     std::string domainname;
1108     domainname = (fqdn).substr(pos + 1);
1109     hostname = (fqdn).substr(0, pos);
1110 
1111     if (!isHostnameValid(hostname) || !isDomainnameValid(domainname))
1112     {
1113         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1114         return;
1115     }
1116 
1117     handleHostnamePatch(hostname, asyncResp);
1118     handleDomainnamePatch(ifaceId, domainname, asyncResp);
1119 }
1120 
1121 inline void
1122     handleMACAddressPatch(const std::string& ifaceId,
1123                           const std::string& macAddress,
1124                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1125 {
1126     crow::connections::systemBus->async_method_call(
1127         [asyncResp, macAddress](const boost::system::error_code ec) {
1128             if (ec)
1129             {
1130                 messages::internalError(asyncResp->res);
1131                 return;
1132             }
1133         },
1134         "xyz.openbmc_project.Network",
1135         "/xyz/openbmc_project/network/" + ifaceId,
1136         "org.freedesktop.DBus.Properties", "Set",
1137         "xyz.openbmc_project.Network.MACAddress", "MACAddress",
1138         dbus::utility::DbusVariantType(macAddress));
1139 }
1140 
1141 inline void setDHCPEnabled(const std::string& ifaceId,
1142                            const std::string& propertyName, const bool v4Value,
1143                            const bool v6Value,
1144                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1145 {
1146     const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value);
1147     crow::connections::systemBus->async_method_call(
1148         [asyncResp](const boost::system::error_code ec) {
1149             if (ec)
1150             {
1151                 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1152                 messages::internalError(asyncResp->res);
1153                 return;
1154             }
1155             messages::success(asyncResp->res);
1156         },
1157         "xyz.openbmc_project.Network",
1158         "/xyz/openbmc_project/network/" + ifaceId,
1159         "org.freedesktop.DBus.Properties", "Set",
1160         "xyz.openbmc_project.Network.EthernetInterface", propertyName,
1161         dbus::utility::DbusVariantType{dhcp});
1162 }
1163 
1164 inline void setEthernetInterfaceBoolProperty(
1165     const std::string& ifaceId, const std::string& propertyName,
1166     const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1167 {
1168     crow::connections::systemBus->async_method_call(
1169         [asyncResp](const boost::system::error_code ec) {
1170             if (ec)
1171             {
1172                 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1173                 messages::internalError(asyncResp->res);
1174                 return;
1175             }
1176         },
1177         "xyz.openbmc_project.Network",
1178         "/xyz/openbmc_project/network/" + ifaceId,
1179         "org.freedesktop.DBus.Properties", "Set",
1180         "xyz.openbmc_project.Network.EthernetInterface", propertyName,
1181         dbus::utility::DbusVariantType{value});
1182 }
1183 
1184 inline void setDHCPv4Config(const std::string& propertyName, const bool& value,
1185                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1186 {
1187     BMCWEB_LOG_DEBUG << propertyName << " = " << value;
1188     crow::connections::systemBus->async_method_call(
1189         [asyncResp](const boost::system::error_code ec) {
1190             if (ec)
1191             {
1192                 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1193                 messages::internalError(asyncResp->res);
1194                 return;
1195             }
1196         },
1197         "xyz.openbmc_project.Network",
1198         "/xyz/openbmc_project/network/config/dhcp",
1199         "org.freedesktop.DBus.Properties", "Set",
1200         "xyz.openbmc_project.Network.DHCPConfiguration", propertyName,
1201         dbus::utility::DbusVariantType{value});
1202 }
1203 
1204 inline void handleDHCPPatch(const std::string& ifaceId,
1205                             const EthernetInterfaceData& ethData,
1206                             const DHCPParameters& v4dhcpParms,
1207                             const DHCPParameters& v6dhcpParms,
1208                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1209 {
1210     bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1211     bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false);
1212 
1213     bool nextv4DHCPState =
1214         v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active;
1215 
1216     bool nextv6DHCPState{};
1217     if (v6dhcpParms.dhcpv6OperatingMode)
1218     {
1219         if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") &&
1220             (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") &&
1221             (*v6dhcpParms.dhcpv6OperatingMode != "Disabled"))
1222         {
1223             messages::propertyValueFormatError(asyncResp->res,
1224                                                *v6dhcpParms.dhcpv6OperatingMode,
1225                                                "OperatingMode");
1226             return;
1227         }
1228         nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful");
1229     }
1230     else
1231     {
1232         nextv6DHCPState = ipv6Active;
1233     }
1234 
1235     bool nextDNS{};
1236     if (v4dhcpParms.useDnsServers && v6dhcpParms.useDnsServers)
1237     {
1238         if (*v4dhcpParms.useDnsServers != *v6dhcpParms.useDnsServers)
1239         {
1240             messages::generalError(asyncResp->res);
1241             return;
1242         }
1243         nextDNS = *v4dhcpParms.useDnsServers;
1244     }
1245     else if (v4dhcpParms.useDnsServers)
1246     {
1247         nextDNS = *v4dhcpParms.useDnsServers;
1248     }
1249     else if (v6dhcpParms.useDnsServers)
1250     {
1251         nextDNS = *v6dhcpParms.useDnsServers;
1252     }
1253     else
1254     {
1255         nextDNS = ethData.dnsEnabled;
1256     }
1257 
1258     bool nextNTP{};
1259     if (v4dhcpParms.useNtpServers && v6dhcpParms.useNtpServers)
1260     {
1261         if (*v4dhcpParms.useNtpServers != *v6dhcpParms.useNtpServers)
1262         {
1263             messages::generalError(asyncResp->res);
1264             return;
1265         }
1266         nextNTP = *v4dhcpParms.useNtpServers;
1267     }
1268     else if (v4dhcpParms.useNtpServers)
1269     {
1270         nextNTP = *v4dhcpParms.useNtpServers;
1271     }
1272     else if (v6dhcpParms.useNtpServers)
1273     {
1274         nextNTP = *v6dhcpParms.useNtpServers;
1275     }
1276     else
1277     {
1278         nextNTP = ethData.ntpEnabled;
1279     }
1280 
1281     bool nextUseDomain{};
1282     if (v4dhcpParms.useDomainName && v6dhcpParms.useDomainName)
1283     {
1284         if (*v4dhcpParms.useDomainName != *v6dhcpParms.useDomainName)
1285         {
1286             messages::generalError(asyncResp->res);
1287             return;
1288         }
1289         nextUseDomain = *v4dhcpParms.useDomainName;
1290     }
1291     else if (v4dhcpParms.useDomainName)
1292     {
1293         nextUseDomain = *v4dhcpParms.useDomainName;
1294     }
1295     else if (v6dhcpParms.useDomainName)
1296     {
1297         nextUseDomain = *v6dhcpParms.useDomainName;
1298     }
1299     else
1300     {
1301         nextUseDomain = ethData.hostNameEnabled;
1302     }
1303 
1304     BMCWEB_LOG_DEBUG << "set DHCPEnabled...";
1305     setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState,
1306                    asyncResp);
1307     BMCWEB_LOG_DEBUG << "set DNSEnabled...";
1308     setDHCPv4Config("DNSEnabled", nextDNS, asyncResp);
1309     BMCWEB_LOG_DEBUG << "set NTPEnabled...";
1310     setDHCPv4Config("NTPEnabled", nextNTP, asyncResp);
1311     BMCWEB_LOG_DEBUG << "set HostNameEnabled...";
1312     setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp);
1313 }
1314 
1315 inline boost::container::flat_set<IPv4AddressData>::const_iterator
1316     getNextStaticIpEntry(
1317         const boost::container::flat_set<IPv4AddressData>::const_iterator& head,
1318         const boost::container::flat_set<IPv4AddressData>::const_iterator& end)
1319 {
1320     return std::find_if(head, end, [](const IPv4AddressData& value) {
1321         return value.origin == "Static";
1322     });
1323 }
1324 
1325 inline boost::container::flat_set<IPv6AddressData>::const_iterator
1326     getNextStaticIpEntry(
1327         const boost::container::flat_set<IPv6AddressData>::const_iterator& head,
1328         const boost::container::flat_set<IPv6AddressData>::const_iterator& end)
1329 {
1330     return std::find_if(head, end, [](const IPv6AddressData& value) {
1331         return value.origin == "Static";
1332     });
1333 }
1334 
1335 inline void handleIPv4StaticPatch(
1336     const std::string& ifaceId, nlohmann::json& input,
1337     const boost::container::flat_set<IPv4AddressData>& ipv4Data,
1338     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1339 {
1340     if ((!input.is_array()) || input.empty())
1341     {
1342         messages::propertyValueTypeError(
1343             asyncResp->res,
1344             input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace),
1345             "IPv4StaticAddresses");
1346         return;
1347     }
1348 
1349     unsigned entryIdx = 1;
1350     // Find the first static IP address currently active on the NIC and
1351     // match it to the first JSON element in the IPv4StaticAddresses array.
1352     // Match each subsequent JSON element to the next static IP programmed
1353     // into the NIC.
1354     boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry =
1355         getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend());
1356 
1357     for (nlohmann::json& thisJson : input)
1358     {
1359         std::string pathString =
1360             "IPv4StaticAddresses/" + std::to_string(entryIdx);
1361 
1362         if (!thisJson.is_null() && !thisJson.empty())
1363         {
1364             std::optional<std::string> address;
1365             std::optional<std::string> subnetMask;
1366             std::optional<std::string> gateway;
1367 
1368             if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1369                                      address, "SubnetMask", subnetMask,
1370                                      "Gateway", gateway))
1371             {
1372                 messages::propertyValueFormatError(
1373                     asyncResp->res,
1374                     thisJson.dump(2, ' ', true,
1375                                   nlohmann::json::error_handler_t::replace),
1376                     pathString);
1377                 return;
1378             }
1379 
1380             // Find the address/subnet/gateway values. Any values that are
1381             // not explicitly provided are assumed to be unmodified from the
1382             // current state of the interface. Merge existing state into the
1383             // current request.
1384             const std::string* addr = nullptr;
1385             const std::string* gw = nullptr;
1386             uint8_t prefixLength = 0;
1387             bool errorInEntry = false;
1388             if (address)
1389             {
1390                 if (ipv4VerifyIpAndGetBitcount(*address))
1391                 {
1392                     addr = &(*address);
1393                 }
1394                 else
1395                 {
1396                     messages::propertyValueFormatError(asyncResp->res, *address,
1397                                                        pathString + "/Address");
1398                     errorInEntry = true;
1399                 }
1400             }
1401             else if (nicIpEntry != ipv4Data.cend())
1402             {
1403                 addr = &(nicIpEntry->address);
1404             }
1405             else
1406             {
1407                 messages::propertyMissing(asyncResp->res,
1408                                           pathString + "/Address");
1409                 errorInEntry = true;
1410             }
1411 
1412             if (subnetMask)
1413             {
1414                 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength))
1415                 {
1416                     messages::propertyValueFormatError(
1417                         asyncResp->res, *subnetMask,
1418                         pathString + "/SubnetMask");
1419                     errorInEntry = true;
1420                 }
1421             }
1422             else if (nicIpEntry != ipv4Data.cend())
1423             {
1424                 if (!ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask,
1425                                                 &prefixLength))
1426                 {
1427                     messages::propertyValueFormatError(
1428                         asyncResp->res, nicIpEntry->netmask,
1429                         pathString + "/SubnetMask");
1430                     errorInEntry = true;
1431                 }
1432             }
1433             else
1434             {
1435                 messages::propertyMissing(asyncResp->res,
1436                                           pathString + "/SubnetMask");
1437                 errorInEntry = true;
1438             }
1439 
1440             if (gateway)
1441             {
1442                 if (ipv4VerifyIpAndGetBitcount(*gateway))
1443                 {
1444                     gw = &(*gateway);
1445                 }
1446                 else
1447                 {
1448                     messages::propertyValueFormatError(asyncResp->res, *gateway,
1449                                                        pathString + "/Gateway");
1450                     errorInEntry = true;
1451                 }
1452             }
1453             else if (nicIpEntry != ipv4Data.cend())
1454             {
1455                 gw = &nicIpEntry->gateway;
1456             }
1457             else
1458             {
1459                 messages::propertyMissing(asyncResp->res,
1460                                           pathString + "/Gateway");
1461                 errorInEntry = true;
1462             }
1463 
1464             if (errorInEntry)
1465             {
1466                 return;
1467             }
1468 
1469             if (nicIpEntry != ipv4Data.cend())
1470             {
1471                 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw,
1472                                     *addr, asyncResp);
1473                 nicIpEntry =
1474                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1475             }
1476             else
1477             {
1478                 createIPv4(ifaceId, prefixLength, *gateway, *address,
1479                            asyncResp);
1480             }
1481             entryIdx++;
1482         }
1483         else
1484         {
1485             if (nicIpEntry == ipv4Data.cend())
1486             {
1487                 // Requesting a DELETE/DO NOT MODIFY action for an item
1488                 // that isn't present on the eth(n) interface. Input JSON is
1489                 // in error, so bail out.
1490                 if (thisJson.is_null())
1491                 {
1492                     messages::resourceCannotBeDeleted(asyncResp->res);
1493                     return;
1494                 }
1495                 messages::propertyValueFormatError(
1496                     asyncResp->res,
1497                     thisJson.dump(2, ' ', true,
1498                                   nlohmann::json::error_handler_t::replace),
1499                     pathString);
1500                 return;
1501             }
1502 
1503             if (thisJson.is_null())
1504             {
1505                 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp);
1506             }
1507             if (nicIpEntry != ipv4Data.cend())
1508             {
1509                 nicIpEntry =
1510                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1511             }
1512             entryIdx++;
1513         }
1514     }
1515 }
1516 
1517 inline void handleStaticNameServersPatch(
1518     const std::string& ifaceId,
1519     const std::vector<std::string>& updatedStaticNameServers,
1520     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1521 {
1522     crow::connections::systemBus->async_method_call(
1523         [asyncResp](const boost::system::error_code ec) {
1524             if (ec)
1525             {
1526                 messages::internalError(asyncResp->res);
1527                 return;
1528             }
1529         },
1530         "xyz.openbmc_project.Network",
1531         "/xyz/openbmc_project/network/" + ifaceId,
1532         "org.freedesktop.DBus.Properties", "Set",
1533         "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers",
1534         dbus::utility::DbusVariantType{updatedStaticNameServers});
1535 }
1536 
1537 inline void handleIPv6StaticAddressesPatch(
1538     const std::string& ifaceId, const nlohmann::json& input,
1539     const boost::container::flat_set<IPv6AddressData>& ipv6Data,
1540     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1541 {
1542     if (!input.is_array() || input.empty())
1543     {
1544         messages::propertyValueTypeError(
1545             asyncResp->res,
1546             input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace),
1547             "IPv6StaticAddresses");
1548         return;
1549     }
1550     size_t entryIdx = 1;
1551     boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry =
1552         getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend());
1553     for (const nlohmann::json& thisJson : input)
1554     {
1555         std::string pathString =
1556             "IPv6StaticAddresses/" + std::to_string(entryIdx);
1557 
1558         if (!thisJson.is_null() && !thisJson.empty())
1559         {
1560             std::optional<std::string> address;
1561             std::optional<uint8_t> prefixLength;
1562             nlohmann::json thisJsonCopy = thisJson;
1563             if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address",
1564                                      address, "PrefixLength", prefixLength))
1565             {
1566                 messages::propertyValueFormatError(
1567                     asyncResp->res,
1568                     thisJson.dump(2, ' ', true,
1569                                   nlohmann::json::error_handler_t::replace),
1570                     pathString);
1571                 return;
1572             }
1573 
1574             const std::string* addr = nullptr;
1575             uint8_t prefix = 0;
1576 
1577             // Find the address and prefixLength values. Any values that are
1578             // not explicitly provided are assumed to be unmodified from the
1579             // current state of the interface. Merge existing state into the
1580             // current request.
1581             if (address)
1582             {
1583                 addr = &(*address);
1584             }
1585             else if (nicIpEntry != ipv6Data.end())
1586             {
1587                 addr = &(nicIpEntry->address);
1588             }
1589             else
1590             {
1591                 messages::propertyMissing(asyncResp->res,
1592                                           pathString + "/Address");
1593                 return;
1594             }
1595 
1596             if (prefixLength)
1597             {
1598                 prefix = *prefixLength;
1599             }
1600             else if (nicIpEntry != ipv6Data.end())
1601             {
1602                 prefix = nicIpEntry->prefixLength;
1603             }
1604             else
1605             {
1606                 messages::propertyMissing(asyncResp->res,
1607                                           pathString + "/PrefixLength");
1608                 return;
1609             }
1610 
1611             if (nicIpEntry != ipv6Data.end())
1612             {
1613                 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr,
1614                                     asyncResp);
1615                 nicIpEntry =
1616                     getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1617             }
1618             else
1619             {
1620                 createIPv6(ifaceId, *prefixLength, *addr, asyncResp);
1621             }
1622             entryIdx++;
1623         }
1624         else
1625         {
1626             if (nicIpEntry == ipv6Data.end())
1627             {
1628                 // Requesting a DELETE/DO NOT MODIFY action for an item
1629                 // that isn't present on the eth(n) interface. Input JSON is
1630                 // in error, so bail out.
1631                 if (thisJson.is_null())
1632                 {
1633                     messages::resourceCannotBeDeleted(asyncResp->res);
1634                     return;
1635                 }
1636                 messages::propertyValueFormatError(
1637                     asyncResp->res,
1638                     thisJson.dump(2, ' ', true,
1639                                   nlohmann::json::error_handler_t::replace),
1640                     pathString);
1641                 return;
1642             }
1643 
1644             if (thisJson.is_null())
1645             {
1646                 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp);
1647             }
1648             if (nicIpEntry != ipv6Data.cend())
1649             {
1650                 nicIpEntry =
1651                     getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1652             }
1653             entryIdx++;
1654         }
1655     }
1656 }
1657 
1658 inline void parseInterfaceData(
1659     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1660     const std::string& ifaceId, const EthernetInterfaceData& ethData,
1661     const boost::container::flat_set<IPv4AddressData>& ipv4Data,
1662     const boost::container::flat_set<IPv6AddressData>& ipv6Data)
1663 {
1664     constexpr const std::array<const char*, 1> inventoryForEthernet = {
1665         "xyz.openbmc_project.Inventory.Item.Ethernet"};
1666 
1667     nlohmann::json& jsonResponse = asyncResp->res.jsonValue;
1668     jsonResponse["Id"] = ifaceId;
1669     jsonResponse["@odata.id"] =
1670         "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId;
1671     jsonResponse["InterfaceEnabled"] = ethData.nicEnabled;
1672 
1673     auto health = std::make_shared<HealthPopulate>(asyncResp);
1674 
1675     crow::connections::systemBus->async_method_call(
1676         [health](const boost::system::error_code ec,
1677                  const dbus::utility::MapperGetSubTreePathsResponse& resp) {
1678             if (ec)
1679             {
1680                 return;
1681             }
1682 
1683             health->inventory = resp;
1684         },
1685         "xyz.openbmc_project.ObjectMapper",
1686         "/xyz/openbmc_project/object_mapper",
1687         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0),
1688         inventoryForEthernet);
1689 
1690     health->populate();
1691 
1692     if (ethData.nicEnabled)
1693     {
1694         jsonResponse["LinkStatus"] = "LinkUp";
1695         jsonResponse["Status"]["State"] = "Enabled";
1696     }
1697     else
1698     {
1699         jsonResponse["LinkStatus"] = "NoLink";
1700         jsonResponse["Status"]["State"] = "Disabled";
1701     }
1702 
1703     jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown";
1704     jsonResponse["SpeedMbps"] = ethData.speed;
1705     jsonResponse["MTUSize"] = ethData.mtuSize;
1706     jsonResponse["MACAddress"] = ethData.macAddress;
1707     jsonResponse["DHCPv4"]["DHCPEnabled"] =
1708         translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1709     jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpEnabled;
1710     jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsEnabled;
1711     jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNameEnabled;
1712 
1713     jsonResponse["DHCPv6"]["OperatingMode"] =
1714         translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Stateful"
1715                                                                : "Disabled";
1716     jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpEnabled;
1717     jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsEnabled;
1718     jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNameEnabled;
1719 
1720     if (!ethData.hostName.empty())
1721     {
1722         jsonResponse["HostName"] = ethData.hostName;
1723 
1724         // When domain name is empty then it means, that it is a network
1725         // without domain names, and the host name itself must be treated as
1726         // FQDN
1727         std::string fqdn = ethData.hostName;
1728         if (!ethData.domainnames.empty())
1729         {
1730             fqdn += "." + ethData.domainnames[0];
1731         }
1732         jsonResponse["FQDN"] = fqdn;
1733     }
1734 
1735     jsonResponse["VLANs"] = {
1736         {"@odata.id",
1737          "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId + "/VLANs"}};
1738 
1739     jsonResponse["NameServers"] = ethData.nameServers;
1740     jsonResponse["StaticNameServers"] = ethData.staticNameServers;
1741 
1742     nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"];
1743     nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"];
1744     ipv4Array = nlohmann::json::array();
1745     ipv4StaticArray = nlohmann::json::array();
1746     for (const auto& ipv4Config : ipv4Data)
1747     {
1748 
1749         std::string gatewayStr = ipv4Config.gateway;
1750         if (gatewayStr.empty())
1751         {
1752             gatewayStr = "0.0.0.0";
1753         }
1754         nlohmann::json::object_t ipv4;
1755         ipv4["AddressOrigin"] = ipv4Config.origin;
1756         ipv4["SubnetMask"] = ipv4Config.netmask;
1757         ipv4["Address"] = ipv4Config.address;
1758         ipv4["Gateway"] = gatewayStr;
1759 
1760         if (ipv4Config.origin == "Static")
1761         {
1762             ipv4StaticArray.push_back(ipv4);
1763         }
1764 
1765         ipv4Array.push_back(std::move(ipv4));
1766     }
1767 
1768     std::string ipv6GatewayStr = ethData.ipv6DefaultGateway;
1769     if (ipv6GatewayStr.empty())
1770     {
1771         ipv6GatewayStr = "0:0:0:0:0:0:0:0";
1772     }
1773 
1774     jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr;
1775 
1776     nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"];
1777     nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"];
1778     ipv6Array = nlohmann::json::array();
1779     ipv6StaticArray = nlohmann::json::array();
1780     nlohmann::json& ipv6AddrPolicyTable =
1781         jsonResponse["IPv6AddressPolicyTable"];
1782     ipv6AddrPolicyTable = nlohmann::json::array();
1783     for (const auto& ipv6Config : ipv6Data)
1784     {
1785         nlohmann::json::object_t ipv6;
1786         ipv6["Address"] = ipv6Config.address;
1787         ipv6["PrefixLength"] = ipv6Config.prefixLength;
1788         ipv6["AddressOrigin"] = ipv6Config.origin;
1789         ipv6["AddressState"] = nullptr;
1790         ipv6Array.push_back(std::move(ipv6));
1791         if (ipv6Config.origin == "Static")
1792         {
1793             nlohmann::json::object_t ipv6Static;
1794             ipv6Static["Address"] = ipv6Config.address;
1795             ipv6Static["PrefixLength"] = ipv6Config.prefixLength;
1796             ipv6StaticArray.push_back(std::move(ipv6Static));
1797         }
1798     }
1799 }
1800 
1801 inline void parseInterfaceData(nlohmann::json& jsonResponse,
1802                                const std::string& parentIfaceId,
1803                                const std::string& ifaceId,
1804                                const EthernetInterfaceData& ethData)
1805 {
1806     // Fill out obvious data...
1807     jsonResponse["Id"] = ifaceId;
1808     jsonResponse["@odata.id"] = "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1809                                 parentIfaceId + "/VLANs/" + ifaceId;
1810 
1811     jsonResponse["VLANEnable"] = true;
1812     if (ethData.vlanId)
1813     {
1814         jsonResponse["VLANId"] = *ethData.vlanId;
1815     }
1816 }
1817 
1818 inline bool verifyNames(const std::string& parent, const std::string& iface)
1819 {
1820     return boost::starts_with(iface, parent + "_");
1821 }
1822 
1823 inline void requestEthernetInterfacesRoutes(App& app)
1824 {
1825     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
1826         .privileges(redfish::privileges::getEthernetInterfaceCollection)
1827         .methods(boost::beast::http::verb::get)(
1828             [&app](const crow::Request& req,
1829                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1830                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1831                 {
1832                     return;
1833                 }
1834 
1835                 asyncResp->res.jsonValue["@odata.type"] =
1836                     "#EthernetInterfaceCollection.EthernetInterfaceCollection";
1837                 asyncResp->res.jsonValue["@odata.id"] =
1838                     "/redfish/v1/Managers/bmc/EthernetInterfaces";
1839                 asyncResp->res.jsonValue["Name"] =
1840                     "Ethernet Network Interface Collection";
1841                 asyncResp->res.jsonValue["Description"] =
1842                     "Collection of EthernetInterfaces for this Manager";
1843 
1844                 // Get eth interface list, and call the below callback for JSON
1845                 // preparation
1846                 getEthernetIfaceList([asyncResp](
1847                                          const bool& success,
1848                                          const boost::container::flat_set<
1849                                              std::string>& ifaceList) {
1850                     if (!success)
1851                     {
1852                         messages::internalError(asyncResp->res);
1853                         return;
1854                     }
1855 
1856                     nlohmann::json& ifaceArray =
1857                         asyncResp->res.jsonValue["Members"];
1858                     ifaceArray = nlohmann::json::array();
1859                     std::string tag = "_";
1860                     for (const std::string& ifaceItem : ifaceList)
1861                     {
1862                         std::size_t found = ifaceItem.find(tag);
1863                         if (found == std::string::npos)
1864                         {
1865                             nlohmann::json::object_t iface;
1866                             iface["@odata.id"] =
1867                                 "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1868                                 ifaceItem;
1869                             ifaceArray.push_back(std::move(iface));
1870                         }
1871                     }
1872 
1873                     asyncResp->res.jsonValue["Members@odata.count"] =
1874                         ifaceArray.size();
1875                     asyncResp->res.jsonValue["@odata.id"] =
1876                         "/redfish/v1/Managers/bmc/EthernetInterfaces";
1877                 });
1878             });
1879 
1880     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
1881         .privileges(redfish::privileges::getEthernetInterface)
1882         .methods(boost::beast::http::verb::get)(
1883             [&app](const crow::Request& req,
1884                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1885                    const std::string& ifaceId) {
1886                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1887                 {
1888                     return;
1889                 }
1890                 getEthernetIfaceData(
1891                     ifaceId,
1892                     [asyncResp,
1893                      ifaceId](const bool& success,
1894                               const EthernetInterfaceData& ethData,
1895                               const boost::container::flat_set<IPv4AddressData>&
1896                                   ipv4Data,
1897                               const boost::container::flat_set<IPv6AddressData>&
1898                                   ipv6Data) {
1899                         if (!success)
1900                         {
1901                             // TODO(Pawel)consider distinguish between non
1902                             // existing object, and other errors
1903                             messages::resourceNotFound(
1904                                 asyncResp->res, "EthernetInterface", ifaceId);
1905                             return;
1906                         }
1907 
1908                         asyncResp->res.jsonValue["@odata.type"] =
1909                             "#EthernetInterface.v1_4_1.EthernetInterface";
1910                         asyncResp->res.jsonValue["Name"] =
1911                             "Manager Ethernet Interface";
1912                         asyncResp->res.jsonValue["Description"] =
1913                             "Management Network Interface";
1914 
1915                         parseInterfaceData(asyncResp, ifaceId, ethData,
1916                                            ipv4Data, ipv6Data);
1917                     });
1918             });
1919 
1920     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
1921         .privileges(redfish::privileges::patchEthernetInterface)
1922         .methods(boost::beast::http::verb::patch)(
1923             [&app](const crow::Request& req,
1924                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1925                    const std::string& ifaceId) {
1926                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1927                 {
1928                     return;
1929                 }
1930                 std::optional<std::string> hostname;
1931                 std::optional<std::string> fqdn;
1932                 std::optional<std::string> macAddress;
1933                 std::optional<std::string> ipv6DefaultGateway;
1934                 std::optional<nlohmann::json> ipv4StaticAddresses;
1935                 std::optional<nlohmann::json> ipv6StaticAddresses;
1936                 std::optional<std::vector<std::string>> staticNameServers;
1937                 std::optional<nlohmann::json> dhcpv4;
1938                 std::optional<nlohmann::json> dhcpv6;
1939                 std::optional<bool> interfaceEnabled;
1940                 std::optional<size_t> mtuSize;
1941                 DHCPParameters v4dhcpParms;
1942                 DHCPParameters v6dhcpParms;
1943 
1944                 if (!json_util::readJsonPatch(
1945                         req, asyncResp->res, "HostName", hostname, "FQDN", fqdn,
1946                         "IPv4StaticAddresses", ipv4StaticAddresses,
1947                         "MACAddress", macAddress, "StaticNameServers",
1948                         staticNameServers, "IPv6DefaultGateway",
1949                         ipv6DefaultGateway, "IPv6StaticAddresses",
1950                         ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6,
1951                         "MTUSize", mtuSize, "InterfaceEnabled",
1952                         interfaceEnabled))
1953                 {
1954                     return;
1955                 }
1956                 if (dhcpv4)
1957                 {
1958                     if (!json_util::readJson(
1959                             *dhcpv4, asyncResp->res, "DHCPEnabled",
1960                             v4dhcpParms.dhcpv4Enabled, "UseDNSServers",
1961                             v4dhcpParms.useDnsServers, "UseNTPServers",
1962                             v4dhcpParms.useNtpServers, "UseDomainName",
1963                             v4dhcpParms.useDomainName))
1964                     {
1965                         return;
1966                     }
1967                 }
1968 
1969                 if (dhcpv6)
1970                 {
1971                     if (!json_util::readJson(
1972                             *dhcpv6, asyncResp->res, "OperatingMode",
1973                             v6dhcpParms.dhcpv6OperatingMode, "UseDNSServers",
1974                             v6dhcpParms.useDnsServers, "UseNTPServers",
1975                             v6dhcpParms.useNtpServers, "UseDomainName",
1976                             v6dhcpParms.useDomainName))
1977                     {
1978                         return;
1979                     }
1980                 }
1981 
1982                 // Get single eth interface data, and call the below callback
1983                 // for JSON preparation
1984                 getEthernetIfaceData(
1985                     ifaceId,
1986                     [asyncResp, ifaceId, hostname = std::move(hostname),
1987                      fqdn = std::move(fqdn), macAddress = std::move(macAddress),
1988                      ipv4StaticAddresses = std::move(ipv4StaticAddresses),
1989                      ipv6DefaultGateway = std::move(ipv6DefaultGateway),
1990                      ipv6StaticAddresses = std::move(ipv6StaticAddresses),
1991                      staticNameServers = std::move(staticNameServers),
1992                      dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6),
1993                      mtuSize = mtuSize, v4dhcpParms = std::move(v4dhcpParms),
1994                      v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled](
1995                         const bool& success,
1996                         const EthernetInterfaceData& ethData,
1997                         const boost::container::flat_set<IPv4AddressData>&
1998                             ipv4Data,
1999                         const boost::container::flat_set<IPv6AddressData>&
2000                             ipv6Data) {
2001                         if (!success)
2002                         {
2003                             // ... otherwise return error
2004                             // TODO(Pawel)consider distinguish between non
2005                             // existing object, and other errors
2006                             messages::resourceNotFound(
2007                                 asyncResp->res, "Ethernet Interface", ifaceId);
2008                             return;
2009                         }
2010 
2011                         if (dhcpv4 || dhcpv6)
2012                         {
2013                             handleDHCPPatch(ifaceId, ethData, v4dhcpParms,
2014                                             v6dhcpParms, asyncResp);
2015                         }
2016 
2017                         if (hostname)
2018                         {
2019                             handleHostnamePatch(*hostname, asyncResp);
2020                         }
2021 
2022                         if (fqdn)
2023                         {
2024                             handleFqdnPatch(ifaceId, *fqdn, asyncResp);
2025                         }
2026 
2027                         if (macAddress)
2028                         {
2029                             handleMACAddressPatch(ifaceId, *macAddress,
2030                                                   asyncResp);
2031                         }
2032 
2033                         if (ipv4StaticAddresses)
2034                         {
2035                             // TODO(ed) for some reason the capture of
2036                             // ipv4Addresses above is returning a const value,
2037                             // not a non-const value. This doesn't really work
2038                             // for us, as we need to be able to efficiently move
2039                             // out the intermedia nlohmann::json objects. This
2040                             // makes a copy of the structure, and operates on
2041                             // that, but could be done more efficiently
2042                             nlohmann::json ipv4Static = *ipv4StaticAddresses;
2043                             handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data,
2044                                                   asyncResp);
2045                         }
2046 
2047                         if (staticNameServers)
2048                         {
2049                             handleStaticNameServersPatch(
2050                                 ifaceId, *staticNameServers, asyncResp);
2051                         }
2052 
2053                         if (ipv6DefaultGateway)
2054                         {
2055                             messages::propertyNotWritable(asyncResp->res,
2056                                                           "IPv6DefaultGateway");
2057                         }
2058 
2059                         if (ipv6StaticAddresses)
2060                         {
2061                             const nlohmann::json& ipv6Static =
2062                                 *ipv6StaticAddresses;
2063                             handleIPv6StaticAddressesPatch(ifaceId, ipv6Static,
2064                                                            ipv6Data, asyncResp);
2065                         }
2066 
2067                         if (interfaceEnabled)
2068                         {
2069                             setEthernetInterfaceBoolProperty(
2070                                 ifaceId, "NICEnabled", *interfaceEnabled,
2071                                 asyncResp);
2072                         }
2073 
2074                         if (mtuSize)
2075                         {
2076                             handleMTUSizePatch(ifaceId, *mtuSize, asyncResp);
2077                         }
2078                     });
2079             });
2080 
2081     BMCWEB_ROUTE(
2082         app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/")
2083         .privileges(redfish::privileges::getVLanNetworkInterface)
2084         .methods(boost::beast::http::verb::get)(
2085             [&app](const crow::Request& req,
2086                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2087                    const std::string& parentIfaceId,
2088                    const std::string& ifaceId) {
2089                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
2090                 {
2091                     return;
2092                 }
2093                 asyncResp->res.jsonValue["@odata.type"] =
2094                     "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
2095                 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface";
2096 
2097                 if (!verifyNames(parentIfaceId, ifaceId))
2098                 {
2099                     return;
2100                 }
2101 
2102                 // Get single eth interface data, and call the below callback
2103                 // for JSON preparation
2104                 getEthernetIfaceData(
2105                     ifaceId,
2106                     [asyncResp, parentIfaceId, ifaceId](
2107                         const bool& success,
2108                         const EthernetInterfaceData& ethData,
2109                         const boost::container::flat_set<IPv4AddressData>&,
2110                         const boost::container::flat_set<IPv6AddressData>&) {
2111                         if (success && ethData.vlanId)
2112                         {
2113                             parseInterfaceData(asyncResp->res.jsonValue,
2114                                                parentIfaceId, ifaceId, ethData);
2115                         }
2116                         else
2117                         {
2118                             // ... otherwise return error
2119                             // TODO(Pawel)consider distinguish between non
2120                             // existing object, and other errors
2121                             messages::resourceNotFound(asyncResp->res,
2122                                                        "VLAN Network Interface",
2123                                                        ifaceId);
2124                         }
2125                     });
2126             });
2127 
2128     BMCWEB_ROUTE(
2129         app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/")
2130         .privileges(redfish::privileges::patchVLanNetworkInterface)
2131         .methods(boost::beast::http::verb::patch)(
2132             [&app](const crow::Request& req,
2133                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2134                    const std::string& parentIfaceId,
2135                    const std::string& ifaceId) {
2136                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
2137                 {
2138                     return;
2139                 }
2140                 if (!verifyNames(parentIfaceId, ifaceId))
2141                 {
2142                     messages::resourceNotFound(
2143                         asyncResp->res, "VLAN Network Interface", ifaceId);
2144                     return;
2145                 }
2146 
2147                 std::optional<bool> vlanEnable;
2148                 std::optional<uint32_t> vlanId;
2149 
2150                 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable",
2151                                               vlanEnable, "VLANId", vlanId))
2152                 {
2153                     return;
2154                 }
2155 
2156                 if (vlanId)
2157                 {
2158                     messages::propertyNotWritable(asyncResp->res, "VLANId");
2159                     return;
2160                 }
2161 
2162                 // Get single eth interface data, and call the below callback
2163                 // for JSON preparation
2164                 getEthernetIfaceData(
2165                     ifaceId,
2166                     [asyncResp, parentIfaceId, ifaceId, vlanEnable](
2167                         const bool& success,
2168                         const EthernetInterfaceData& ethData,
2169                         const boost::container::flat_set<IPv4AddressData>&,
2170                         const boost::container::flat_set<IPv6AddressData>&) {
2171                         if (success && ethData.vlanId)
2172                         {
2173                             auto callback =
2174                                 [asyncResp](
2175                                     const boost::system::error_code ec) {
2176                                     if (ec)
2177                                     {
2178                                         messages::internalError(asyncResp->res);
2179                                         return;
2180                                     }
2181                                 };
2182 
2183                             if (vlanEnable && !(*vlanEnable))
2184                             {
2185                                 BMCWEB_LOG_DEBUG
2186                                     << "vlanEnable is false. Deleting the "
2187                                        "vlan interface";
2188                                 crow::connections::systemBus->async_method_call(
2189                                     std::move(callback),
2190                                     "xyz.openbmc_project.Network",
2191                                     std::string(
2192                                         "/xyz/openbmc_project/network/") +
2193                                         ifaceId,
2194                                     "xyz.openbmc_project.Object.Delete",
2195                                     "Delete");
2196                             }
2197                         }
2198                         else
2199                         {
2200                             // TODO(Pawel)consider distinguish between non
2201                             // existing object, and other errors
2202                             messages::resourceNotFound(asyncResp->res,
2203                                                        "VLAN Network Interface",
2204                                                        ifaceId);
2205                             return;
2206                         }
2207                     });
2208             });
2209 
2210     BMCWEB_ROUTE(
2211         app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/")
2212         .privileges(redfish::privileges::deleteVLanNetworkInterface)
2213         .methods(boost::beast::http::verb::delete_)(
2214             [&app](const crow::Request& req,
2215                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2216                    const std::string& parentIfaceId,
2217                    const std::string& ifaceId) {
2218                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
2219                 {
2220                     return;
2221                 }
2222                 if (!verifyNames(parentIfaceId, ifaceId))
2223                 {
2224                     messages::resourceNotFound(
2225                         asyncResp->res, "VLAN Network Interface", ifaceId);
2226                     return;
2227                 }
2228 
2229                 // Get single eth interface data, and call the below callback
2230                 // for JSON preparation
2231                 getEthernetIfaceData(
2232                     ifaceId,
2233                     [asyncResp, parentIfaceId, ifaceId](
2234                         const bool& success,
2235                         const EthernetInterfaceData& ethData,
2236                         const boost::container::flat_set<IPv4AddressData>&,
2237                         const boost::container::flat_set<IPv6AddressData>&) {
2238                         if (success && ethData.vlanId)
2239                         {
2240                             auto callback =
2241                                 [asyncResp](
2242                                     const boost::system::error_code ec) {
2243                                     if (ec)
2244                                     {
2245                                         messages::internalError(asyncResp->res);
2246                                     }
2247                                 };
2248                             crow::connections::systemBus->async_method_call(
2249                                 std::move(callback),
2250                                 "xyz.openbmc_project.Network",
2251                                 std::string("/xyz/openbmc_project/network/") +
2252                                     ifaceId,
2253                                 "xyz.openbmc_project.Object.Delete", "Delete");
2254                         }
2255                         else
2256                         {
2257                             // ... otherwise return error
2258                             // TODO(Pawel)consider distinguish between non
2259                             // existing object, and other errors
2260                             messages::resourceNotFound(asyncResp->res,
2261                                                        "VLAN Network Interface",
2262                                                        ifaceId);
2263                         }
2264                     });
2265             });
2266 
2267     BMCWEB_ROUTE(app,
2268                  "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/")
2269 
2270         .privileges(redfish::privileges::getVLanNetworkInterfaceCollection)
2271         .methods(boost::beast::http::verb::get)(
2272             [&app](const crow::Request& req,
2273                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2274                    const std::string& rootInterfaceName) {
2275                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
2276                 {
2277                     return;
2278                 }
2279                 // Get eth interface list, and call the below callback for JSON
2280                 // preparation
2281                 getEthernetIfaceList([asyncResp, rootInterfaceName](
2282                                          const bool& success,
2283                                          const boost::container::flat_set<
2284                                              std::string>& ifaceList) {
2285                     if (!success)
2286                     {
2287                         messages::internalError(asyncResp->res);
2288                         return;
2289                     }
2290 
2291                     if (ifaceList.find(rootInterfaceName) == ifaceList.end())
2292                     {
2293                         messages::resourceNotFound(
2294                             asyncResp->res, "VLanNetworkInterfaceCollection",
2295                             rootInterfaceName);
2296                         return;
2297                     }
2298 
2299                     asyncResp->res.jsonValue["@odata.type"] =
2300                         "#VLanNetworkInterfaceCollection."
2301                         "VLanNetworkInterfaceCollection";
2302                     asyncResp->res.jsonValue["Name"] =
2303                         "VLAN Network Interface Collection";
2304 
2305                     nlohmann::json ifaceArray = nlohmann::json::array();
2306 
2307                     for (const std::string& ifaceItem : ifaceList)
2308                     {
2309                         if (boost::starts_with(ifaceItem,
2310                                                rootInterfaceName + "_"))
2311                         {
2312                             std::string path =
2313                                 "/redfish/v1/Managers/bmc/EthernetInterfaces/";
2314                             path += rootInterfaceName;
2315                             path += "/VLANs/";
2316                             path += ifaceItem;
2317                             nlohmann::json::object_t iface;
2318                             iface["@odata.id"] = std::move(path);
2319                             ifaceArray.push_back(std::move(iface));
2320                         }
2321                     }
2322 
2323                     asyncResp->res.jsonValue["Members@odata.count"] =
2324                         ifaceArray.size();
2325                     asyncResp->res.jsonValue["Members"] = std::move(ifaceArray);
2326                     asyncResp->res.jsonValue["@odata.id"] =
2327                         "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
2328                         rootInterfaceName + "/VLANs";
2329                 });
2330             });
2331 
2332     BMCWEB_ROUTE(app,
2333                  "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/")
2334         .privileges(redfish::privileges::postVLanNetworkInterfaceCollection)
2335         .methods(boost::beast::http::verb::post)(
2336             [&app](const crow::Request& req,
2337                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2338                    const std::string& rootInterfaceName) {
2339                 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
2340                 {
2341                     return;
2342                 }
2343                 bool vlanEnable = false;
2344                 uint32_t vlanId = 0;
2345                 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId",
2346                                               vlanId, "VLANEnable", vlanEnable))
2347                 {
2348                     return;
2349                 }
2350                 // Need both vlanId and vlanEnable to service this request
2351                 if (vlanId == 0U)
2352                 {
2353                     messages::propertyMissing(asyncResp->res, "VLANId");
2354                 }
2355                 if (!vlanEnable)
2356                 {
2357                     messages::propertyMissing(asyncResp->res, "VLANEnable");
2358                 }
2359                 if (static_cast<bool>(vlanId) ^ vlanEnable)
2360                 {
2361                     return;
2362                 }
2363 
2364                 auto callback =
2365                     [asyncResp](const boost::system::error_code ec) {
2366                         if (ec)
2367                         {
2368                             // TODO(ed) make more consistent error messages
2369                             // based on phosphor-network responses
2370                             messages::internalError(asyncResp->res);
2371                             return;
2372                         }
2373                         messages::created(asyncResp->res);
2374                     };
2375                 crow::connections::systemBus->async_method_call(
2376                     std::move(callback), "xyz.openbmc_project.Network",
2377                     "/xyz/openbmc_project/network",
2378                     "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
2379                     rootInterfaceName, vlanId);
2380             });
2381 }
2382 
2383 } // namespace redfish
2384