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