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