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