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