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