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