xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision ab0d439013c3a42a8221f8718124bcc897ac21ab)
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, "ProtocolType",
863                 staticGateway.protocol);
864             if (!success)
865             {
866                 return false;
867             }
868         }
869     }
870     return true;
871 }
872 
873 /**
874  * @brief Creates IPv6 with given data
875  *
876  * @param[in] ifaceId      Id of interface whose IP should be added
877  * @param[in] prefixLength Prefix length that needs to be added
878  * @param[in] address      IP address that needs to be added
879  * @param[io] asyncResp    Response object that will be returned to client
880  *
881  * @return None
882  */
883 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength,
884                        const std::string& address,
885                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
886 {
887     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
888     path /= ifaceId;
889 
890     auto createIpHandler =
891         [asyncResp, address](const boost::system::error_code& ec) {
892             if (ec)
893             {
894                 if (ec == boost::system::errc::io_error)
895                 {
896                     messages::propertyValueFormatError(asyncResp->res, address,
897                                                        "Address");
898                 }
899                 else
900                 {
901                     messages::internalError(asyncResp->res);
902                 }
903             }
904         };
905     // Passing null for gateway, as per redfish spec IPv6StaticAddresses
906     // object does not have associated gateway property
907     crow::connections::systemBus->async_method_call(
908         std::move(createIpHandler), "xyz.openbmc_project.Network", path,
909         "xyz.openbmc_project.Network.IP.Create", "IP",
910         "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength,
911         "");
912 }
913 
914 /**
915  * @brief Deletes given IPv6 Static Gateway
916  *
917  * @param[in] ifaceId     Id of interface whose IP should be deleted
918  * @param[in] ipHash      DBus Hash id of IP that should be deleted
919  * @param[io] asyncResp   Response object that will be returned to client
920  *
921  * @return None
922  */
923 inline void
924     deleteIPv6Gateway(std::string_view ifaceId, std::string_view gatewayId,
925                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
926 {
927     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
928     path /= ifaceId;
929     path /= gatewayId;
930     crow::connections::systemBus->async_method_call(
931         [asyncResp](const boost::system::error_code& ec) {
932             if (ec)
933             {
934                 messages::internalError(asyncResp->res);
935             }
936         },
937         "xyz.openbmc_project.Network", path,
938         "xyz.openbmc_project.Object.Delete", "Delete");
939 }
940 
941 /**
942  * @brief Creates IPv6 static default gateway with given data
943  *
944  * @param[in] ifaceId      Id of interface whose IP should be added
945  * @param[in] gateway      Gateway address that needs to be added
946  * @param[io] asyncResp    Response object that will be returned to client
947  *
948  * @return None
949  */
950 inline void createIPv6DefaultGateway(
951     std::string_view ifaceId, std::string_view gateway,
952     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
953 {
954     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
955     path /= ifaceId;
956     auto createIpHandler = [asyncResp](const boost::system::error_code& ec) {
957         if (ec)
958         {
959             messages::internalError(asyncResp->res);
960         }
961     };
962     crow::connections::systemBus->async_method_call(
963         std::move(createIpHandler), "xyz.openbmc_project.Network", path,
964         "xyz.openbmc_project.Network.StaticGateway.Create", "StaticGateway",
965         gateway, "xyz.openbmc_project.Network.IP.Protocol.IPv6");
966 }
967 
968 /**
969  * @brief Deletes the IPv6 default gateway entry for this interface and
970  * creates a replacement IPv6 default gateway entry
971  *
972  * @param[in] ifaceId      Id of interface upon which to create the IPv6
973  * entry
974  * @param[in] gateway      IPv6 gateway to assign to this interface
975  * @param[io] asyncResp    Response object that will be returned to client
976  *
977  * @return None
978  */
979 inline void deleteAndCreateIPv6DefaultGateway(
980     std::string_view ifaceId, std::string_view gatewayId,
981     std::string_view gateway,
982     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
983 {
984     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
985     path /= ifaceId;
986     path /= gatewayId;
987     crow::connections::systemBus->async_method_call(
988         [asyncResp, ifaceId, gateway](const boost::system::error_code& ec) {
989             if (ec)
990             {
991                 messages::internalError(asyncResp->res);
992                 return;
993             }
994             createIPv6DefaultGateway(ifaceId, gateway, asyncResp);
995         },
996         "xyz.openbmc_project.Network", path,
997         "xyz.openbmc_project.Object.Delete", "Delete");
998 }
999 
1000 /**
1001  * @brief Sets IPv6 default gateway with given data
1002  *
1003  * @param[in] ifaceId      Id of interface whose gateway should be added
1004  * @param[in] input        Contains address that needs to be added
1005  * @param[in] staticGatewayData  Current static gateways in the system
1006  * @param[io] asyncResp    Response object that will be returned to client
1007  *
1008  * @return None
1009  */
1010 
1011 inline void handleIPv6DefaultGateway(
1012     const std::string& ifaceId,
1013     std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input,
1014     const std::vector<StaticGatewayData>& staticGatewayData,
1015     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1016 {
1017     size_t entryIdx = 1;
1018     std::vector<StaticGatewayData>::const_iterator staticGatewayEntry =
1019         staticGatewayData.begin();
1020 
1021     for (std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson :
1022          input)
1023     {
1024         // find the next gateway entry
1025         while (staticGatewayEntry != staticGatewayData.end())
1026         {
1027             if (staticGatewayEntry->protocol ==
1028                 "xyz.openbmc_project.Network.IP.Protocol.IPv6")
1029             {
1030                 break;
1031             }
1032             staticGatewayEntry++;
1033         }
1034         std::string pathString =
1035             "IPv6StaticDefaultGateways/" + std::to_string(entryIdx);
1036         nlohmann::json::object_t* obj =
1037             std::get_if<nlohmann::json::object_t>(&thisJson);
1038         if (obj == nullptr)
1039         {
1040             if (staticGatewayEntry == staticGatewayData.end())
1041             {
1042                 messages::resourceCannotBeDeleted(asyncResp->res);
1043                 return;
1044             }
1045             deleteIPv6Gateway(ifaceId, staticGatewayEntry->id, asyncResp);
1046             return;
1047         }
1048         if (obj->empty())
1049         {
1050             // Do nothing, but make sure the entry exists.
1051             if (staticGatewayEntry == staticGatewayData.end())
1052             {
1053                 messages::propertyValueFormatError(asyncResp->res, *obj,
1054                                                    pathString);
1055                 return;
1056             }
1057         }
1058         std::optional<std::string> address;
1059 
1060         if (!json_util::readJsonObject(*obj, asyncResp->res, "Address",
1061                                        address))
1062         {
1063             return;
1064         }
1065         const std::string* addr = nullptr;
1066         if (address)
1067         {
1068             addr = &(*address);
1069         }
1070         else if (staticGatewayEntry != staticGatewayData.end())
1071         {
1072             addr = &(staticGatewayEntry->gateway);
1073         }
1074         else
1075         {
1076             messages::propertyMissing(asyncResp->res, pathString + "/Address");
1077             return;
1078         }
1079         if (staticGatewayEntry != staticGatewayData.end())
1080         {
1081             deleteAndCreateIPv6DefaultGateway(ifaceId, staticGatewayEntry->id,
1082                                               *addr, asyncResp);
1083             staticGatewayEntry++;
1084         }
1085         else
1086         {
1087             createIPv6DefaultGateway(ifaceId, *addr, asyncResp);
1088         }
1089         entryIdx++;
1090     }
1091 }
1092 
1093 /**
1094  * Function that retrieves all properties for given Ethernet Interface
1095  * Object
1096  * from EntityManager Network Manager
1097  * @param ethiface_id a eth interface id to query on DBus
1098  * @param callback a function that shall be called to convert Dbus output
1099  * into JSON
1100  */
1101 template <typename CallbackFunc>
1102 void getEthernetIfaceData(const std::string& ethifaceId,
1103                           CallbackFunc&& callback)
1104 {
1105     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
1106     dbus::utility::getManagedObjects(
1107         "xyz.openbmc_project.Network", path,
1108         [ethifaceId{std::string{ethifaceId}},
1109          callback = std::forward<CallbackFunc>(callback)](
1110             const boost::system::error_code& ec,
1111             const dbus::utility::ManagedObjectType& resp) mutable {
1112             EthernetInterfaceData ethData{};
1113             std::vector<IPv4AddressData> ipv4Data;
1114             std::vector<IPv6AddressData> ipv6Data;
1115             std::vector<StaticGatewayData> ipv6GatewayData;
1116 
1117             if (ec)
1118             {
1119                 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1120                 return;
1121             }
1122 
1123             bool found =
1124                 extractEthernetInterfaceData(ethifaceId, resp, ethData);
1125             if (!found)
1126             {
1127                 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1128                 return;
1129             }
1130 
1131             extractIPData(ethifaceId, resp, ipv4Data);
1132             // Fix global GW
1133             for (IPv4AddressData& ipv4 : ipv4Data)
1134             {
1135                 if (((ipv4.linktype == LinkType::Global) &&
1136                      (ipv4.gateway == "0.0.0.0")) ||
1137                     (ipv4.origin == "DHCP") || (ipv4.origin == "Static"))
1138                 {
1139                     ipv4.gateway = ethData.defaultGateway;
1140                 }
1141             }
1142 
1143             extractIPV6Data(ethifaceId, resp, ipv6Data);
1144             if (!extractIPv6DefaultGatewayData(ethifaceId, resp,
1145                                                ipv6GatewayData))
1146             {
1147                 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1148             }
1149             // Finally make a callback with useful data
1150             callback(true, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1151         });
1152 }
1153 
1154 /**
1155  * Function that retrieves all Ethernet Interfaces available through Network
1156  * Manager
1157  * @param callback a function that shall be called to convert Dbus output
1158  * into JSON.
1159  */
1160 template <typename CallbackFunc>
1161 void getEthernetIfaceList(CallbackFunc&& callback)
1162 {
1163     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
1164     dbus::utility::getManagedObjects(
1165         "xyz.openbmc_project.Network", path,
1166         [callback = std::forward<CallbackFunc>(callback)](
1167             const boost::system::error_code& ec,
1168             const dbus::utility::ManagedObjectType& resp) {
1169             // Callback requires vector<string> to retrieve all available
1170             // ethernet interfaces
1171             std::vector<std::string> ifaceList;
1172             ifaceList.reserve(resp.size());
1173             if (ec)
1174             {
1175                 callback(false, ifaceList);
1176                 return;
1177             }
1178 
1179             // Iterate over all retrieved ObjectPaths.
1180             for (const auto& objpath : resp)
1181             {
1182                 // And all interfaces available for certain ObjectPath.
1183                 for (const auto& interface : objpath.second)
1184                 {
1185                     // If interface is
1186                     // xyz.openbmc_project.Network.EthernetInterface, this is
1187                     // what we're looking for.
1188                     if (interface.first ==
1189                         "xyz.openbmc_project.Network.EthernetInterface")
1190                     {
1191                         std::string ifaceId = objpath.first.filename();
1192                         if (ifaceId.empty())
1193                         {
1194                             continue;
1195                         }
1196                         // and put it into output vector.
1197                         ifaceList.emplace_back(ifaceId);
1198                     }
1199                 }
1200             }
1201 
1202             std::ranges::sort(ifaceList, AlphanumLess<std::string>());
1203 
1204             // Finally make a callback with useful data
1205             callback(true, ifaceList);
1206         });
1207 }
1208 
1209 inline void
1210     handleHostnamePatch(const std::string& hostname,
1211                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1212 {
1213     // SHOULD handle host names of up to 255 characters(RFC 1123)
1214     if (hostname.length() > 255)
1215     {
1216         messages::propertyValueFormatError(asyncResp->res, hostname,
1217                                            "HostName");
1218         return;
1219     }
1220     setDbusProperty(
1221         asyncResp, "HostName", "xyz.openbmc_project.Network",
1222         sdbusplus::message::object_path("/xyz/openbmc_project/network/config"),
1223         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
1224         hostname);
1225 }
1226 
1227 inline void
1228     handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize,
1229                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1230 {
1231     sdbusplus::message::object_path objPath("/xyz/openbmc_project/network");
1232     objPath /= ifaceId;
1233     setDbusProperty(asyncResp, "MTUSize", "xyz.openbmc_project.Network",
1234                     objPath, "xyz.openbmc_project.Network.EthernetInterface",
1235                     "MTU", mtuSize);
1236 }
1237 
1238 inline void handleDomainnamePatch(
1239     const std::string& ifaceId, const std::string& domainname,
1240     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1241 {
1242     std::vector<std::string> vectorDomainname = {domainname};
1243     setDbusProperty(
1244         asyncResp, "FQDN", "xyz.openbmc_project.Network",
1245         sdbusplus::message::object_path("/xyz/openbmc_project/network") /
1246             ifaceId,
1247         "xyz.openbmc_project.Network.EthernetInterface", "DomainName",
1248         vectorDomainname);
1249 }
1250 
1251 inline bool isHostnameValid(const std::string& hostname)
1252 {
1253     // A valid host name can never have the dotted-decimal form (RFC 1123)
1254     if (std::ranges::all_of(hostname, ::isdigit))
1255     {
1256         return false;
1257     }
1258     // Each label(hostname/subdomains) within a valid FQDN
1259     // MUST handle host names of up to 63 characters (RFC 1123)
1260     // labels cannot start or end with hyphens (RFC 952)
1261     // labels can start with numbers (RFC 1123)
1262     const static std::regex pattern(
1263         "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
1264 
1265     return std::regex_match(hostname, pattern);
1266 }
1267 
1268 inline bool isDomainnameValid(const std::string& domainname)
1269 {
1270     // Can have multiple subdomains
1271     // Top Level Domain's min length is 2 character
1272     const static std::regex pattern(
1273         "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$");
1274 
1275     return std::regex_match(domainname, pattern);
1276 }
1277 
1278 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn,
1279                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1280 {
1281     // Total length of FQDN must not exceed 255 characters(RFC 1035)
1282     if (fqdn.length() > 255)
1283     {
1284         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1285         return;
1286     }
1287 
1288     size_t pos = fqdn.find('.');
1289     if (pos == std::string::npos)
1290     {
1291         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1292         return;
1293     }
1294 
1295     std::string hostname;
1296     std::string domainname;
1297     domainname = (fqdn).substr(pos + 1);
1298     hostname = (fqdn).substr(0, pos);
1299 
1300     if (!isHostnameValid(hostname) || !isDomainnameValid(domainname))
1301     {
1302         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1303         return;
1304     }
1305 
1306     handleHostnamePatch(hostname, asyncResp);
1307     handleDomainnamePatch(ifaceId, domainname, asyncResp);
1308 }
1309 
1310 inline void handleMACAddressPatch(
1311     const std::string& ifaceId, const std::string& macAddress,
1312     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1313 {
1314     setDbusProperty(
1315         asyncResp, "MACAddress", "xyz.openbmc_project.Network",
1316         sdbusplus::message::object_path("/xyz/openbmc_project/network") /
1317             ifaceId,
1318         "xyz.openbmc_project.Network.MACAddress", "MACAddress", macAddress);
1319 }
1320 
1321 inline void setDHCPEnabled(const std::string& ifaceId,
1322                            const std::string& propertyName, const bool v4Value,
1323                            const bool v6Value,
1324                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1325 {
1326     const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value);
1327     setDbusProperty(
1328         asyncResp, "DHCPv4", "xyz.openbmc_project.Network",
1329         sdbusplus::message::object_path("/xyz/openbmc_project/network") /
1330             ifaceId,
1331         "xyz.openbmc_project.Network.EthernetInterface", propertyName, dhcp);
1332 }
1333 
1334 enum class NetworkType
1335 {
1336     dhcp4,
1337     dhcp6
1338 };
1339 
1340 inline void setDHCPConfig(const std::string& propertyName, const bool& value,
1341                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1342                           const std::string& ethifaceId, NetworkType type)
1343 {
1344     BMCWEB_LOG_DEBUG("{} = {}", propertyName, value);
1345     std::string redfishPropertyName;
1346     sdbusplus::message::object_path path("/xyz/openbmc_project/network/");
1347     path /= ethifaceId;
1348 
1349     if (type == NetworkType::dhcp4)
1350     {
1351         path /= "dhcp4";
1352         redfishPropertyName = "DHCPv4";
1353     }
1354     else
1355     {
1356         path /= "dhcp6";
1357         redfishPropertyName = "DHCPv6";
1358     }
1359 
1360     setDbusProperty(
1361         asyncResp, redfishPropertyName, "xyz.openbmc_project.Network", path,
1362         "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, value);
1363 }
1364 
1365 inline void handleSLAACAutoConfigPatch(
1366     const std::string& ifaceId, bool ipv6AutoConfigEnabled,
1367     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1368 {
1369     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
1370     path /= ifaceId;
1371     setDbusProperty(asyncResp,
1372                     "StatelessAddressAutoConfig/IPv6AutoConfigEnabled",
1373                     "xyz.openbmc_project.Network", path,
1374                     "xyz.openbmc_project.Network.EthernetInterface",
1375                     "IPv6AcceptRA", ipv6AutoConfigEnabled);
1376 }
1377 
1378 inline void handleDHCPPatch(
1379     const std::string& ifaceId, const EthernetInterfaceData& ethData,
1380     const DHCPParameters& v4dhcpParms, const DHCPParameters& v6dhcpParms,
1381     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1382 {
1383     bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1384     bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false);
1385 
1386     if (ipv4Active)
1387     {
1388         updateIPv4DefaultGateway(ifaceId, "", asyncResp);
1389     }
1390     bool nextv4DHCPState =
1391         v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active;
1392 
1393     bool nextv6DHCPState{};
1394     if (v6dhcpParms.dhcpv6OperatingMode)
1395     {
1396         if ((*v6dhcpParms.dhcpv6OperatingMode != "Enabled") &&
1397             (*v6dhcpParms.dhcpv6OperatingMode != "Disabled"))
1398         {
1399             messages::propertyValueFormatError(asyncResp->res,
1400                                                *v6dhcpParms.dhcpv6OperatingMode,
1401                                                "OperatingMode");
1402             return;
1403         }
1404         nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Enabled");
1405     }
1406     else
1407     {
1408         nextv6DHCPState = ipv6Active;
1409     }
1410 
1411     bool nextDNSv4 = ethData.dnsv4Enabled;
1412     bool nextDNSv6 = ethData.dnsv6Enabled;
1413     if (v4dhcpParms.useDnsServers)
1414     {
1415         nextDNSv4 = *v4dhcpParms.useDnsServers;
1416     }
1417     if (v6dhcpParms.useDnsServers)
1418     {
1419         nextDNSv6 = *v6dhcpParms.useDnsServers;
1420     }
1421 
1422     bool nextNTPv4 = ethData.ntpv4Enabled;
1423     bool nextNTPv6 = ethData.ntpv6Enabled;
1424     if (v4dhcpParms.useNtpServers)
1425     {
1426         nextNTPv4 = *v4dhcpParms.useNtpServers;
1427     }
1428     if (v6dhcpParms.useNtpServers)
1429     {
1430         nextNTPv6 = *v6dhcpParms.useNtpServers;
1431     }
1432 
1433     bool nextUsev4Domain = ethData.domainv4Enabled;
1434     bool nextUsev6Domain = ethData.domainv6Enabled;
1435     if (v4dhcpParms.useDomainName)
1436     {
1437         nextUsev4Domain = *v4dhcpParms.useDomainName;
1438     }
1439     if (v6dhcpParms.useDomainName)
1440     {
1441         nextUsev6Domain = *v6dhcpParms.useDomainName;
1442     }
1443 
1444     BMCWEB_LOG_DEBUG("set DHCPEnabled...");
1445     setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState,
1446                    asyncResp);
1447     BMCWEB_LOG_DEBUG("set DNSEnabled...");
1448     setDHCPConfig("DNSEnabled", nextDNSv4, asyncResp, ifaceId,
1449                   NetworkType::dhcp4);
1450     BMCWEB_LOG_DEBUG("set NTPEnabled...");
1451     setDHCPConfig("NTPEnabled", nextNTPv4, asyncResp, ifaceId,
1452                   NetworkType::dhcp4);
1453     BMCWEB_LOG_DEBUG("set DomainEnabled...");
1454     setDHCPConfig("DomainEnabled", nextUsev4Domain, asyncResp, ifaceId,
1455                   NetworkType::dhcp4);
1456     BMCWEB_LOG_DEBUG("set DNSEnabled for dhcp6...");
1457     setDHCPConfig("DNSEnabled", nextDNSv6, asyncResp, ifaceId,
1458                   NetworkType::dhcp6);
1459     BMCWEB_LOG_DEBUG("set NTPEnabled for dhcp6...");
1460     setDHCPConfig("NTPEnabled", nextNTPv6, asyncResp, ifaceId,
1461                   NetworkType::dhcp6);
1462     BMCWEB_LOG_DEBUG("set DomainEnabled for dhcp6...");
1463     setDHCPConfig("DomainEnabled", nextUsev6Domain, asyncResp, ifaceId,
1464                   NetworkType::dhcp6);
1465 }
1466 
1467 inline std::vector<IPv4AddressData>::const_iterator getNextStaticIpEntry(
1468     const std::vector<IPv4AddressData>::const_iterator& head,
1469     const std::vector<IPv4AddressData>::const_iterator& end)
1470 {
1471     return std::find_if(head, end, [](const IPv4AddressData& value) {
1472         return value.origin == "Static";
1473     });
1474 }
1475 
1476 inline std::vector<IPv6AddressData>::const_iterator getNextStaticIpEntry(
1477     const std::vector<IPv6AddressData>::const_iterator& head,
1478     const std::vector<IPv6AddressData>::const_iterator& end)
1479 {
1480     return std::find_if(head, end, [](const IPv6AddressData& value) {
1481         return value.origin == "Static";
1482     });
1483 }
1484 
1485 inline void handleIPv4StaticPatch(
1486     const std::string& ifaceId,
1487     std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input,
1488     const EthernetInterfaceData& ethData,
1489     const std::vector<IPv4AddressData>& ipv4Data,
1490     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1491 {
1492     unsigned entryIdx = 1;
1493     // Find the first static IP address currently active on the NIC and
1494     // match it to the first JSON element in the IPv4StaticAddresses array.
1495     // Match each subsequent JSON element to the next static IP programmed
1496     // into the NIC.
1497     std::vector<IPv4AddressData>::const_iterator nicIpEntry =
1498         getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend());
1499 
1500     bool gatewayValueAssigned{};
1501     bool preserveGateway{};
1502     std::string activePath{};
1503     std::string activeGateway{};
1504     if (!ethData.defaultGateway.empty() && ethData.defaultGateway != "0.0.0.0")
1505     {
1506         // The NIC is already configured with a default gateway. Use this if
1507         // the leading entry in the PATCH is '{}', which is preserving an active
1508         // static address.
1509         activeGateway = ethData.defaultGateway;
1510         activePath = "IPv4StaticAddresses/1";
1511         gatewayValueAssigned = true;
1512     }
1513 
1514     for (std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson :
1515          input)
1516     {
1517         std::string pathString =
1518             "IPv4StaticAddresses/" + std::to_string(entryIdx);
1519         nlohmann::json::object_t* obj =
1520             std::get_if<nlohmann::json::object_t>(&thisJson);
1521         if (obj == nullptr)
1522         {
1523             if (nicIpEntry != ipv4Data.cend())
1524             {
1525                 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp);
1526                 nicIpEntry =
1527                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1528                 if (!preserveGateway && (nicIpEntry == ipv4Data.cend()))
1529                 {
1530                     // All entries have been processed, and this last has
1531                     // requested the IP address be deleted. No prior entry
1532                     // performed an action that created or modified a
1533                     // gateway. Deleting this IP address means the default
1534                     // gateway entry has to be removed as well.
1535                     updateIPv4DefaultGateway(ifaceId, "", asyncResp);
1536                 }
1537                 entryIdx++;
1538                 continue;
1539             }
1540             // Received a DELETE action on an entry not assigned to the NIC
1541             messages::resourceCannotBeDeleted(asyncResp->res);
1542             return;
1543         }
1544 
1545         // An Add/Modify action is requested
1546         if (!obj->empty())
1547         {
1548             std::optional<std::string> address;
1549             std::optional<std::string> subnetMask;
1550             std::optional<std::string> gateway;
1551 
1552             if (!json_util::readJsonObject(*obj, asyncResp->res, "Address",
1553                                            address, "SubnetMask", subnetMask,
1554                                            "Gateway", gateway))
1555             {
1556                 messages::propertyValueFormatError(asyncResp->res, *obj,
1557                                                    pathString);
1558                 return;
1559             }
1560 
1561             // Find the address/subnet/gateway values. Any values that are
1562             // not explicitly provided are assumed to be unmodified from the
1563             // current state of the interface. Merge existing state into the
1564             // current request.
1565             if (address)
1566             {
1567                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address))
1568                 {
1569                     messages::propertyValueFormatError(asyncResp->res, *address,
1570                                                        pathString + "/Address");
1571                     return;
1572                 }
1573             }
1574             else if (nicIpEntry != ipv4Data.cend())
1575             {
1576                 address = (nicIpEntry->address);
1577             }
1578             else
1579             {
1580                 messages::propertyMissing(asyncResp->res,
1581                                           pathString + "/Address");
1582                 return;
1583             }
1584 
1585             uint8_t prefixLength = 0;
1586             if (subnetMask)
1587             {
1588                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask,
1589                                                          &prefixLength))
1590                 {
1591                     messages::propertyValueFormatError(
1592                         asyncResp->res, *subnetMask,
1593                         pathString + "/SubnetMask");
1594                     return;
1595                 }
1596             }
1597             else if (nicIpEntry != ipv4Data.cend())
1598             {
1599                 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask,
1600                                                          &prefixLength))
1601                 {
1602                     messages::propertyValueFormatError(
1603                         asyncResp->res, nicIpEntry->netmask,
1604                         pathString + "/SubnetMask");
1605                     return;
1606                 }
1607             }
1608             else
1609             {
1610                 messages::propertyMissing(asyncResp->res,
1611                                           pathString + "/SubnetMask");
1612                 return;
1613             }
1614 
1615             if (gateway)
1616             {
1617                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway))
1618                 {
1619                     messages::propertyValueFormatError(asyncResp->res, *gateway,
1620                                                        pathString + "/Gateway");
1621                     return;
1622                 }
1623             }
1624             else if (nicIpEntry != ipv4Data.cend())
1625             {
1626                 gateway = nicIpEntry->gateway;
1627             }
1628             else
1629             {
1630                 messages::propertyMissing(asyncResp->res,
1631                                           pathString + "/Gateway");
1632                 return;
1633             }
1634 
1635             if (gatewayValueAssigned)
1636             {
1637                 if (activeGateway != gateway)
1638                 {
1639                     // A NIC can only have a single active gateway value.
1640                     // If any gateway in the array of static addresses
1641                     // mismatch the PATCH is in error.
1642                     std::string arg1 = pathString + "/Gateway";
1643                     std::string arg2 = activePath + "/Gateway";
1644                     messages::propertyValueConflict(asyncResp->res, arg1, arg2);
1645                     return;
1646                 }
1647             }
1648             else
1649             {
1650                 // Capture the very first gateway value from the incoming
1651                 // JSON record and use it at the default gateway.
1652                 updateIPv4DefaultGateway(ifaceId, *gateway, asyncResp);
1653                 activeGateway = *gateway;
1654                 activePath = pathString;
1655                 gatewayValueAssigned = true;
1656             }
1657 
1658             if (nicIpEntry != ipv4Data.cend())
1659             {
1660                 deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId,
1661                                          nicIpEntry->id, prefixLength, *address,
1662                                          *gateway, asyncResp);
1663                 nicIpEntry =
1664                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1665                 preserveGateway = true;
1666             }
1667             else
1668             {
1669                 createIPv4(ifaceId, prefixLength, *gateway, *address,
1670                            asyncResp);
1671                 preserveGateway = true;
1672             }
1673             entryIdx++;
1674         }
1675         else
1676         {
1677             // Received {}, do not modify this address
1678             if (nicIpEntry != ipv4Data.cend())
1679             {
1680                 nicIpEntry =
1681                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1682                 preserveGateway = true;
1683                 entryIdx++;
1684             }
1685             else
1686             {
1687                 // Requested a DO NOT MODIFY action on an entry not assigned
1688                 // to the NIC
1689                 messages::propertyValueFormatError(asyncResp->res, *obj,
1690                                                    pathString);
1691                 return;
1692             }
1693         }
1694     }
1695 }
1696 
1697 inline void handleStaticNameServersPatch(
1698     const std::string& ifaceId,
1699     const std::vector<std::string>& updatedStaticNameServers,
1700     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1701 {
1702     setDbusProperty(
1703         asyncResp, "StaticNameServers", "xyz.openbmc_project.Network",
1704         sdbusplus::message::object_path("/xyz/openbmc_project/network") /
1705             ifaceId,
1706         "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers",
1707         updatedStaticNameServers);
1708 }
1709 
1710 inline void handleIPv6StaticAddressesPatch(
1711     const std::string& ifaceId,
1712     std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input,
1713     const std::vector<IPv6AddressData>& ipv6Data,
1714     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1715 {
1716     size_t entryIdx = 1;
1717     std::vector<IPv6AddressData>::const_iterator nicIpEntry =
1718         getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend());
1719     for (std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson :
1720          input)
1721     {
1722         std::string pathString =
1723             "IPv6StaticAddresses/" + std::to_string(entryIdx);
1724         nlohmann::json::object_t* obj =
1725             std::get_if<nlohmann::json::object_t>(&thisJson);
1726         if (obj != nullptr && !obj->empty())
1727         {
1728             std::optional<std::string> address;
1729             std::optional<uint8_t> prefixLength;
1730             nlohmann::json::object_t thisJsonCopy = *obj;
1731             if (!json_util::readJsonObject(thisJsonCopy, asyncResp->res,
1732                                            "Address", address, "PrefixLength",
1733                                            prefixLength))
1734             {
1735                 messages::propertyValueFormatError(asyncResp->res, thisJsonCopy,
1736                                                    pathString);
1737                 return;
1738             }
1739 
1740             // Find the address and prefixLength values. Any values that are
1741             // not explicitly provided are assumed to be unmodified from the
1742             // current state of the interface. Merge existing state into the
1743             // current request.
1744             if (!address)
1745             {
1746                 if (nicIpEntry == ipv6Data.end())
1747                 {
1748                     messages::propertyMissing(asyncResp->res,
1749                                               pathString + "/Address");
1750                     return;
1751                 }
1752                 address = nicIpEntry->address;
1753             }
1754 
1755             if (!prefixLength)
1756             {
1757                 if (nicIpEntry == ipv6Data.end())
1758                 {
1759                     messages::propertyMissing(asyncResp->res,
1760                                               pathString + "/PrefixLength");
1761                     return;
1762                 }
1763                 prefixLength = nicIpEntry->prefixLength;
1764             }
1765 
1766             if (nicIpEntry != ipv6Data.end())
1767             {
1768                 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId,
1769                                          nicIpEntry->id, *prefixLength,
1770                                          *address, "", asyncResp);
1771                 nicIpEntry =
1772                     getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1773             }
1774             else
1775             {
1776                 createIPv6(ifaceId, *prefixLength, *address, asyncResp);
1777             }
1778             entryIdx++;
1779         }
1780         else
1781         {
1782             if (nicIpEntry == ipv6Data.end())
1783             {
1784                 // Requesting a DELETE/DO NOT MODIFY action for an item
1785                 // that isn't present on the eth(n) interface. Input JSON is
1786                 // in error, so bail out.
1787                 if (obj == nullptr)
1788                 {
1789                     messages::resourceCannotBeDeleted(asyncResp->res);
1790                     return;
1791                 }
1792                 messages::propertyValueFormatError(asyncResp->res, *obj,
1793                                                    pathString);
1794                 return;
1795             }
1796 
1797             if (obj == nullptr)
1798             {
1799                 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp);
1800             }
1801             if (nicIpEntry != ipv6Data.cend())
1802             {
1803                 nicIpEntry =
1804                     getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1805             }
1806             entryIdx++;
1807         }
1808     }
1809 }
1810 
1811 inline std::string extractParentInterfaceName(const std::string& ifaceId)
1812 {
1813     std::size_t pos = ifaceId.find('_');
1814     return ifaceId.substr(0, pos);
1815 }
1816 
1817 inline void parseInterfaceData(
1818     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1819     const std::string& ifaceId, const EthernetInterfaceData& ethData,
1820     const std::vector<IPv4AddressData>& ipv4Data,
1821     const std::vector<IPv6AddressData>& ipv6Data,
1822     const std::vector<StaticGatewayData>& ipv6GatewayData)
1823 {
1824     nlohmann::json& jsonResponse = asyncResp->res.jsonValue;
1825     jsonResponse["Id"] = ifaceId;
1826     jsonResponse["@odata.id"] =
1827         boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}",
1828                             BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceId);
1829     jsonResponse["InterfaceEnabled"] = ethData.nicEnabled;
1830 
1831     if (ethData.nicEnabled)
1832     {
1833         jsonResponse["LinkStatus"] =
1834             ethData.linkUp ? ethernet_interface::LinkStatus::LinkUp
1835                            : ethernet_interface::LinkStatus::LinkDown;
1836         jsonResponse["Status"]["State"] = resource::State::Enabled;
1837     }
1838     else
1839     {
1840         jsonResponse["LinkStatus"] = ethernet_interface::LinkStatus::NoLink;
1841         jsonResponse["Status"]["State"] = resource::State::Disabled;
1842     }
1843 
1844     jsonResponse["SpeedMbps"] = ethData.speed;
1845     jsonResponse["MTUSize"] = ethData.mtuSize;
1846     if (ethData.macAddress)
1847     {
1848         jsonResponse["MACAddress"] = *ethData.macAddress;
1849     }
1850     jsonResponse["DHCPv4"]["DHCPEnabled"] =
1851         translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1852     jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled;
1853     jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled;
1854     jsonResponse["DHCPv4"]["UseDomainName"] = ethData.domainv4Enabled;
1855     jsonResponse["DHCPv6"]["OperatingMode"] =
1856         translateDhcpEnabledToBool(ethData.dhcpEnabled, false)
1857             ? "Enabled"
1858             : "Disabled";
1859     jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled;
1860     jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled;
1861     jsonResponse["DHCPv6"]["UseDomainName"] = ethData.domainv6Enabled;
1862     jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] =
1863         ethData.ipv6AcceptRa;
1864 
1865     if (!ethData.hostName.empty())
1866     {
1867         jsonResponse["HostName"] = ethData.hostName;
1868 
1869         // When domain name is empty then it means, that it is a network
1870         // without domain names, and the host name itself must be treated as
1871         // FQDN
1872         std::string fqdn = ethData.hostName;
1873         if (!ethData.domainnames.empty())
1874         {
1875             fqdn += "." + ethData.domainnames[0];
1876         }
1877         jsonResponse["FQDN"] = fqdn;
1878     }
1879 
1880     if (ethData.vlanId)
1881     {
1882         jsonResponse["EthernetInterfaceType"] =
1883             ethernet_interface::EthernetDeviceType::Virtual;
1884         jsonResponse["VLAN"]["VLANEnable"] = true;
1885         jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId;
1886         jsonResponse["VLAN"]["Tagged"] = true;
1887 
1888         nlohmann::json::array_t relatedInterfaces;
1889         nlohmann::json& parentInterface = relatedInterfaces.emplace_back();
1890         parentInterface["@odata.id"] =
1891             boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces",
1892                                 BMCWEB_REDFISH_MANAGER_URI_NAME,
1893                                 extractParentInterfaceName(ifaceId));
1894         jsonResponse["Links"]["RelatedInterfaces"] =
1895             std::move(relatedInterfaces);
1896     }
1897     else
1898     {
1899         jsonResponse["EthernetInterfaceType"] =
1900             ethernet_interface::EthernetDeviceType::Physical;
1901     }
1902 
1903     jsonResponse["NameServers"] = ethData.nameServers;
1904     jsonResponse["StaticNameServers"] = ethData.staticNameServers;
1905 
1906     nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"];
1907     nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"];
1908     ipv4Array = nlohmann::json::array();
1909     ipv4StaticArray = nlohmann::json::array();
1910     for (const auto& ipv4Config : ipv4Data)
1911     {
1912         std::string gatewayStr = ipv4Config.gateway;
1913         if (gatewayStr.empty())
1914         {
1915             gatewayStr = "0.0.0.0";
1916         }
1917         nlohmann::json::object_t ipv4;
1918         ipv4["AddressOrigin"] = ipv4Config.origin;
1919         ipv4["SubnetMask"] = ipv4Config.netmask;
1920         ipv4["Address"] = ipv4Config.address;
1921         ipv4["Gateway"] = gatewayStr;
1922 
1923         if (ipv4Config.origin == "Static")
1924         {
1925             ipv4StaticArray.push_back(ipv4);
1926         }
1927 
1928         ipv4Array.emplace_back(std::move(ipv4));
1929     }
1930 
1931     std::string ipv6GatewayStr = ethData.ipv6DefaultGateway;
1932     if (ipv6GatewayStr.empty())
1933     {
1934         ipv6GatewayStr = "0:0:0:0:0:0:0:0";
1935     }
1936 
1937     jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr;
1938 
1939     nlohmann::json::array_t ipv6StaticGatewayArray;
1940     for (const auto& ipv6GatewayConfig : ipv6GatewayData)
1941     {
1942         nlohmann::json::object_t ipv6Gateway;
1943         ipv6Gateway["Address"] = ipv6GatewayConfig.gateway;
1944         ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway));
1945     }
1946     jsonResponse["IPv6StaticDefaultGateways"] =
1947         std::move(ipv6StaticGatewayArray);
1948 
1949     nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"];
1950     nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"];
1951     ipv6Array = nlohmann::json::array();
1952     ipv6StaticArray = nlohmann::json::array();
1953     nlohmann::json& ipv6AddrPolicyTable =
1954         jsonResponse["IPv6AddressPolicyTable"];
1955     ipv6AddrPolicyTable = nlohmann::json::array();
1956     for (const auto& ipv6Config : ipv6Data)
1957     {
1958         nlohmann::json::object_t ipv6;
1959         ipv6["Address"] = ipv6Config.address;
1960         ipv6["PrefixLength"] = ipv6Config.prefixLength;
1961         ipv6["AddressOrigin"] = ipv6Config.origin;
1962 
1963         ipv6Array.emplace_back(std::move(ipv6));
1964         if (ipv6Config.origin == "Static")
1965         {
1966             nlohmann::json::object_t ipv6Static;
1967             ipv6Static["Address"] = ipv6Config.address;
1968             ipv6Static["PrefixLength"] = ipv6Config.prefixLength;
1969             ipv6StaticArray.emplace_back(std::move(ipv6Static));
1970         }
1971     }
1972 }
1973 
1974 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1975                         const std::string& ifaceId,
1976                         const boost::system::error_code& ec,
1977                         const sdbusplus::message_t& m)
1978 {
1979     if (!ec)
1980     {
1981         return;
1982     }
1983     const sd_bus_error* dbusError = m.get_error();
1984     if (dbusError == nullptr)
1985     {
1986         messages::internalError(asyncResp->res);
1987         return;
1988     }
1989     BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
1990 
1991     if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") ==
1992         dbusError->name)
1993     {
1994         messages::resourceNotFound(asyncResp->res, "EthernetInterface",
1995                                    ifaceId);
1996         return;
1997     }
1998     if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") ==
1999         dbusError->name)
2000     {
2001         messages::resourceCannotBeDeleted(asyncResp->res);
2002         return;
2003     }
2004     messages::internalError(asyncResp->res);
2005 }
2006 
2007 inline void afterVlanCreate(
2008     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2009     const std::string& parentInterfaceUri, const std::string& vlanInterface,
2010     const boost::system::error_code& ec, const sdbusplus::message_t& m
2011 
2012 )
2013 {
2014     if (ec)
2015     {
2016         const sd_bus_error* dbusError = m.get_error();
2017         if (dbusError == nullptr)
2018         {
2019             messages::internalError(asyncResp->res);
2020             return;
2021         }
2022         BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
2023 
2024         if (std::string_view(
2025                 "xyz.openbmc_project.Common.Error.ResourceNotFound") ==
2026             dbusError->name)
2027         {
2028             messages::propertyValueNotInList(
2029                 asyncResp->res, parentInterfaceUri,
2030                 "Links/RelatedInterfaces/0/@odata.id");
2031             return;
2032         }
2033         if (std::string_view(
2034                 "xyz.openbmc_project.Common.Error.InvalidArgument") ==
2035             dbusError->name)
2036         {
2037             messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface",
2038                                             "Id", vlanInterface);
2039             return;
2040         }
2041         messages::internalError(asyncResp->res);
2042         return;
2043     }
2044 
2045     const boost::urls::url vlanInterfaceUri =
2046         boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}",
2047                             BMCWEB_REDFISH_MANAGER_URI_NAME, vlanInterface);
2048     asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer());
2049 }
2050 
2051 inline void requestEthernetInterfacesRoutes(App& app)
2052 {
2053     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/")
2054         .privileges(redfish::privileges::getEthernetInterfaceCollection)
2055         .methods(boost::beast::http::verb::get)(
2056             [&app](const crow::Request& req,
2057                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2058                    const std::string& managerId) {
2059                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2060                 {
2061                     return;
2062                 }
2063 
2064                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2065                 {
2066                     messages::resourceNotFound(asyncResp->res, "Manager",
2067                                                managerId);
2068                     return;
2069                 }
2070 
2071                 asyncResp->res.jsonValue["@odata.type"] =
2072                     "#EthernetInterfaceCollection.EthernetInterfaceCollection";
2073                 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2074                     "/redfish/v1/Managers/{}/EthernetInterfaces",
2075                     BMCWEB_REDFISH_MANAGER_URI_NAME);
2076                 asyncResp->res.jsonValue["Name"] =
2077                     "Ethernet Network Interface Collection";
2078                 asyncResp->res.jsonValue["Description"] =
2079                     "Collection of EthernetInterfaces for this Manager";
2080 
2081                 // Get eth interface list, and call the below callback for JSON
2082                 // preparation
2083                 getEthernetIfaceList(
2084                     [asyncResp](const bool& success,
2085                                 const std::vector<std::string>& ifaceList) {
2086                         if (!success)
2087                         {
2088                             messages::internalError(asyncResp->res);
2089                             return;
2090                         }
2091 
2092                         nlohmann::json& ifaceArray =
2093                             asyncResp->res.jsonValue["Members"];
2094                         ifaceArray = nlohmann::json::array();
2095                         for (const std::string& ifaceItem : ifaceList)
2096                         {
2097                             nlohmann::json::object_t iface;
2098                             iface["@odata.id"] = boost::urls::format(
2099                                 "/redfish/v1/Managers/{}/EthernetInterfaces/{}",
2100                                 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceItem);
2101                             ifaceArray.push_back(std::move(iface));
2102                         }
2103 
2104                         asyncResp->res.jsonValue["Members@odata.count"] =
2105                             ifaceArray.size();
2106                         asyncResp->res.jsonValue["@odata.id"] =
2107                             boost::urls::format(
2108                                 "/redfish/v1/Managers/{}/EthernetInterfaces",
2109                                 BMCWEB_REDFISH_MANAGER_URI_NAME);
2110                     });
2111             });
2112 
2113     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/")
2114         .privileges(redfish::privileges::postEthernetInterfaceCollection)
2115         .methods(boost::beast::http::verb::post)(
2116             [&app](const crow::Request& req,
2117                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2118                    const std::string& managerId) {
2119                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2120                 {
2121                     return;
2122                 }
2123 
2124                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2125                 {
2126                     messages::resourceNotFound(asyncResp->res, "Manager",
2127                                                managerId);
2128                     return;
2129                 }
2130 
2131                 bool vlanEnable = false;
2132                 uint32_t vlanId = 0;
2133                 std::vector<nlohmann::json::object_t> relatedInterfaces;
2134 
2135                 if (!json_util::readJsonPatch(
2136                         req, asyncResp->res, "VLAN/VLANEnable", vlanEnable,
2137                         "VLAN/VLANId", vlanId, "Links/RelatedInterfaces",
2138                         relatedInterfaces))
2139                 {
2140                     return;
2141                 }
2142 
2143                 if (relatedInterfaces.size() != 1)
2144                 {
2145                     messages::arraySizeTooLong(asyncResp->res,
2146                                                "Links/RelatedInterfaces",
2147                                                relatedInterfaces.size());
2148                     return;
2149                 }
2150 
2151                 std::string parentInterfaceUri;
2152                 if (!json_util::readJsonObject(relatedInterfaces[0],
2153                                                asyncResp->res, "@odata.id",
2154                                                parentInterfaceUri))
2155                 {
2156                     messages::propertyMissing(
2157                         asyncResp->res, "Links/RelatedInterfaces/0/@odata.id");
2158                     return;
2159                 }
2160                 BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri);
2161 
2162                 boost::system::result<boost::urls::url_view> parsedUri =
2163                     boost::urls::parse_relative_ref(parentInterfaceUri);
2164                 if (!parsedUri)
2165                 {
2166                     messages::propertyValueFormatError(
2167                         asyncResp->res, parentInterfaceUri,
2168                         "Links/RelatedInterfaces/0/@odata.id");
2169                     return;
2170                 }
2171 
2172                 std::string parentInterface;
2173                 if (!crow::utility::readUrlSegments(
2174                         *parsedUri, "redfish", "v1", "Managers", "bmc",
2175                         "EthernetInterfaces", std::ref(parentInterface)))
2176                 {
2177                     messages::propertyValueNotInList(
2178                         asyncResp->res, parentInterfaceUri,
2179                         "Links/RelatedInterfaces/0/@odata.id");
2180                     return;
2181                 }
2182 
2183                 if (!vlanEnable)
2184                 {
2185                     // In OpenBMC implementation, VLANEnable cannot be false on
2186                     // create
2187                     messages::propertyValueIncorrect(
2188                         asyncResp->res, "VLAN/VLANEnable", "false");
2189                     return;
2190                 }
2191 
2192                 std::string vlanInterface =
2193                     parentInterface + "_" + std::to_string(vlanId);
2194                 crow::connections::systemBus->async_method_call(
2195                     [asyncResp, parentInterfaceUri,
2196                      vlanInterface](const boost::system::error_code& ec,
2197                                     const sdbusplus::message_t& m) {
2198                         afterVlanCreate(asyncResp, parentInterfaceUri,
2199                                         vlanInterface, ec, m);
2200                     },
2201                     "xyz.openbmc_project.Network",
2202                     "/xyz/openbmc_project/network",
2203                     "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
2204                     parentInterface, vlanId);
2205             });
2206 
2207     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/")
2208         .privileges(redfish::privileges::getEthernetInterface)
2209         .methods(boost::beast::http::verb::get)(
2210             [&app](const crow::Request& req,
2211                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2212                    const std::string& managerId, const std::string& ifaceId) {
2213                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2214                 {
2215                     return;
2216                 }
2217 
2218                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2219                 {
2220                     messages::resourceNotFound(asyncResp->res, "Manager",
2221                                                managerId);
2222                     return;
2223                 }
2224 
2225                 getEthernetIfaceData(
2226                     ifaceId,
2227                     [asyncResp, ifaceId](
2228                         const bool& success,
2229                         const EthernetInterfaceData& ethData,
2230                         const std::vector<IPv4AddressData>& ipv4Data,
2231                         const std::vector<IPv6AddressData>& ipv6Data,
2232                         const std::vector<StaticGatewayData>& ipv6GatewayData) {
2233                         if (!success)
2234                         {
2235                             // TODO(Pawel)consider distinguish between non
2236                             // existing object, and other errors
2237                             messages::resourceNotFound(
2238                                 asyncResp->res, "EthernetInterface", ifaceId);
2239                             return;
2240                         }
2241 
2242                         asyncResp->res.jsonValue["@odata.type"] =
2243                             "#EthernetInterface.v1_9_0.EthernetInterface";
2244                         asyncResp->res.jsonValue["Name"] =
2245                             "Manager Ethernet Interface";
2246                         asyncResp->res.jsonValue["Description"] =
2247                             "Management Network Interface";
2248 
2249                         parseInterfaceData(asyncResp, ifaceId, ethData,
2250                                            ipv4Data, ipv6Data, ipv6GatewayData);
2251                     });
2252             });
2253 
2254     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/")
2255         .privileges(redfish::privileges::patchEthernetInterface)
2256         .methods(boost::beast::http::verb::patch)(
2257             [&app](const crow::Request& req,
2258                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2259                    const std::string& managerId, const std::string& ifaceId) {
2260                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2261                 {
2262                     return;
2263                 }
2264 
2265                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2266                 {
2267                     messages::resourceNotFound(asyncResp->res, "Manager",
2268                                                managerId);
2269                     return;
2270                 }
2271 
2272                 std::optional<std::string> hostname;
2273                 std::optional<std::string> fqdn;
2274                 std::optional<std::string> macAddress;
2275                 std::optional<std::string> ipv6DefaultGateway;
2276                 std::optional<std::vector<
2277                     std::variant<nlohmann::json::object_t, std::nullptr_t>>>
2278                     ipv4StaticAddresses;
2279                 std::optional<std::vector<
2280                     std::variant<nlohmann::json::object_t, std::nullptr_t>>>
2281                     ipv6StaticAddresses;
2282                 std::optional<std::vector<
2283                     std::variant<nlohmann::json::object_t, std::nullptr_t>>>
2284                     ipv6StaticDefaultGateways;
2285                 std::optional<std::vector<std::string>> staticNameServers;
2286                 std::optional<bool> ipv6AutoConfigEnabled;
2287                 std::optional<bool> interfaceEnabled;
2288                 std::optional<size_t> mtuSize;
2289                 DHCPParameters v4dhcpParms;
2290                 DHCPParameters v6dhcpParms;
2291                 // clang-format off
2292         if (!json_util::readJsonPatch(req, asyncResp->res,
2293                 "DHCPv4/DHCPEnabled",   v4dhcpParms.dhcpv4Enabled,
2294                 "DHCPv4/UseDNSServers", v4dhcpParms.useDnsServers,
2295                 "DHCPv4/UseDomainName", v4dhcpParms.useDomainName,
2296                 "DHCPv4/UseNTPServers", v4dhcpParms.useNtpServers,
2297                 "DHCPv6/OperatingMode", v6dhcpParms.dhcpv6OperatingMode,
2298                 "DHCPv6/UseDNSServers", v6dhcpParms.useDnsServers,
2299                 "DHCPv6/UseDomainName", v6dhcpParms.useDomainName,
2300                 "DHCPv6/UseNTPServers", v6dhcpParms.useNtpServers,
2301                 "FQDN", fqdn,
2302                 "HostName", hostname,
2303                 "IPv4StaticAddresses", ipv4StaticAddresses,
2304                 "IPv6DefaultGateway", ipv6DefaultGateway,
2305                 "IPv6StaticAddresses", ipv6StaticAddresses,
2306                 "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways,
2307                 "InterfaceEnabled", interfaceEnabled,
2308                 "MACAddress", macAddress,
2309                 "MTUSize", mtuSize,
2310                 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled,
2311                 "StaticNameServers", staticNameServers
2312                 )
2313             )
2314         {
2315             return;
2316         }
2317                 // clang-format on
2318 
2319                 // Get single eth interface data, and call the below callback
2320                 // for JSON preparation
2321                 getEthernetIfaceData(
2322                     ifaceId,
2323                     [asyncResp, ifaceId, hostname = std::move(hostname),
2324                      fqdn = std::move(fqdn), macAddress = std::move(macAddress),
2325                      ipv4StaticAddresses = std::move(ipv4StaticAddresses),
2326                      ipv6DefaultGateway = std::move(ipv6DefaultGateway),
2327                      ipv6StaticAddresses = std::move(ipv6StaticAddresses),
2328                      ipv6StaticDefaultGateway =
2329                          std::move(ipv6StaticDefaultGateways),
2330                      staticNameServers = std::move(staticNameServers), mtuSize,
2331                      ipv6AutoConfigEnabled,
2332                      v4dhcpParms = std::move(v4dhcpParms),
2333                      v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled](
2334                         const bool success,
2335                         const EthernetInterfaceData& ethData,
2336                         const std::vector<IPv4AddressData>& ipv4Data,
2337                         const std::vector<IPv6AddressData>& ipv6Data,
2338                         const std::vector<StaticGatewayData>&
2339                             ipv6GatewayData) mutable {
2340                         if (!success)
2341                         {
2342                             // ... otherwise return error
2343                             // TODO(Pawel)consider distinguish between non
2344                             // existing object, and other errors
2345                             messages::resourceNotFound(
2346                                 asyncResp->res, "EthernetInterface", ifaceId);
2347                             return;
2348                         }
2349 
2350                         handleDHCPPatch(ifaceId, ethData, v4dhcpParms,
2351                                         v6dhcpParms, asyncResp);
2352 
2353                         if (hostname)
2354                         {
2355                             handleHostnamePatch(*hostname, asyncResp);
2356                         }
2357 
2358                         if (ipv6AutoConfigEnabled)
2359                         {
2360                             handleSLAACAutoConfigPatch(
2361                                 ifaceId, *ipv6AutoConfigEnabled, asyncResp);
2362                         }
2363 
2364                         if (fqdn)
2365                         {
2366                             handleFqdnPatch(ifaceId, *fqdn, asyncResp);
2367                         }
2368 
2369                         if (macAddress)
2370                         {
2371                             handleMACAddressPatch(ifaceId, *macAddress,
2372                                                   asyncResp);
2373                         }
2374 
2375                         if (ipv4StaticAddresses)
2376                         {
2377                             handleIPv4StaticPatch(ifaceId, *ipv4StaticAddresses,
2378                                                   ethData, ipv4Data, asyncResp);
2379                         }
2380 
2381                         if (staticNameServers)
2382                         {
2383                             handleStaticNameServersPatch(
2384                                 ifaceId, *staticNameServers, asyncResp);
2385                         }
2386 
2387                         if (ipv6DefaultGateway)
2388                         {
2389                             messages::propertyNotWritable(asyncResp->res,
2390                                                           "IPv6DefaultGateway");
2391                         }
2392 
2393                         if (ipv6StaticAddresses)
2394                         {
2395                             handleIPv6StaticAddressesPatch(ifaceId,
2396                                                            *ipv6StaticAddresses,
2397                                                            ipv6Data, asyncResp);
2398                         }
2399 
2400                         if (ipv6StaticDefaultGateway)
2401                         {
2402                             handleIPv6DefaultGateway(
2403                                 ifaceId, *ipv6StaticDefaultGateway,
2404                                 ipv6GatewayData, asyncResp);
2405                         }
2406 
2407                         if (interfaceEnabled)
2408                         {
2409                             setDbusProperty(
2410                                 asyncResp, "InterfaceEnabled",
2411                                 "xyz.openbmc_project.Network",
2412                                 sdbusplus::message::object_path(
2413                                     "/xyz/openbmc_project/network") /
2414                                     ifaceId,
2415                                 "xyz.openbmc_project.Network.EthernetInterface",
2416                                 "NICEnabled", *interfaceEnabled);
2417                         }
2418 
2419                         if (mtuSize)
2420                         {
2421                             handleMTUSizePatch(ifaceId, *mtuSize, asyncResp);
2422                         }
2423                     });
2424             });
2425 
2426     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/")
2427         .privileges(redfish::privileges::deleteEthernetInterface)
2428         .methods(boost::beast::http::verb::delete_)(
2429             [&app](const crow::Request& req,
2430                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2431                    const std::string& managerId, const std::string& ifaceId) {
2432                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2433                 {
2434                     return;
2435                 }
2436 
2437                 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2438                 {
2439                     messages::resourceNotFound(asyncResp->res, "Manager",
2440                                                managerId);
2441                     return;
2442                 }
2443 
2444                 crow::connections::systemBus->async_method_call(
2445                     [asyncResp, ifaceId](const boost::system::error_code& ec,
2446                                          const sdbusplus::message_t& m) {
2447                         afterDelete(asyncResp, ifaceId, ec, m);
2448                     },
2449                     "xyz.openbmc_project.Network",
2450                     std::string("/xyz/openbmc_project/network/") + ifaceId,
2451                     "xyz.openbmc_project.Object.Delete", "Delete");
2452             });
2453 }
2454 
2455 } // namespace redfish
2456