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