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