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