xref: /openbmc/bmcweb/redfish-core/lib/ethernet.hpp (revision b6164cbec4dd7f5c4e6e7667b203874e11cd8b3c)
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     sdbusplus::asio::setProperty(
733         *crow::connections::systemBus, "xyz.openbmc_project.Network",
734         "/xyz/openbmc_project/network/" + ifaceId,
735         "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway",
736         gateway, [asyncResp](const boost::system::error_code& ec) {
737         if (ec)
738         {
739             messages::internalError(asyncResp->res);
740             return;
741         }
742         asyncResp->res.result(boost::beast::http::status::no_content);
743     });
744 }
745 /**
746  * @brief Creates a static IPv4 entry
747  *
748  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
749  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
750  * @param[in] gateway      IPv4 address of this interfaces gateway
751  * @param[in] address      IPv4 address to assign to this interface
752  * @param[io] asyncResp    Response object that will be returned to client
753  *
754  * @return None
755  */
756 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength,
757                        const std::string& gateway, const std::string& address,
758                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
759 {
760     auto createIpHandler = [asyncResp, ifaceId,
761                             gateway](const boost::system::error_code& ec) {
762         if (ec)
763         {
764             messages::internalError(asyncResp->res);
765             return;
766         }
767         updateIPv4DefaultGateway(ifaceId, gateway, asyncResp);
768     };
769 
770     crow::connections::systemBus->async_method_call(
771         std::move(createIpHandler), "xyz.openbmc_project.Network",
772         "/xyz/openbmc_project/network/" + ifaceId,
773         "xyz.openbmc_project.Network.IP.Create", "IP",
774         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength,
775         gateway);
776 }
777 
778 /**
779  * @brief Deletes the IPv6 entry for this interface and creates a replacement
780  * static IPv6 entry
781  *
782  * @param[in] ifaceId      Id of interface upon which to create the IPv6 entry
783  * @param[in] id           The unique hash entry identifying the DBus entry
784  * @param[in] prefixLength IPv6 prefix syntax for the subnet mask
785  * @param[in] address      IPv6 address to assign to this interface
786  * @param[io] asyncResp    Response object that will be returned to client
787  *
788  * @return None
789  */
790 
791 enum class IpVersion
792 {
793     IpV4,
794     IpV6
795 };
796 
797 inline void deleteAndCreateIPAddress(
798     IpVersion version, const std::string& ifaceId, const std::string& id,
799     uint8_t prefixLength, const std::string& address,
800     const std::string& gateway,
801     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
802 {
803     crow::connections::systemBus->async_method_call(
804         [asyncResp, version, ifaceId, address, prefixLength,
805          gateway](const boost::system::error_code& ec) {
806         if (ec)
807         {
808             messages::internalError(asyncResp->res);
809         }
810         std::string protocol = "xyz.openbmc_project.Network.IP.Protocol.";
811         protocol += version == IpVersion::IpV4 ? "IPv4" : "IPv6";
812         crow::connections::systemBus->async_method_call(
813             [asyncResp](const boost::system::error_code& ec2) {
814             if (ec2)
815             {
816                 messages::internalError(asyncResp->res);
817             }
818         },
819             "xyz.openbmc_project.Network",
820             "/xyz/openbmc_project/network/" + ifaceId,
821             "xyz.openbmc_project.Network.IP.Create", "IP", protocol, address,
822             prefixLength, gateway);
823     },
824         "xyz.openbmc_project.Network",
825         "/xyz/openbmc_project/network/" + ifaceId + id,
826         "xyz.openbmc_project.Object.Delete", "Delete");
827 }
828 
829 inline bool extractIPv6DefaultGatewayData(
830     const std::string& ethifaceId,
831     const dbus::utility::ManagedObjectType& dbusData,
832     std::vector<StaticGatewayData>& staticGatewayConfig)
833 {
834     std::string staticGatewayPathStart("/xyz/openbmc_project/network/");
835     staticGatewayPathStart += ethifaceId;
836 
837     for (const auto& objpath : dbusData)
838     {
839         if (!std::string_view(objpath.first.str)
840                  .starts_with(staticGatewayPathStart))
841         {
842             continue;
843         }
844         for (const auto& interface : objpath.second)
845         {
846             if (interface.first != "xyz.openbmc_project.Network.StaticGateway")
847             {
848                 continue;
849             }
850             StaticGatewayData& staticGateway =
851                 staticGatewayConfig.emplace_back();
852             staticGateway.id = objpath.first.filename();
853 
854             bool success = sdbusplus::unpackPropertiesNoThrow(
855                 redfish::dbus_utils::UnpackErrorPrinter(), interface.second,
856                 "Gateway", staticGateway.gateway, "PrefixLength",
857                 staticGateway.prefixLength, "ProtocolType",
858                 staticGateway.protocol);
859             if (!success)
860             {
861                 return false;
862             }
863         }
864     }
865     return true;
866 }
867 
868 /**
869  * @brief Creates IPv6 with given data
870  *
871  * @param[in] ifaceId      Id of interface whose IP should be added
872  * @param[in] prefixLength Prefix length that needs to be added
873  * @param[in] address      IP address that needs to be added
874  * @param[io] asyncResp    Response object that will be returned to client
875  *
876  * @return None
877  */
878 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength,
879                        const std::string& address,
880                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
881 {
882     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
883     path /= ifaceId;
884 
885     auto createIpHandler = [asyncResp,
886                             address](const boost::system::error_code& ec) {
887         if (ec)
888         {
889             if (ec == boost::system::errc::io_error)
890             {
891                 messages::propertyValueFormatError(asyncResp->res, address,
892                                                    "Address");
893             }
894             else
895             {
896                 messages::internalError(asyncResp->res);
897             }
898         }
899     };
900     // Passing null for gateway, as per redfish spec IPv6StaticAddresses
901     // object does not have associated gateway property
902     crow::connections::systemBus->async_method_call(
903         std::move(createIpHandler), "xyz.openbmc_project.Network", path,
904         "xyz.openbmc_project.Network.IP.Create", "IP",
905         "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength,
906         "");
907 }
908 
909 /**
910  * @brief Deletes given IPv6 Static Gateway
911  *
912  * @param[in] ifaceId     Id of interface whose IP should be deleted
913  * @param[in] ipHash      DBus Hash id of IP that should be deleted
914  * @param[io] asyncResp   Response object that will be returned to client
915  *
916  * @return None
917  */
918 inline void
919     deleteIPv6Gateway(std::string_view gatewayId,
920                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
921 {
922     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
923     path /= gatewayId;
924     crow::connections::systemBus->async_method_call(
925         [asyncResp](const boost::system::error_code& ec) {
926         if (ec)
927         {
928             messages::internalError(asyncResp->res);
929         }
930     },
931         "xyz.openbmc_project.Network", path,
932         "xyz.openbmc_project.Object.Delete", "Delete");
933 }
934 
935 /**
936  * @brief Creates IPv6 static default gateway with given data
937  *
938  * @param[in] ifaceId      Id of interface whose IP should be added
939  * @param[in] prefixLength Prefix length that needs to be added
940  * @param[in] gateway      Gateway address that needs to be added
941  * @param[io] asyncResp    Response object that will be returned to client
942  *
943  * @return None
944  */
945 inline void createIPv6DefaultGateway(
946     std::string_view ifaceId, size_t prefixLength, std::string_view gateway,
947     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
948 {
949     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
950     path /= ifaceId;
951     auto createIpHandler = [asyncResp](const boost::system::error_code& ec) {
952         if (ec)
953         {
954             messages::internalError(asyncResp->res);
955         }
956     };
957     crow::connections::systemBus->async_method_call(
958         std::move(createIpHandler), "xyz.openbmc_project.Network", path,
959         "xyz.openbmc_project.Network.StaticGateway.Create", "StaticGateway",
960         gateway, prefixLength, "xyz.openbmc_project.Network.IP.Protocol.IPv6");
961 }
962 
963 /**
964  * @brief Deletes the IPv6 default gateway entry for this interface and
965  * creates a replacement IPv6 default gateway entry
966  *
967  * @param[in] ifaceId      Id of interface upon which to create the IPv6
968  * entry
969  * @param[in] gateway      IPv6 gateway to assign to this interface
970  * @param[in] prefixLength IPv6 prefix syntax for the subnet mask
971  * @param[io] asyncResp    Response object that will be returned to client
972  *
973  * @return None
974  */
975 inline void deleteAndCreateIPv6DefaultGateway(
976     std::string_view ifaceId, std::string_view gatewayId,
977     std::string_view gateway, size_t prefixLength,
978     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
979 {
980     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
981     path /= gatewayId;
982     crow::connections::systemBus->async_method_call(
983         [asyncResp, ifaceId, gateway,
984          prefixLength](const boost::system::error_code& ec) {
985         if (ec)
986         {
987             messages::internalError(asyncResp->res);
988             return;
989         }
990         createIPv6DefaultGateway(ifaceId, prefixLength, gateway, asyncResp);
991     },
992         "xyz.openbmc_project.Network", path,
993         "xyz.openbmc_project.Object.Delete", "Delete");
994 }
995 
996 /**
997  * @brief Sets IPv6 default gateway with given data
998  *
999  * @param[in] ifaceId      Id of interface whose gateway should be added
1000  * @param[in] input        Contains address that needs to be added
1001  * @param[in] staticGatewayData  Current static gateways in the system
1002  * @param[io] asyncResp    Response object that will be returned to client
1003  *
1004  * @return None
1005  */
1006 
1007 inline void handleIPv6DefaultGateway(
1008     const std::string& ifaceId, const nlohmann::json::array_t& input,
1009     const std::vector<StaticGatewayData>& staticGatewayData,
1010     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1011 {
1012     size_t entryIdx = 1;
1013     std::vector<StaticGatewayData>::const_iterator staticGatewayEntry =
1014         staticGatewayData.begin();
1015 
1016     for (const nlohmann::json& thisJson : input)
1017     {
1018         // find the next gateway entry
1019         while (staticGatewayEntry != staticGatewayData.end())
1020         {
1021             if (staticGatewayEntry->protocol ==
1022                 "xyz.openbmc_project.Network.IP.Protocol.IPv6")
1023             {
1024                 break;
1025             }
1026             staticGatewayEntry++;
1027         }
1028         std::string pathString = "IPv6StaticDefaultGateways/" +
1029                                  std::to_string(entryIdx);
1030         if (thisJson.is_null())
1031         {
1032             if (staticGatewayEntry == staticGatewayData.end())
1033             {
1034                 messages::resourceCannotBeDeleted(asyncResp->res);
1035                 return;
1036             }
1037             deleteIPv6Gateway(staticGatewayEntry->id, asyncResp);
1038             return;
1039         }
1040         if (thisJson.is_object() && thisJson.empty())
1041         {
1042             // Do nothing, but make sure the entry exists.
1043             if (staticGatewayEntry == staticGatewayData.end())
1044             {
1045                 messages::propertyValueFormatError(asyncResp->res, thisJson,
1046                                                    pathString);
1047                 return;
1048             }
1049         }
1050         std::optional<std::string> address;
1051         std::optional<size_t> prefixLength;
1052 
1053         nlohmann::json thisJsonCopy = thisJson;
1054         if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address",
1055                                  address, "PrefixLength", prefixLength))
1056         {
1057             return;
1058         }
1059         const std::string* addr = nullptr;
1060         size_t prefix = 0;
1061         if (address)
1062         {
1063             addr = &(*address);
1064         }
1065         else if (staticGatewayEntry != staticGatewayData.end())
1066         {
1067             addr = &(staticGatewayEntry->gateway);
1068         }
1069         else
1070         {
1071             messages::propertyMissing(asyncResp->res, pathString + "/Address");
1072             return;
1073         }
1074         if (prefixLength)
1075         {
1076             prefix = *prefixLength;
1077         }
1078         else if (staticGatewayEntry != staticGatewayData.end())
1079         {
1080             prefix = staticGatewayEntry->prefixLength;
1081         }
1082         else
1083         {
1084             messages::propertyMissing(asyncResp->res,
1085                                       pathString + "/PrefixLength");
1086             return;
1087         }
1088         if (staticGatewayEntry != staticGatewayData.end())
1089         {
1090             deleteAndCreateIPv6DefaultGateway(ifaceId, staticGatewayEntry->id,
1091                                               *addr, prefix, asyncResp);
1092             staticGatewayEntry++;
1093         }
1094         else
1095         {
1096             createIPv6DefaultGateway(ifaceId, prefix, *addr, asyncResp);
1097         }
1098         entryIdx++;
1099     }
1100 }
1101 
1102 /**
1103  * Function that retrieves all properties for given Ethernet Interface
1104  * Object
1105  * from EntityManager Network Manager
1106  * @param ethiface_id a eth interface id to query on DBus
1107  * @param callback a function that shall be called to convert Dbus output
1108  * into JSON
1109  */
1110 template <typename CallbackFunc>
1111 void getEthernetIfaceData(const std::string& ethifaceId,
1112                           CallbackFunc&& callback)
1113 {
1114     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
1115     dbus::utility::getManagedObjects(
1116         "xyz.openbmc_project.Network", path,
1117         [ethifaceId{std::string{ethifaceId}},
1118          callback{std::forward<CallbackFunc>(callback)}](
1119             const boost::system::error_code& ec,
1120             const dbus::utility::ManagedObjectType& resp) {
1121         EthernetInterfaceData ethData{};
1122         std::vector<IPv4AddressData> ipv4Data;
1123         std::vector<IPv6AddressData> ipv6Data;
1124         std::vector<StaticGatewayData> ipv6GatewayData;
1125 
1126         if (ec)
1127         {
1128             callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1129             return;
1130         }
1131 
1132         bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData);
1133         if (!found)
1134         {
1135             callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1136             return;
1137         }
1138 
1139         extractIPData(ethifaceId, resp, ipv4Data);
1140         // Fix global GW
1141         for (IPv4AddressData& ipv4 : ipv4Data)
1142         {
1143             if (((ipv4.linktype == LinkType::Global) &&
1144                  (ipv4.gateway == "0.0.0.0")) ||
1145                 (ipv4.origin == "DHCP") || (ipv4.origin == "Static"))
1146             {
1147                 ipv4.gateway = ethData.defaultGateway;
1148             }
1149         }
1150 
1151         extractIPV6Data(ethifaceId, resp, ipv6Data);
1152         if (!extractIPv6DefaultGatewayData(ethifaceId, resp, ipv6GatewayData))
1153         {
1154             callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1155         }
1156         // Finally make a callback with useful data
1157         callback(true, ethData, ipv4Data, ipv6Data, ipv6GatewayData);
1158     });
1159 }
1160 
1161 /**
1162  * Function that retrieves all Ethernet Interfaces available through Network
1163  * Manager
1164  * @param callback a function that shall be called to convert Dbus output
1165  * into JSON.
1166  */
1167 template <typename CallbackFunc>
1168 void getEthernetIfaceList(CallbackFunc&& callback)
1169 {
1170     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
1171     dbus::utility::getManagedObjects(
1172         "xyz.openbmc_project.Network", path,
1173         [callback{std::forward<CallbackFunc>(callback)}](
1174             const boost::system::error_code& ec,
1175             const dbus::utility::ManagedObjectType& resp) {
1176         // Callback requires vector<string> to retrieve all available
1177         // ethernet interfaces
1178         std::vector<std::string> ifaceList;
1179         ifaceList.reserve(resp.size());
1180         if (ec)
1181         {
1182             callback(false, ifaceList);
1183             return;
1184         }
1185 
1186         // Iterate over all retrieved ObjectPaths.
1187         for (const auto& objpath : resp)
1188         {
1189             // And all interfaces available for certain ObjectPath.
1190             for (const auto& interface : objpath.second)
1191             {
1192                 // If interface is
1193                 // xyz.openbmc_project.Network.EthernetInterface, this is
1194                 // what we're looking for.
1195                 if (interface.first ==
1196                     "xyz.openbmc_project.Network.EthernetInterface")
1197                 {
1198                     std::string ifaceId = objpath.first.filename();
1199                     if (ifaceId.empty())
1200                     {
1201                         continue;
1202                     }
1203                     // and put it into output vector.
1204                     ifaceList.emplace_back(ifaceId);
1205                 }
1206             }
1207         }
1208 
1209         std::ranges::sort(ifaceList, AlphanumLess<std::string>());
1210 
1211         // Finally make a callback with useful data
1212         callback(true, ifaceList);
1213     });
1214 }
1215 
1216 inline void
1217     handleHostnamePatch(const std::string& hostname,
1218                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1219 {
1220     // SHOULD handle host names of up to 255 characters(RFC 1123)
1221     if (hostname.length() > 255)
1222     {
1223         messages::propertyValueFormatError(asyncResp->res, hostname,
1224                                            "HostName");
1225         return;
1226     }
1227     sdbusplus::asio::setProperty(
1228         *crow::connections::systemBus, "xyz.openbmc_project.Network",
1229         "/xyz/openbmc_project/network/config",
1230         "xyz.openbmc_project.Network.SystemConfiguration", "HostName", hostname,
1231         [asyncResp](const boost::system::error_code& ec) {
1232         if (ec)
1233         {
1234             messages::internalError(asyncResp->res);
1235         }
1236     });
1237 }
1238 
1239 inline void
1240     handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize,
1241                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1242 {
1243     sdbusplus::message::object_path objPath = "/xyz/openbmc_project/network/" +
1244                                               ifaceId;
1245     sdbusplus::asio::setProperty(
1246         *crow::connections::systemBus, "xyz.openbmc_project.Network", objPath,
1247         "xyz.openbmc_project.Network.EthernetInterface", "MTU", mtuSize,
1248         [asyncResp](const boost::system::error_code& ec) {
1249         if (ec)
1250         {
1251             messages::internalError(asyncResp->res);
1252         }
1253     });
1254 }
1255 
1256 inline void
1257     handleDomainnamePatch(const std::string& ifaceId,
1258                           const std::string& domainname,
1259                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1260 {
1261     std::vector<std::string> vectorDomainname = {domainname};
1262     sdbusplus::asio::setProperty(
1263         *crow::connections::systemBus, "xyz.openbmc_project.Network",
1264         "/xyz/openbmc_project/network/" + ifaceId,
1265         "xyz.openbmc_project.Network.EthernetInterface", "DomainName",
1266         vectorDomainname, [asyncResp](const boost::system::error_code& ec) {
1267         if (ec)
1268         {
1269             messages::internalError(asyncResp->res);
1270         }
1271     });
1272 }
1273 
1274 inline bool isHostnameValid(const std::string& hostname)
1275 {
1276     // A valid host name can never have the dotted-decimal form (RFC 1123)
1277     if (std::ranges::all_of(hostname, ::isdigit))
1278     {
1279         return false;
1280     }
1281     // Each label(hostname/subdomains) within a valid FQDN
1282     // MUST handle host names of up to 63 characters (RFC 1123)
1283     // labels cannot start or end with hyphens (RFC 952)
1284     // labels can start with numbers (RFC 1123)
1285     const static std::regex pattern(
1286         "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
1287 
1288     return std::regex_match(hostname, pattern);
1289 }
1290 
1291 inline bool isDomainnameValid(const std::string& domainname)
1292 {
1293     // Can have multiple subdomains
1294     // Top Level Domain's min length is 2 character
1295     const static std::regex pattern(
1296         "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$");
1297 
1298     return std::regex_match(domainname, pattern);
1299 }
1300 
1301 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn,
1302                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1303 {
1304     // Total length of FQDN must not exceed 255 characters(RFC 1035)
1305     if (fqdn.length() > 255)
1306     {
1307         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1308         return;
1309     }
1310 
1311     size_t pos = fqdn.find('.');
1312     if (pos == std::string::npos)
1313     {
1314         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1315         return;
1316     }
1317 
1318     std::string hostname;
1319     std::string domainname;
1320     domainname = (fqdn).substr(pos + 1);
1321     hostname = (fqdn).substr(0, pos);
1322 
1323     if (!isHostnameValid(hostname) || !isDomainnameValid(domainname))
1324     {
1325         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1326         return;
1327     }
1328 
1329     handleHostnamePatch(hostname, asyncResp);
1330     handleDomainnamePatch(ifaceId, domainname, asyncResp);
1331 }
1332 
1333 inline void
1334     handleMACAddressPatch(const std::string& ifaceId,
1335                           const std::string& macAddress,
1336                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1337 {
1338     static constexpr std::string_view dbusNotAllowedError =
1339         "xyz.openbmc_project.Common.Error.NotAllowed";
1340 
1341     sdbusplus::asio::setProperty(
1342         *crow::connections::systemBus, "xyz.openbmc_project.Network",
1343         "/xyz/openbmc_project/network/" + ifaceId,
1344         "xyz.openbmc_project.Network.MACAddress", "MACAddress", macAddress,
1345         [asyncResp](const boost::system::error_code& ec,
1346                     const sdbusplus::message_t& msg) {
1347         if (ec)
1348         {
1349             const sd_bus_error* err = msg.get_error();
1350             if (err == nullptr)
1351             {
1352                 messages::internalError(asyncResp->res);
1353                 return;
1354             }
1355             if (err->name == dbusNotAllowedError)
1356             {
1357                 messages::propertyNotWritable(asyncResp->res, "MACAddress");
1358                 return;
1359             }
1360             messages::internalError(asyncResp->res);
1361             return;
1362         }
1363     });
1364 }
1365 
1366 inline void setDHCPEnabled(const std::string& ifaceId,
1367                            const std::string& propertyName, const bool v4Value,
1368                            const bool v6Value,
1369                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1370 {
1371     const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value);
1372     sdbusplus::asio::setProperty(
1373         *crow::connections::systemBus, "xyz.openbmc_project.Network",
1374         "/xyz/openbmc_project/network/" + ifaceId,
1375         "xyz.openbmc_project.Network.EthernetInterface", propertyName, dhcp,
1376         [asyncResp](const boost::system::error_code& ec) {
1377         if (ec)
1378         {
1379             BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec);
1380             messages::internalError(asyncResp->res);
1381             return;
1382         }
1383         messages::success(asyncResp->res);
1384     });
1385 }
1386 
1387 inline void setEthernetInterfaceBoolProperty(
1388     const std::string& ifaceId, const std::string& propertyName,
1389     const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1390 {
1391     sdbusplus::asio::setProperty(
1392         *crow::connections::systemBus, "xyz.openbmc_project.Network",
1393         "/xyz/openbmc_project/network/" + ifaceId,
1394         "xyz.openbmc_project.Network.EthernetInterface", propertyName, value,
1395         [asyncResp](const boost::system::error_code& ec) {
1396         if (ec)
1397         {
1398             BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec);
1399             messages::internalError(asyncResp->res);
1400             return;
1401         }
1402     });
1403 }
1404 
1405 enum class NetworkType
1406 {
1407     dhcp4,
1408     dhcp6
1409 };
1410 
1411 inline void setDHCPConfig(const std::string& propertyName, const bool& value,
1412                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1413                           const std::string& ethifaceId, NetworkType type)
1414 {
1415     BMCWEB_LOG_DEBUG("{} = {}", propertyName, value);
1416     sdbusplus::message::object_path path("/xyz/openbmc_project/network/");
1417     path /= ethifaceId;
1418 
1419     if (type == NetworkType::dhcp4)
1420     {
1421         path /= "dhcp4";
1422     }
1423     else
1424     {
1425         path /= "dhcp6";
1426     }
1427 
1428     sdbusplus::asio::setProperty(
1429         *crow::connections::systemBus, "xyz.openbmc_project.Network", path,
1430         "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, value,
1431         [asyncResp](const boost::system::error_code& ec) {
1432         if (ec)
1433         {
1434             BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec);
1435             messages::internalError(asyncResp->res);
1436             return;
1437         }
1438     });
1439 }
1440 
1441 inline void handleSLAACAutoConfigPatch(
1442     const std::string& ifaceId, bool ipv6AutoConfigEnabled,
1443     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1444 {
1445     sdbusplus::message::object_path path("/xyz/openbmc_project/network");
1446     path /= ifaceId;
1447     sdbusplus::asio::setProperty(
1448         *crow::connections::systemBus, "xyz.openbmc_project.Network", path,
1449         "xyz.openbmc_project.Network.EthernetInterface", "IPv6AcceptRA",
1450         ipv6AutoConfigEnabled,
1451         [asyncResp](const boost::system::error_code& ec) {
1452         if (ec)
1453         {
1454             BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec);
1455             messages::internalError(asyncResp->res);
1456             return;
1457         }
1458         messages::success(asyncResp->res);
1459     });
1460 }
1461 
1462 inline void handleDHCPPatch(const std::string& ifaceId,
1463                             const EthernetInterfaceData& ethData,
1464                             const DHCPParameters& v4dhcpParms,
1465                             const DHCPParameters& v6dhcpParms,
1466                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1467 {
1468     bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1469     bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false);
1470 
1471     bool nextv4DHCPState =
1472         v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active;
1473 
1474     bool nextv6DHCPState{};
1475     if (v6dhcpParms.dhcpv6OperatingMode)
1476     {
1477         if ((*v6dhcpParms.dhcpv6OperatingMode != "Enabled") &&
1478             (*v6dhcpParms.dhcpv6OperatingMode != "Disabled"))
1479         {
1480             messages::propertyValueFormatError(asyncResp->res,
1481                                                *v6dhcpParms.dhcpv6OperatingMode,
1482                                                "OperatingMode");
1483             return;
1484         }
1485         nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Enabled");
1486     }
1487     else
1488     {
1489         nextv6DHCPState = ipv6Active;
1490     }
1491 
1492     bool nextDNSv4 = ethData.dnsv4Enabled;
1493     bool nextDNSv6 = ethData.dnsv6Enabled;
1494     if (v4dhcpParms.useDnsServers)
1495     {
1496         nextDNSv4 = *v4dhcpParms.useDnsServers;
1497     }
1498     if (v6dhcpParms.useDnsServers)
1499     {
1500         nextDNSv6 = *v6dhcpParms.useDnsServers;
1501     }
1502 
1503     bool nextNTPv4 = ethData.ntpv4Enabled;
1504     bool nextNTPv6 = ethData.ntpv6Enabled;
1505     if (v4dhcpParms.useNtpServers)
1506     {
1507         nextNTPv4 = *v4dhcpParms.useNtpServers;
1508     }
1509     if (v6dhcpParms.useNtpServers)
1510     {
1511         nextNTPv6 = *v6dhcpParms.useNtpServers;
1512     }
1513 
1514     bool nextUsev4Domain = ethData.domainv4Enabled;
1515     bool nextUsev6Domain = ethData.domainv6Enabled;
1516     if (v4dhcpParms.useDomainName)
1517     {
1518         nextUsev4Domain = *v4dhcpParms.useDomainName;
1519     }
1520     if (v6dhcpParms.useDomainName)
1521     {
1522         nextUsev6Domain = *v6dhcpParms.useDomainName;
1523     }
1524 
1525     BMCWEB_LOG_DEBUG("set DHCPEnabled...");
1526     setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState,
1527                    asyncResp);
1528     BMCWEB_LOG_DEBUG("set DNSEnabled...");
1529     setDHCPConfig("DNSEnabled", nextDNSv4, asyncResp, ifaceId,
1530                   NetworkType::dhcp4);
1531     BMCWEB_LOG_DEBUG("set NTPEnabled...");
1532     setDHCPConfig("NTPEnabled", nextNTPv4, asyncResp, ifaceId,
1533                   NetworkType::dhcp4);
1534     BMCWEB_LOG_DEBUG("set DomainEnabled...");
1535     setDHCPConfig("DomainEnabled", nextUsev4Domain, asyncResp, ifaceId,
1536                   NetworkType::dhcp4);
1537     BMCWEB_LOG_DEBUG("set DNSEnabled for dhcp6...");
1538     setDHCPConfig("DNSEnabled", nextDNSv6, asyncResp, ifaceId,
1539                   NetworkType::dhcp6);
1540     BMCWEB_LOG_DEBUG("set NTPEnabled for dhcp6...");
1541     setDHCPConfig("NTPEnabled", nextNTPv6, asyncResp, ifaceId,
1542                   NetworkType::dhcp6);
1543     BMCWEB_LOG_DEBUG("set DomainEnabled for dhcp6...");
1544     setDHCPConfig("DomainEnabled", nextUsev6Domain, asyncResp, ifaceId,
1545                   NetworkType::dhcp6);
1546 }
1547 
1548 inline std::vector<IPv4AddressData>::const_iterator getNextStaticIpEntry(
1549     const std::vector<IPv4AddressData>::const_iterator& head,
1550     const std::vector<IPv4AddressData>::const_iterator& end)
1551 {
1552     return std::find_if(head, end, [](const IPv4AddressData& value) {
1553         return value.origin == "Static";
1554     });
1555 }
1556 
1557 inline std::vector<IPv6AddressData>::const_iterator getNextStaticIpEntry(
1558     const std::vector<IPv6AddressData>::const_iterator& head,
1559     const std::vector<IPv6AddressData>::const_iterator& end)
1560 {
1561     return std::find_if(head, end, [](const IPv6AddressData& value) {
1562         return value.origin == "Static";
1563     });
1564 }
1565 
1566 inline void
1567     handleIPv4StaticPatch(const std::string& ifaceId,
1568                           nlohmann::json::array_t& input,
1569                           const std::vector<IPv4AddressData>& ipv4Data,
1570                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1571 {
1572     if (input.empty())
1573     {
1574         messages::propertyValueTypeError(asyncResp->res, input,
1575                                          "IPv4StaticAddresses");
1576         return;
1577     }
1578 
1579     unsigned entryIdx = 1;
1580     // Find the first static IP address currently active on the NIC and
1581     // match it to the first JSON element in the IPv4StaticAddresses array.
1582     // Match each subsequent JSON element to the next static IP programmed
1583     // into the NIC.
1584     std::vector<IPv4AddressData>::const_iterator nicIpEntry =
1585         getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend());
1586 
1587     for (nlohmann::json& thisJson : input)
1588     {
1589         std::string pathString = "IPv4StaticAddresses/" +
1590                                  std::to_string(entryIdx);
1591 
1592         if (!thisJson.is_null() && !thisJson.empty())
1593         {
1594             std::optional<std::string> address;
1595             std::optional<std::string> subnetMask;
1596             std::optional<std::string> gateway;
1597 
1598             if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1599                                      address, "SubnetMask", subnetMask,
1600                                      "Gateway", gateway))
1601             {
1602                 messages::propertyValueFormatError(asyncResp->res, thisJson,
1603                                                    pathString);
1604                 return;
1605             }
1606 
1607             // Find the address/subnet/gateway values. Any values that are
1608             // not explicitly provided are assumed to be unmodified from the
1609             // current state of the interface. Merge existing state into the
1610             // current request.
1611             if (address)
1612             {
1613                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address))
1614                 {
1615                     messages::propertyValueFormatError(asyncResp->res, *address,
1616                                                        pathString + "/Address");
1617                     return;
1618                 }
1619             }
1620             else if (nicIpEntry != ipv4Data.cend())
1621             {
1622                 address = (nicIpEntry->address);
1623             }
1624             else
1625             {
1626                 messages::propertyMissing(asyncResp->res,
1627                                           pathString + "/Address");
1628                 return;
1629             }
1630 
1631             uint8_t prefixLength = 0;
1632             if (subnetMask)
1633             {
1634                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask,
1635                                                          &prefixLength))
1636                 {
1637                     messages::propertyValueFormatError(
1638                         asyncResp->res, *subnetMask,
1639                         pathString + "/SubnetMask");
1640                     return;
1641                 }
1642             }
1643             else if (nicIpEntry != ipv4Data.cend())
1644             {
1645                 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask,
1646                                                          &prefixLength))
1647                 {
1648                     messages::propertyValueFormatError(
1649                         asyncResp->res, nicIpEntry->netmask,
1650                         pathString + "/SubnetMask");
1651                     return;
1652                 }
1653             }
1654             else
1655             {
1656                 messages::propertyMissing(asyncResp->res,
1657                                           pathString + "/SubnetMask");
1658                 return;
1659             }
1660 
1661             if (gateway)
1662             {
1663                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway))
1664                 {
1665                     messages::propertyValueFormatError(asyncResp->res, *gateway,
1666                                                        pathString + "/Gateway");
1667                     return;
1668                 }
1669             }
1670             else if (nicIpEntry != ipv4Data.cend())
1671             {
1672                 gateway = nicIpEntry->gateway;
1673             }
1674             else
1675             {
1676                 messages::propertyMissing(asyncResp->res,
1677                                           pathString + "/Gateway");
1678                 return;
1679             }
1680 
1681             if (nicIpEntry != ipv4Data.cend())
1682             {
1683                 deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId,
1684                                          nicIpEntry->id, prefixLength, *address,
1685                                          *gateway, asyncResp);
1686                 nicIpEntry = getNextStaticIpEntry(++nicIpEntry,
1687                                                   ipv4Data.cend());
1688             }
1689             else
1690             {
1691                 createIPv4(ifaceId, prefixLength, *gateway, *address,
1692                            asyncResp);
1693             }
1694             entryIdx++;
1695         }
1696         else
1697         {
1698             if (nicIpEntry == ipv4Data.cend())
1699             {
1700                 // Requesting a DELETE/DO NOT MODIFY action for an item
1701                 // that isn't present on the eth(n) interface. Input JSON is
1702                 // in error, so bail out.
1703                 if (thisJson.is_null())
1704                 {
1705                     messages::resourceCannotBeDeleted(asyncResp->res);
1706                     return;
1707                 }
1708                 messages::propertyValueFormatError(asyncResp->res, thisJson,
1709                                                    pathString);
1710                 return;
1711             }
1712 
1713             if (thisJson.is_null())
1714             {
1715                 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp);
1716             }
1717             if (nicIpEntry != ipv4Data.cend())
1718             {
1719                 nicIpEntry = getNextStaticIpEntry(++nicIpEntry,
1720                                                   ipv4Data.cend());
1721             }
1722             entryIdx++;
1723         }
1724     }
1725 }
1726 
1727 inline void handleStaticNameServersPatch(
1728     const std::string& ifaceId,
1729     const std::vector<std::string>& updatedStaticNameServers,
1730     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1731 {
1732     sdbusplus::asio::setProperty(
1733         *crow::connections::systemBus, "xyz.openbmc_project.Network",
1734         "/xyz/openbmc_project/network/" + ifaceId,
1735         "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers",
1736         updatedStaticNameServers,
1737         [asyncResp](const boost::system::error_code& ec) {
1738         if (ec)
1739         {
1740             messages::internalError(asyncResp->res);
1741             return;
1742         }
1743     });
1744 }
1745 
1746 inline void handleIPv6StaticAddressesPatch(
1747     const std::string& ifaceId, const nlohmann::json::array_t& input,
1748     const std::vector<IPv6AddressData>& ipv6Data,
1749     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1750 {
1751     if (input.empty())
1752     {
1753         messages::propertyValueTypeError(asyncResp->res, input,
1754                                          "IPv6StaticAddresses");
1755         return;
1756     }
1757     size_t entryIdx = 1;
1758     std::vector<IPv6AddressData>::const_iterator nicIpEntry =
1759         getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend());
1760     for (const nlohmann::json& thisJson : input)
1761     {
1762         std::string pathString = "IPv6StaticAddresses/" +
1763                                  std::to_string(entryIdx);
1764 
1765         if (!thisJson.is_null() && !thisJson.empty())
1766         {
1767             std::optional<std::string> address;
1768             std::optional<uint8_t> prefixLength;
1769             nlohmann::json thisJsonCopy = thisJson;
1770             if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address",
1771                                      address, "PrefixLength", prefixLength))
1772             {
1773                 messages::propertyValueFormatError(asyncResp->res, thisJson,
1774                                                    pathString);
1775                 return;
1776             }
1777 
1778             const std::string* addr = nullptr;
1779             uint8_t prefix = 0;
1780 
1781             // Find the address and prefixLength values. Any values that are
1782             // not explicitly provided are assumed to be unmodified from the
1783             // current state of the interface. Merge existing state into the
1784             // current request.
1785             if (address)
1786             {
1787                 addr = &(*address);
1788             }
1789             else if (nicIpEntry != ipv6Data.end())
1790             {
1791                 addr = &(nicIpEntry->address);
1792             }
1793             else
1794             {
1795                 messages::propertyMissing(asyncResp->res,
1796                                           pathString + "/Address");
1797                 return;
1798             }
1799 
1800             if (prefixLength)
1801             {
1802                 prefix = *prefixLength;
1803             }
1804             else if (nicIpEntry != ipv6Data.end())
1805             {
1806                 prefix = nicIpEntry->prefixLength;
1807             }
1808             else
1809             {
1810                 messages::propertyMissing(asyncResp->res,
1811                                           pathString + "/PrefixLength");
1812                 return;
1813             }
1814 
1815             if (nicIpEntry != ipv6Data.end())
1816             {
1817                 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId,
1818                                          nicIpEntry->id, prefix, *addr, "",
1819                                          asyncResp);
1820                 nicIpEntry = getNextStaticIpEntry(++nicIpEntry,
1821                                                   ipv6Data.cend());
1822             }
1823             else
1824             {
1825                 createIPv6(ifaceId, *prefixLength, *addr, asyncResp);
1826             }
1827             entryIdx++;
1828         }
1829         else
1830         {
1831             if (nicIpEntry == ipv6Data.end())
1832             {
1833                 // Requesting a DELETE/DO NOT MODIFY action for an item
1834                 // that isn't present on the eth(n) interface. Input JSON is
1835                 // in error, so bail out.
1836                 if (thisJson.is_null())
1837                 {
1838                     messages::resourceCannotBeDeleted(asyncResp->res);
1839                     return;
1840                 }
1841                 messages::propertyValueFormatError(asyncResp->res, thisJson,
1842                                                    pathString);
1843                 return;
1844             }
1845 
1846             if (thisJson.is_null())
1847             {
1848                 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp);
1849             }
1850             if (nicIpEntry != ipv6Data.cend())
1851             {
1852                 nicIpEntry = getNextStaticIpEntry(++nicIpEntry,
1853                                                   ipv6Data.cend());
1854             }
1855             entryIdx++;
1856         }
1857     }
1858 }
1859 
1860 inline std::string extractParentInterfaceName(const std::string& ifaceId)
1861 {
1862     std::size_t pos = ifaceId.find('_');
1863     return ifaceId.substr(0, pos);
1864 }
1865 
1866 inline void
1867     parseInterfaceData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1868                        const std::string& ifaceId,
1869                        const EthernetInterfaceData& ethData,
1870                        const std::vector<IPv4AddressData>& ipv4Data,
1871                        const std::vector<IPv6AddressData>& ipv6Data,
1872                        const std::vector<StaticGatewayData>& ipv6GatewayData)
1873 {
1874     nlohmann::json& jsonResponse = asyncResp->res.jsonValue;
1875     jsonResponse["Id"] = ifaceId;
1876     jsonResponse["@odata.id"] = boost::urls::format(
1877         "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", ifaceId);
1878     jsonResponse["InterfaceEnabled"] = ethData.nicEnabled;
1879 
1880     if constexpr (bmcwebEnableHealthPopulate)
1881     {
1882         constexpr std::array<std::string_view, 1> inventoryForEthernet = {
1883             "xyz.openbmc_project.Inventory.Item.Ethernet"};
1884         auto health = std::make_shared<HealthPopulate>(asyncResp);
1885         dbus::utility::getSubTreePaths(
1886             "/", 0, inventoryForEthernet,
1887             [health](const boost::system::error_code& ec,
1888                      const dbus::utility::MapperGetSubTreePathsResponse& resp) {
1889             if (ec)
1890             {
1891                 return;
1892             }
1893 
1894             health->inventory = resp;
1895         });
1896 
1897         health->populate();
1898     }
1899 
1900     if (ethData.nicEnabled)
1901     {
1902         jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown";
1903         jsonResponse["Status"]["State"] = "Enabled";
1904     }
1905     else
1906     {
1907         jsonResponse["LinkStatus"] = "NoLink";
1908         jsonResponse["Status"]["State"] = "Disabled";
1909     }
1910 
1911     jsonResponse["SpeedMbps"] = ethData.speed;
1912     jsonResponse["MTUSize"] = ethData.mtuSize;
1913     jsonResponse["MACAddress"] = ethData.macAddress;
1914     jsonResponse["DHCPv4"]["DHCPEnabled"] =
1915         translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1916     jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled;
1917     jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled;
1918     jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNamev4Enabled;
1919     jsonResponse["DHCPv6"]["OperatingMode"] =
1920         translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Enabled"
1921                                                                : "Disabled";
1922     jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled;
1923     jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled;
1924     jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNamev6Enabled;
1925     jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] =
1926         ethData.ipv6AcceptRa;
1927 
1928     if (!ethData.hostName.empty())
1929     {
1930         jsonResponse["HostName"] = ethData.hostName;
1931 
1932         // When domain name is empty then it means, that it is a network
1933         // without domain names, and the host name itself must be treated as
1934         // FQDN
1935         std::string fqdn = ethData.hostName;
1936         if (!ethData.domainnames.empty())
1937         {
1938             fqdn += "." + ethData.domainnames[0];
1939         }
1940         jsonResponse["FQDN"] = fqdn;
1941     }
1942 
1943     if (ethData.vlanId)
1944     {
1945         jsonResponse["EthernetInterfaceType"] = "Virtual";
1946         jsonResponse["VLAN"]["VLANEnable"] = true;
1947         jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId;
1948         jsonResponse["VLAN"]["Tagged"] = true;
1949 
1950         nlohmann::json::array_t relatedInterfaces;
1951         nlohmann::json& parentInterface = relatedInterfaces.emplace_back();
1952         parentInterface["@odata.id"] =
1953             boost::urls::format("/redfish/v1/Managers/bmc/EthernetInterfaces",
1954                                 extractParentInterfaceName(ifaceId));
1955         jsonResponse["Links"]["RelatedInterfaces"] =
1956             std::move(relatedInterfaces);
1957     }
1958     else
1959     {
1960         jsonResponse["EthernetInterfaceType"] = "Physical";
1961     }
1962 
1963     jsonResponse["NameServers"] = ethData.nameServers;
1964     jsonResponse["StaticNameServers"] = ethData.staticNameServers;
1965 
1966     nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"];
1967     nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"];
1968     ipv4Array = nlohmann::json::array();
1969     ipv4StaticArray = nlohmann::json::array();
1970     for (const auto& ipv4Config : ipv4Data)
1971     {
1972         std::string gatewayStr = ipv4Config.gateway;
1973         if (gatewayStr.empty())
1974         {
1975             gatewayStr = "0.0.0.0";
1976         }
1977         nlohmann::json::object_t ipv4;
1978         ipv4["AddressOrigin"] = ipv4Config.origin;
1979         ipv4["SubnetMask"] = ipv4Config.netmask;
1980         ipv4["Address"] = ipv4Config.address;
1981         ipv4["Gateway"] = gatewayStr;
1982 
1983         if (ipv4Config.origin == "Static")
1984         {
1985             ipv4StaticArray.push_back(ipv4);
1986         }
1987 
1988         ipv4Array.emplace_back(std::move(ipv4));
1989     }
1990 
1991     std::string ipv6GatewayStr = ethData.ipv6DefaultGateway;
1992     if (ipv6GatewayStr.empty())
1993     {
1994         ipv6GatewayStr = "0:0:0:0:0:0:0:0";
1995     }
1996 
1997     jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr;
1998 
1999     nlohmann::json::array_t ipv6StaticGatewayArray;
2000     for (const auto& ipv6GatewayConfig : ipv6GatewayData)
2001     {
2002         nlohmann::json::object_t ipv6Gateway;
2003         ipv6Gateway["Address"] = ipv6GatewayConfig.gateway;
2004         ipv6Gateway["PrefixLength"] = ipv6GatewayConfig.prefixLength;
2005         ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway));
2006     }
2007     jsonResponse["IPv6StaticDefaultGateways"] =
2008         std::move(ipv6StaticGatewayArray);
2009 
2010     nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"];
2011     nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"];
2012     ipv6Array = nlohmann::json::array();
2013     ipv6StaticArray = nlohmann::json::array();
2014     nlohmann::json& ipv6AddrPolicyTable =
2015         jsonResponse["IPv6AddressPolicyTable"];
2016     ipv6AddrPolicyTable = nlohmann::json::array();
2017     for (const auto& ipv6Config : ipv6Data)
2018     {
2019         nlohmann::json::object_t ipv6;
2020         ipv6["Address"] = ipv6Config.address;
2021         ipv6["PrefixLength"] = ipv6Config.prefixLength;
2022         ipv6["AddressOrigin"] = ipv6Config.origin;
2023 
2024         ipv6Array.emplace_back(std::move(ipv6));
2025         if (ipv6Config.origin == "Static")
2026         {
2027             nlohmann::json::object_t ipv6Static;
2028             ipv6Static["Address"] = ipv6Config.address;
2029             ipv6Static["PrefixLength"] = ipv6Config.prefixLength;
2030             ipv6StaticArray.emplace_back(std::move(ipv6Static));
2031         }
2032     }
2033 }
2034 
2035 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2036                         const std::string& ifaceId,
2037                         const boost::system::error_code& ec,
2038                         const sdbusplus::message_t& m)
2039 {
2040     if (!ec)
2041     {
2042         return;
2043     }
2044     const sd_bus_error* dbusError = m.get_error();
2045     if (dbusError == nullptr)
2046     {
2047         messages::internalError(asyncResp->res);
2048         return;
2049     }
2050     BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
2051 
2052     if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") ==
2053         dbusError->name)
2054     {
2055         messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2056                                    ifaceId);
2057         return;
2058     }
2059     if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") ==
2060         dbusError->name)
2061     {
2062         messages::resourceCannotBeDeleted(asyncResp->res);
2063         return;
2064     }
2065     messages::internalError(asyncResp->res);
2066 }
2067 
2068 inline void afterVlanCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2069                             const std::string& parentInterfaceUri,
2070                             const std::string& vlanInterface,
2071                             const boost::system::error_code& ec,
2072                             const sdbusplus::message_t& m
2073 
2074 )
2075 {
2076     if (ec)
2077     {
2078         const sd_bus_error* dbusError = m.get_error();
2079         if (dbusError == nullptr)
2080         {
2081             messages::internalError(asyncResp->res);
2082             return;
2083         }
2084         BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
2085 
2086         if (std::string_view(
2087                 "xyz.openbmc_project.Common.Error.ResourceNotFound") ==
2088             dbusError->name)
2089         {
2090             messages::propertyValueNotInList(
2091                 asyncResp->res, parentInterfaceUri,
2092                 "Links/RelatedInterfaces/0/@odata.id");
2093             return;
2094         }
2095         if (std::string_view(
2096                 "xyz.openbmc_project.Common.Error.InvalidArgument") ==
2097             dbusError->name)
2098         {
2099             messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface",
2100                                             "Id", vlanInterface);
2101             return;
2102         }
2103         messages::internalError(asyncResp->res);
2104         return;
2105     }
2106 
2107     const boost::urls::url vlanInterfaceUri = boost::urls::format(
2108         "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", vlanInterface);
2109     asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer());
2110 }
2111 
2112 inline void requestEthernetInterfacesRoutes(App& app)
2113 {
2114     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
2115         .privileges(redfish::privileges::getEthernetInterfaceCollection)
2116         .methods(boost::beast::http::verb::get)(
2117             [&app](const crow::Request& req,
2118                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2119         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2120         {
2121             return;
2122         }
2123 
2124         asyncResp->res.jsonValue["@odata.type"] =
2125             "#EthernetInterfaceCollection.EthernetInterfaceCollection";
2126         asyncResp->res.jsonValue["@odata.id"] =
2127             "/redfish/v1/Managers/bmc/EthernetInterfaces";
2128         asyncResp->res.jsonValue["Name"] =
2129             "Ethernet Network Interface Collection";
2130         asyncResp->res.jsonValue["Description"] =
2131             "Collection of EthernetInterfaces for this Manager";
2132 
2133         // Get eth interface list, and call the below callback for JSON
2134         // preparation
2135         getEthernetIfaceList(
2136             [asyncResp](const bool& success,
2137                         const std::vector<std::string>& ifaceList) {
2138             if (!success)
2139             {
2140                 messages::internalError(asyncResp->res);
2141                 return;
2142             }
2143 
2144             nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"];
2145             ifaceArray = nlohmann::json::array();
2146             for (const std::string& ifaceItem : ifaceList)
2147             {
2148                 nlohmann::json::object_t iface;
2149                 iface["@odata.id"] = boost::urls::format(
2150                     "/redfish/v1/Managers/bmc/EthernetInterfaces/{}",
2151                     ifaceItem);
2152                 ifaceArray.push_back(std::move(iface));
2153             }
2154 
2155             asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size();
2156             asyncResp->res.jsonValue["@odata.id"] =
2157                 "/redfish/v1/Managers/bmc/EthernetInterfaces";
2158         });
2159     });
2160 
2161     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
2162         .privileges(redfish::privileges::postEthernetInterfaceCollection)
2163         .methods(boost::beast::http::verb::post)(
2164             [&app](const crow::Request& req,
2165                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2166         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2167         {
2168             return;
2169         }
2170 
2171         bool vlanEnable = false;
2172         uint32_t vlanId = 0;
2173         nlohmann::json::array_t relatedInterfaces;
2174 
2175         if (!json_util::readJsonPatch(req, asyncResp->res, "VLAN/VLANEnable",
2176                                       vlanEnable, "VLAN/VLANId", vlanId,
2177                                       "Links/RelatedInterfaces",
2178                                       relatedInterfaces))
2179         {
2180             return;
2181         }
2182 
2183         if (relatedInterfaces.size() != 1)
2184         {
2185             messages::arraySizeTooLong(asyncResp->res,
2186                                        "Links/RelatedInterfaces",
2187                                        relatedInterfaces.size());
2188             return;
2189         }
2190 
2191         std::string parentInterfaceUri;
2192         if (!json_util::readJson(relatedInterfaces[0], asyncResp->res,
2193                                  "@odata.id", parentInterfaceUri))
2194         {
2195             messages::propertyMissing(asyncResp->res,
2196                                       "Links/RelatedInterfaces/0/@odata.id");
2197             return;
2198         }
2199         BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri);
2200 
2201         boost::system::result<boost::urls::url_view> parsedUri =
2202             boost::urls::parse_relative_ref(parentInterfaceUri);
2203         if (!parsedUri)
2204         {
2205             messages::propertyValueFormatError(
2206                 asyncResp->res, parentInterfaceUri,
2207                 "Links/RelatedInterfaces/0/@odata.id");
2208             return;
2209         }
2210 
2211         std::string parentInterface;
2212         if (!crow::utility::readUrlSegments(
2213                 *parsedUri, "redfish", "v1", "Managers", "bmc",
2214                 "EthernetInterfaces", std::ref(parentInterface)))
2215         {
2216             messages::propertyValueNotInList(
2217                 asyncResp->res, parentInterfaceUri,
2218                 "Links/RelatedInterfaces/0/@odata.id");
2219             return;
2220         }
2221 
2222         if (!vlanEnable)
2223         {
2224             // In OpenBMC implementation, VLANEnable cannot be false on
2225             // create
2226             messages::propertyValueIncorrect(asyncResp->res, "VLAN/VLANEnable",
2227                                              "false");
2228             return;
2229         }
2230 
2231         std::string vlanInterface = parentInterface + "_" +
2232                                     std::to_string(vlanId);
2233         crow::connections::systemBus->async_method_call(
2234             [asyncResp, parentInterfaceUri,
2235              vlanInterface](const boost::system::error_code& ec,
2236                             const sdbusplus::message_t& m) {
2237             afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec,
2238                             m);
2239         },
2240             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
2241             "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface,
2242             vlanId);
2243     });
2244 
2245     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
2246         .privileges(redfish::privileges::getEthernetInterface)
2247         .methods(boost::beast::http::verb::get)(
2248             [&app](const crow::Request& req,
2249                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2250                    const std::string& ifaceId) {
2251         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2252         {
2253             return;
2254         }
2255         getEthernetIfaceData(
2256             ifaceId,
2257             [asyncResp,
2258              ifaceId](const bool& success, const EthernetInterfaceData& ethData,
2259                       const std::vector<IPv4AddressData>& ipv4Data,
2260                       const std::vector<IPv6AddressData>& ipv6Data,
2261                       const std::vector<StaticGatewayData>& ipv6GatewayData) {
2262             if (!success)
2263             {
2264                 // TODO(Pawel)consider distinguish between non
2265                 // existing object, and other errors
2266                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2267                                            ifaceId);
2268                 return;
2269             }
2270 
2271             asyncResp->res.jsonValue["@odata.type"] =
2272                 "#EthernetInterface.v1_9_0.EthernetInterface";
2273             asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface";
2274             asyncResp->res.jsonValue["Description"] =
2275                 "Management Network Interface";
2276 
2277             parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data,
2278                                ipv6GatewayData);
2279         });
2280     });
2281 
2282     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
2283         .privileges(redfish::privileges::patchEthernetInterface)
2284         .methods(boost::beast::http::verb::patch)(
2285             [&app](const crow::Request& req,
2286                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2287                    const std::string& ifaceId) {
2288         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2289         {
2290             return;
2291         }
2292         std::optional<std::string> hostname;
2293         std::optional<std::string> fqdn;
2294         std::optional<std::string> macAddress;
2295         std::optional<std::string> ipv6DefaultGateway;
2296         std::optional<nlohmann::json::array_t> ipv4StaticAddresses;
2297         std::optional<nlohmann::json::array_t> ipv6StaticAddresses;
2298         std::optional<nlohmann::json::array_t> ipv6StaticDefaultGateways;
2299         std::optional<std::vector<std::string>> staticNameServers;
2300         std::optional<nlohmann::json> dhcpv4;
2301         std::optional<nlohmann::json> dhcpv6;
2302         std::optional<bool> ipv6AutoConfigEnabled;
2303         std::optional<bool> interfaceEnabled;
2304         std::optional<size_t> mtuSize;
2305         DHCPParameters v4dhcpParms;
2306         DHCPParameters v6dhcpParms;
2307         // clang-format off
2308         if (!json_util::readJsonPatch(
2309                 req, asyncResp->res,
2310                 "DHCPv4", dhcpv4,
2311                 "DHCPv6", dhcpv6,
2312                 "FQDN", fqdn,
2313                 "HostName", hostname,
2314                 "IPv4StaticAddresses", ipv4StaticAddresses,
2315                 "IPv6DefaultGateway", ipv6DefaultGateway,
2316                 "IPv6StaticAddresses", ipv6StaticAddresses,
2317                 "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways,
2318                 "InterfaceEnabled", interfaceEnabled,
2319                 "MACAddress", macAddress,
2320                 "MTUSize", mtuSize,
2321                 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled,
2322                 "StaticNameServers", staticNameServers
2323                 )
2324             )
2325         {
2326             return;
2327         }
2328         //clang-format on
2329         if (dhcpv4)
2330         {
2331             if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled",
2332                                      v4dhcpParms.dhcpv4Enabled, "UseDNSServers",
2333                                      v4dhcpParms.useDnsServers, "UseNTPServers",
2334                                      v4dhcpParms.useNtpServers, "UseDomainName",
2335                                      v4dhcpParms.useDomainName))
2336             {
2337                 return;
2338             }
2339         }
2340 
2341         if (dhcpv6)
2342         {
2343             if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode",
2344                                      v6dhcpParms.dhcpv6OperatingMode,
2345                                      "UseDNSServers", v6dhcpParms.useDnsServers,
2346                                      "UseNTPServers", v6dhcpParms.useNtpServers,
2347                                      "UseDomainName",
2348                                      v6dhcpParms.useDomainName))
2349             {
2350                 return;
2351             }
2352         }
2353 
2354         // Get single eth interface data, and call the below callback
2355         // for JSON preparation
2356         getEthernetIfaceData(
2357             ifaceId,
2358             [asyncResp, ifaceId, hostname = std::move(hostname),
2359              fqdn = std::move(fqdn), macAddress = std::move(macAddress),
2360              ipv4StaticAddresses = std::move(ipv4StaticAddresses),
2361              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
2362              ipv6StaticAddresses = std::move(ipv6StaticAddresses),
2363              ipv6StaticDefaultGateway = std::move(ipv6StaticDefaultGateways),
2364              staticNameServers = std::move(staticNameServers),
2365              dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize,
2366              ipv6AutoConfigEnabled, v4dhcpParms = std::move(v4dhcpParms),
2367              v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled](
2368                 const bool& success, const EthernetInterfaceData& ethData,
2369                 const std::vector<IPv4AddressData>& ipv4Data,
2370                 const std::vector<IPv6AddressData>& ipv6Data,
2371                 const std::vector<StaticGatewayData>& ipv6GatewayData) {
2372             if (!success)
2373             {
2374                 // ... otherwise return error
2375                 // TODO(Pawel)consider distinguish between non
2376                 // existing object, and other errors
2377                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2378                                            ifaceId);
2379                 return;
2380             }
2381 
2382             if (dhcpv4 || dhcpv6)
2383             {
2384                 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms,
2385                                 asyncResp);
2386             }
2387 
2388             if (hostname)
2389             {
2390                 handleHostnamePatch(*hostname, asyncResp);
2391             }
2392 
2393             if (ipv6AutoConfigEnabled)
2394             {
2395                 handleSLAACAutoConfigPatch(ifaceId, *ipv6AutoConfigEnabled,
2396                                            asyncResp);
2397             }
2398 
2399             if (fqdn)
2400             {
2401                 handleFqdnPatch(ifaceId, *fqdn, asyncResp);
2402             }
2403 
2404             if (macAddress)
2405             {
2406                 handleMACAddressPatch(ifaceId, *macAddress, asyncResp);
2407             }
2408 
2409             if (ipv4StaticAddresses)
2410             {
2411                 // TODO(ed) for some reason the capture of
2412                 // ipv4Addresses above is returning a const value,
2413                 // not a non-const value. This doesn't really work
2414                 // for us, as we need to be able to efficiently move
2415                 // out the intermedia nlohmann::json objects. This
2416                 // makes a copy of the structure, and operates on
2417                 // that, but could be done more efficiently
2418                 nlohmann::json::array_t ipv4Static = *ipv4StaticAddresses;
2419                 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp);
2420             }
2421 
2422             if (staticNameServers)
2423             {
2424                 handleStaticNameServersPatch(ifaceId, *staticNameServers,
2425                                              asyncResp);
2426             }
2427 
2428             if (ipv6DefaultGateway)
2429             {
2430                 messages::propertyNotWritable(asyncResp->res,
2431                                               "IPv6DefaultGateway");
2432             }
2433 
2434             if (ipv6StaticAddresses)
2435             {
2436                 handleIPv6StaticAddressesPatch(ifaceId, *ipv6StaticAddresses,
2437                                                ipv6Data, asyncResp);
2438             }
2439 
2440             if (ipv6StaticDefaultGateway)
2441             {
2442                 handleIPv6DefaultGateway(ifaceId, *ipv6StaticDefaultGateway,
2443                                          ipv6GatewayData, asyncResp);
2444             }
2445 
2446             if (interfaceEnabled)
2447             {
2448                 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled",
2449                                                  *interfaceEnabled, asyncResp);
2450             }
2451 
2452             if (mtuSize)
2453             {
2454                 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp);
2455             }
2456             });
2457         });
2458 
2459     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
2460         .privileges(redfish::privileges::deleteEthernetInterface)
2461         .methods(boost::beast::http::verb::delete_)(
2462             [&app](const crow::Request& req,
2463                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2464                    const std::string& ifaceId) {
2465         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2466         {
2467             return;
2468         }
2469 
2470         crow::connections::systemBus->async_method_call(
2471             [asyncResp, ifaceId](const boost::system::error_code& ec,
2472                                  const sdbusplus::message_t& m) {
2473             afterDelete(asyncResp, ifaceId, ec, m);
2474             },
2475             "xyz.openbmc_project.Network",
2476             std::string("/xyz/openbmc_project/network/") + ifaceId,
2477             "xyz.openbmc_project.Object.Delete", "Delete");
2478         });
2479 }
2480 
2481 } // namespace redfish
2482