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             // Find the address and prefixLength values. Any values that are
1779             // not explicitly provided are assumed to be unmodified from the
1780             // current state of the interface. Merge existing state into the
1781             // current request.
1782             if (!address)
1783             {
1784                 if (nicIpEntry == ipv6Data.end())
1785                 {
1786                     messages::propertyMissing(asyncResp->res,
1787                                               pathString + "/Address");
1788                     return;
1789                 }
1790                 address = nicIpEntry->address;
1791             }
1792 
1793             if (!prefixLength)
1794             {
1795                 if (nicIpEntry == ipv6Data.end())
1796                 {
1797                     messages::propertyMissing(asyncResp->res,
1798                                               pathString + "/PrefixLength");
1799                     return;
1800                 }
1801                 prefixLength = nicIpEntry->prefixLength;
1802             }
1803 
1804             if (nicIpEntry != ipv6Data.end())
1805             {
1806                 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId,
1807                                          nicIpEntry->id, *prefixLength,
1808                                          *address, "", asyncResp);
1809                 nicIpEntry = getNextStaticIpEntry(++nicIpEntry,
1810                                                   ipv6Data.cend());
1811             }
1812             else
1813             {
1814                 createIPv6(ifaceId, *prefixLength, *address, asyncResp);
1815             }
1816             entryIdx++;
1817         }
1818         else
1819         {
1820             if (nicIpEntry == ipv6Data.end())
1821             {
1822                 // Requesting a DELETE/DO NOT MODIFY action for an item
1823                 // that isn't present on the eth(n) interface. Input JSON is
1824                 // in error, so bail out.
1825                 if (thisJson.is_null())
1826                 {
1827                     messages::resourceCannotBeDeleted(asyncResp->res);
1828                     return;
1829                 }
1830                 messages::propertyValueFormatError(asyncResp->res, thisJson,
1831                                                    pathString);
1832                 return;
1833             }
1834 
1835             if (thisJson.is_null())
1836             {
1837                 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp);
1838             }
1839             if (nicIpEntry != ipv6Data.cend())
1840             {
1841                 nicIpEntry = getNextStaticIpEntry(++nicIpEntry,
1842                                                   ipv6Data.cend());
1843             }
1844             entryIdx++;
1845         }
1846     }
1847 }
1848 
1849 inline std::string extractParentInterfaceName(const std::string& ifaceId)
1850 {
1851     std::size_t pos = ifaceId.find('_');
1852     return ifaceId.substr(0, pos);
1853 }
1854 
1855 inline void
1856     parseInterfaceData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1857                        const std::string& ifaceId,
1858                        const EthernetInterfaceData& ethData,
1859                        const std::vector<IPv4AddressData>& ipv4Data,
1860                        const std::vector<IPv6AddressData>& ipv6Data,
1861                        const std::vector<StaticGatewayData>& ipv6GatewayData)
1862 {
1863     nlohmann::json& jsonResponse = asyncResp->res.jsonValue;
1864     jsonResponse["Id"] = ifaceId;
1865     jsonResponse["@odata.id"] = boost::urls::format(
1866         "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", ifaceId);
1867     jsonResponse["InterfaceEnabled"] = ethData.nicEnabled;
1868 
1869     if constexpr (bmcwebEnableHealthPopulate)
1870     {
1871         constexpr std::array<std::string_view, 1> inventoryForEthernet = {
1872             "xyz.openbmc_project.Inventory.Item.Ethernet"};
1873         auto health = std::make_shared<HealthPopulate>(asyncResp);
1874         dbus::utility::getSubTreePaths(
1875             "/", 0, inventoryForEthernet,
1876             [health](const boost::system::error_code& ec,
1877                      const dbus::utility::MapperGetSubTreePathsResponse& resp) {
1878             if (ec)
1879             {
1880                 return;
1881             }
1882 
1883             health->inventory = resp;
1884         });
1885 
1886         health->populate();
1887     }
1888 
1889     if (ethData.nicEnabled)
1890     {
1891         jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown";
1892         jsonResponse["Status"]["State"] = "Enabled";
1893     }
1894     else
1895     {
1896         jsonResponse["LinkStatus"] = "NoLink";
1897         jsonResponse["Status"]["State"] = "Disabled";
1898     }
1899 
1900     jsonResponse["SpeedMbps"] = ethData.speed;
1901     jsonResponse["MTUSize"] = ethData.mtuSize;
1902     jsonResponse["MACAddress"] = ethData.macAddress;
1903     jsonResponse["DHCPv4"]["DHCPEnabled"] =
1904         translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1905     jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled;
1906     jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled;
1907     jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNamev4Enabled;
1908     jsonResponse["DHCPv6"]["OperatingMode"] =
1909         translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Enabled"
1910                                                                : "Disabled";
1911     jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled;
1912     jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled;
1913     jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNamev6Enabled;
1914     jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] =
1915         ethData.ipv6AcceptRa;
1916 
1917     if (!ethData.hostName.empty())
1918     {
1919         jsonResponse["HostName"] = ethData.hostName;
1920 
1921         // When domain name is empty then it means, that it is a network
1922         // without domain names, and the host name itself must be treated as
1923         // FQDN
1924         std::string fqdn = ethData.hostName;
1925         if (!ethData.domainnames.empty())
1926         {
1927             fqdn += "." + ethData.domainnames[0];
1928         }
1929         jsonResponse["FQDN"] = fqdn;
1930     }
1931 
1932     if (ethData.vlanId)
1933     {
1934         jsonResponse["EthernetInterfaceType"] = "Virtual";
1935         jsonResponse["VLAN"]["VLANEnable"] = true;
1936         jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId;
1937         jsonResponse["VLAN"]["Tagged"] = true;
1938 
1939         nlohmann::json::array_t relatedInterfaces;
1940         nlohmann::json& parentInterface = relatedInterfaces.emplace_back();
1941         parentInterface["@odata.id"] =
1942             boost::urls::format("/redfish/v1/Managers/bmc/EthernetInterfaces",
1943                                 extractParentInterfaceName(ifaceId));
1944         jsonResponse["Links"]["RelatedInterfaces"] =
1945             std::move(relatedInterfaces);
1946     }
1947     else
1948     {
1949         jsonResponse["EthernetInterfaceType"] = "Physical";
1950     }
1951 
1952     jsonResponse["NameServers"] = ethData.nameServers;
1953     jsonResponse["StaticNameServers"] = ethData.staticNameServers;
1954 
1955     nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"];
1956     nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"];
1957     ipv4Array = nlohmann::json::array();
1958     ipv4StaticArray = nlohmann::json::array();
1959     for (const auto& ipv4Config : ipv4Data)
1960     {
1961         std::string gatewayStr = ipv4Config.gateway;
1962         if (gatewayStr.empty())
1963         {
1964             gatewayStr = "0.0.0.0";
1965         }
1966         nlohmann::json::object_t ipv4;
1967         ipv4["AddressOrigin"] = ipv4Config.origin;
1968         ipv4["SubnetMask"] = ipv4Config.netmask;
1969         ipv4["Address"] = ipv4Config.address;
1970         ipv4["Gateway"] = gatewayStr;
1971 
1972         if (ipv4Config.origin == "Static")
1973         {
1974             ipv4StaticArray.push_back(ipv4);
1975         }
1976 
1977         ipv4Array.emplace_back(std::move(ipv4));
1978     }
1979 
1980     std::string ipv6GatewayStr = ethData.ipv6DefaultGateway;
1981     if (ipv6GatewayStr.empty())
1982     {
1983         ipv6GatewayStr = "0:0:0:0:0:0:0:0";
1984     }
1985 
1986     jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr;
1987 
1988     nlohmann::json::array_t ipv6StaticGatewayArray;
1989     for (const auto& ipv6GatewayConfig : ipv6GatewayData)
1990     {
1991         nlohmann::json::object_t ipv6Gateway;
1992         ipv6Gateway["Address"] = ipv6GatewayConfig.gateway;
1993         ipv6Gateway["PrefixLength"] = ipv6GatewayConfig.prefixLength;
1994         ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway));
1995     }
1996     jsonResponse["IPv6StaticDefaultGateways"] =
1997         std::move(ipv6StaticGatewayArray);
1998 
1999     nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"];
2000     nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"];
2001     ipv6Array = nlohmann::json::array();
2002     ipv6StaticArray = nlohmann::json::array();
2003     nlohmann::json& ipv6AddrPolicyTable =
2004         jsonResponse["IPv6AddressPolicyTable"];
2005     ipv6AddrPolicyTable = nlohmann::json::array();
2006     for (const auto& ipv6Config : ipv6Data)
2007     {
2008         nlohmann::json::object_t ipv6;
2009         ipv6["Address"] = ipv6Config.address;
2010         ipv6["PrefixLength"] = ipv6Config.prefixLength;
2011         ipv6["AddressOrigin"] = ipv6Config.origin;
2012 
2013         ipv6Array.emplace_back(std::move(ipv6));
2014         if (ipv6Config.origin == "Static")
2015         {
2016             nlohmann::json::object_t ipv6Static;
2017             ipv6Static["Address"] = ipv6Config.address;
2018             ipv6Static["PrefixLength"] = ipv6Config.prefixLength;
2019             ipv6StaticArray.emplace_back(std::move(ipv6Static));
2020         }
2021     }
2022 }
2023 
2024 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2025                         const std::string& ifaceId,
2026                         const boost::system::error_code& ec,
2027                         const sdbusplus::message_t& m)
2028 {
2029     if (!ec)
2030     {
2031         return;
2032     }
2033     const sd_bus_error* dbusError = m.get_error();
2034     if (dbusError == nullptr)
2035     {
2036         messages::internalError(asyncResp->res);
2037         return;
2038     }
2039     BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
2040 
2041     if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") ==
2042         dbusError->name)
2043     {
2044         messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2045                                    ifaceId);
2046         return;
2047     }
2048     if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") ==
2049         dbusError->name)
2050     {
2051         messages::resourceCannotBeDeleted(asyncResp->res);
2052         return;
2053     }
2054     messages::internalError(asyncResp->res);
2055 }
2056 
2057 inline void afterVlanCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2058                             const std::string& parentInterfaceUri,
2059                             const std::string& vlanInterface,
2060                             const boost::system::error_code& ec,
2061                             const sdbusplus::message_t& m
2062 
2063 )
2064 {
2065     if (ec)
2066     {
2067         const sd_bus_error* dbusError = m.get_error();
2068         if (dbusError == nullptr)
2069         {
2070             messages::internalError(asyncResp->res);
2071             return;
2072         }
2073         BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
2074 
2075         if (std::string_view(
2076                 "xyz.openbmc_project.Common.Error.ResourceNotFound") ==
2077             dbusError->name)
2078         {
2079             messages::propertyValueNotInList(
2080                 asyncResp->res, parentInterfaceUri,
2081                 "Links/RelatedInterfaces/0/@odata.id");
2082             return;
2083         }
2084         if (std::string_view(
2085                 "xyz.openbmc_project.Common.Error.InvalidArgument") ==
2086             dbusError->name)
2087         {
2088             messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface",
2089                                             "Id", vlanInterface);
2090             return;
2091         }
2092         messages::internalError(asyncResp->res);
2093         return;
2094     }
2095 
2096     const boost::urls::url vlanInterfaceUri = boost::urls::format(
2097         "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", vlanInterface);
2098     asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer());
2099 }
2100 
2101 inline void requestEthernetInterfacesRoutes(App& app)
2102 {
2103     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
2104         .privileges(redfish::privileges::getEthernetInterfaceCollection)
2105         .methods(boost::beast::http::verb::get)(
2106             [&app](const crow::Request& req,
2107                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2108         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2109         {
2110             return;
2111         }
2112 
2113         asyncResp->res.jsonValue["@odata.type"] =
2114             "#EthernetInterfaceCollection.EthernetInterfaceCollection";
2115         asyncResp->res.jsonValue["@odata.id"] =
2116             "/redfish/v1/Managers/bmc/EthernetInterfaces";
2117         asyncResp->res.jsonValue["Name"] =
2118             "Ethernet Network Interface Collection";
2119         asyncResp->res.jsonValue["Description"] =
2120             "Collection of EthernetInterfaces for this Manager";
2121 
2122         // Get eth interface list, and call the below callback for JSON
2123         // preparation
2124         getEthernetIfaceList(
2125             [asyncResp](const bool& success,
2126                         const std::vector<std::string>& ifaceList) {
2127             if (!success)
2128             {
2129                 messages::internalError(asyncResp->res);
2130                 return;
2131             }
2132 
2133             nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"];
2134             ifaceArray = nlohmann::json::array();
2135             for (const std::string& ifaceItem : ifaceList)
2136             {
2137                 nlohmann::json::object_t iface;
2138                 iface["@odata.id"] = boost::urls::format(
2139                     "/redfish/v1/Managers/bmc/EthernetInterfaces/{}",
2140                     ifaceItem);
2141                 ifaceArray.push_back(std::move(iface));
2142             }
2143 
2144             asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size();
2145             asyncResp->res.jsonValue["@odata.id"] =
2146                 "/redfish/v1/Managers/bmc/EthernetInterfaces";
2147         });
2148     });
2149 
2150     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
2151         .privileges(redfish::privileges::postEthernetInterfaceCollection)
2152         .methods(boost::beast::http::verb::post)(
2153             [&app](const crow::Request& req,
2154                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2155         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2156         {
2157             return;
2158         }
2159 
2160         bool vlanEnable = false;
2161         uint32_t vlanId = 0;
2162         nlohmann::json::array_t relatedInterfaces;
2163 
2164         if (!json_util::readJsonPatch(req, asyncResp->res, "VLAN/VLANEnable",
2165                                       vlanEnable, "VLAN/VLANId", vlanId,
2166                                       "Links/RelatedInterfaces",
2167                                       relatedInterfaces))
2168         {
2169             return;
2170         }
2171 
2172         if (relatedInterfaces.size() != 1)
2173         {
2174             messages::arraySizeTooLong(asyncResp->res,
2175                                        "Links/RelatedInterfaces",
2176                                        relatedInterfaces.size());
2177             return;
2178         }
2179 
2180         std::string parentInterfaceUri;
2181         if (!json_util::readJson(relatedInterfaces[0], asyncResp->res,
2182                                  "@odata.id", parentInterfaceUri))
2183         {
2184             messages::propertyMissing(asyncResp->res,
2185                                       "Links/RelatedInterfaces/0/@odata.id");
2186             return;
2187         }
2188         BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri);
2189 
2190         boost::system::result<boost::urls::url_view> parsedUri =
2191             boost::urls::parse_relative_ref(parentInterfaceUri);
2192         if (!parsedUri)
2193         {
2194             messages::propertyValueFormatError(
2195                 asyncResp->res, parentInterfaceUri,
2196                 "Links/RelatedInterfaces/0/@odata.id");
2197             return;
2198         }
2199 
2200         std::string parentInterface;
2201         if (!crow::utility::readUrlSegments(
2202                 *parsedUri, "redfish", "v1", "Managers", "bmc",
2203                 "EthernetInterfaces", std::ref(parentInterface)))
2204         {
2205             messages::propertyValueNotInList(
2206                 asyncResp->res, parentInterfaceUri,
2207                 "Links/RelatedInterfaces/0/@odata.id");
2208             return;
2209         }
2210 
2211         if (!vlanEnable)
2212         {
2213             // In OpenBMC implementation, VLANEnable cannot be false on
2214             // create
2215             messages::propertyValueIncorrect(asyncResp->res, "VLAN/VLANEnable",
2216                                              "false");
2217             return;
2218         }
2219 
2220         std::string vlanInterface = parentInterface + "_" +
2221                                     std::to_string(vlanId);
2222         crow::connections::systemBus->async_method_call(
2223             [asyncResp, parentInterfaceUri,
2224              vlanInterface](const boost::system::error_code& ec,
2225                             const sdbusplus::message_t& m) {
2226             afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec,
2227                             m);
2228         },
2229             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
2230             "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface,
2231             vlanId);
2232     });
2233 
2234     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
2235         .privileges(redfish::privileges::getEthernetInterface)
2236         .methods(boost::beast::http::verb::get)(
2237             [&app](const crow::Request& req,
2238                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2239                    const std::string& ifaceId) {
2240         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2241         {
2242             return;
2243         }
2244         getEthernetIfaceData(
2245             ifaceId,
2246             [asyncResp,
2247              ifaceId](const bool& success, const EthernetInterfaceData& ethData,
2248                       const std::vector<IPv4AddressData>& ipv4Data,
2249                       const std::vector<IPv6AddressData>& ipv6Data,
2250                       const std::vector<StaticGatewayData>& ipv6GatewayData) {
2251             if (!success)
2252             {
2253                 // TODO(Pawel)consider distinguish between non
2254                 // existing object, and other errors
2255                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2256                                            ifaceId);
2257                 return;
2258             }
2259 
2260             asyncResp->res.jsonValue["@odata.type"] =
2261                 "#EthernetInterface.v1_9_0.EthernetInterface";
2262             asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface";
2263             asyncResp->res.jsonValue["Description"] =
2264                 "Management Network Interface";
2265 
2266             parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data,
2267                                ipv6GatewayData);
2268         });
2269     });
2270 
2271     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
2272         .privileges(redfish::privileges::patchEthernetInterface)
2273         .methods(boost::beast::http::verb::patch)(
2274             [&app](const crow::Request& req,
2275                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2276                    const std::string& ifaceId) {
2277         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2278         {
2279             return;
2280         }
2281         std::optional<std::string> hostname;
2282         std::optional<std::string> fqdn;
2283         std::optional<std::string> macAddress;
2284         std::optional<std::string> ipv6DefaultGateway;
2285         std::optional<nlohmann::json::array_t> ipv4StaticAddresses;
2286         std::optional<nlohmann::json::array_t> ipv6StaticAddresses;
2287         std::optional<nlohmann::json::array_t> ipv6StaticDefaultGateways;
2288         std::optional<std::vector<std::string>> staticNameServers;
2289         std::optional<nlohmann::json> dhcpv4;
2290         std::optional<nlohmann::json> dhcpv6;
2291         std::optional<bool> ipv6AutoConfigEnabled;
2292         std::optional<bool> interfaceEnabled;
2293         std::optional<size_t> mtuSize;
2294         DHCPParameters v4dhcpParms;
2295         DHCPParameters v6dhcpParms;
2296         // clang-format off
2297         if (!json_util::readJsonPatch(
2298                 req, asyncResp->res,
2299                 "DHCPv4", dhcpv4,
2300                 "DHCPv6", dhcpv6,
2301                 "FQDN", fqdn,
2302                 "HostName", hostname,
2303                 "IPv4StaticAddresses", ipv4StaticAddresses,
2304                 "IPv6DefaultGateway", ipv6DefaultGateway,
2305                 "IPv6StaticAddresses", ipv6StaticAddresses,
2306                 "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways,
2307                 "InterfaceEnabled", interfaceEnabled,
2308                 "MACAddress", macAddress,
2309                 "MTUSize", mtuSize,
2310                 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled,
2311                 "StaticNameServers", staticNameServers
2312                 )
2313             )
2314         {
2315             return;
2316         }
2317         // clang-format on
2318         if (dhcpv4)
2319         {
2320             if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled",
2321                                      v4dhcpParms.dhcpv4Enabled, "UseDNSServers",
2322                                      v4dhcpParms.useDnsServers, "UseNTPServers",
2323                                      v4dhcpParms.useNtpServers, "UseDomainName",
2324                                      v4dhcpParms.useDomainName))
2325             {
2326                 return;
2327             }
2328         }
2329 
2330         if (dhcpv6)
2331         {
2332             if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode",
2333                                      v6dhcpParms.dhcpv6OperatingMode,
2334                                      "UseDNSServers", v6dhcpParms.useDnsServers,
2335                                      "UseNTPServers", v6dhcpParms.useNtpServers,
2336                                      "UseDomainName",
2337                                      v6dhcpParms.useDomainName))
2338             {
2339                 return;
2340             }
2341         }
2342 
2343         // Get single eth interface data, and call the below callback
2344         // for JSON preparation
2345         getEthernetIfaceData(
2346             ifaceId,
2347             [asyncResp, ifaceId, hostname = std::move(hostname),
2348              fqdn = std::move(fqdn), macAddress = std::move(macAddress),
2349              ipv4StaticAddresses = std::move(ipv4StaticAddresses),
2350              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
2351              ipv6StaticAddresses = std::move(ipv6StaticAddresses),
2352              ipv6StaticDefaultGateway = std::move(ipv6StaticDefaultGateways),
2353              staticNameServers = std::move(staticNameServers),
2354              dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize,
2355              ipv6AutoConfigEnabled, v4dhcpParms = std::move(v4dhcpParms),
2356              v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled](
2357                 const bool& success, const EthernetInterfaceData& ethData,
2358                 const std::vector<IPv4AddressData>& ipv4Data,
2359                 const std::vector<IPv6AddressData>& ipv6Data,
2360                 const std::vector<StaticGatewayData>& ipv6GatewayData) {
2361             if (!success)
2362             {
2363                 // ... otherwise return error
2364                 // TODO(Pawel)consider distinguish between non
2365                 // existing object, and other errors
2366                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2367                                            ifaceId);
2368                 return;
2369             }
2370 
2371             if (dhcpv4 || dhcpv6)
2372             {
2373                 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms,
2374                                 asyncResp);
2375             }
2376 
2377             if (hostname)
2378             {
2379                 handleHostnamePatch(*hostname, asyncResp);
2380             }
2381 
2382             if (ipv6AutoConfigEnabled)
2383             {
2384                 handleSLAACAutoConfigPatch(ifaceId, *ipv6AutoConfigEnabled,
2385                                            asyncResp);
2386             }
2387 
2388             if (fqdn)
2389             {
2390                 handleFqdnPatch(ifaceId, *fqdn, asyncResp);
2391             }
2392 
2393             if (macAddress)
2394             {
2395                 handleMACAddressPatch(ifaceId, *macAddress, asyncResp);
2396             }
2397 
2398             if (ipv4StaticAddresses)
2399             {
2400                 // TODO(ed) for some reason the capture of
2401                 // ipv4Addresses above is returning a const value,
2402                 // not a non-const value. This doesn't really work
2403                 // for us, as we need to be able to efficiently move
2404                 // out the intermedia nlohmann::json objects. This
2405                 // makes a copy of the structure, and operates on
2406                 // that, but could be done more efficiently
2407                 nlohmann::json::array_t ipv4Static = *ipv4StaticAddresses;
2408                 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp);
2409             }
2410 
2411             if (staticNameServers)
2412             {
2413                 handleStaticNameServersPatch(ifaceId, *staticNameServers,
2414                                              asyncResp);
2415             }
2416 
2417             if (ipv6DefaultGateway)
2418             {
2419                 messages::propertyNotWritable(asyncResp->res,
2420                                               "IPv6DefaultGateway");
2421             }
2422 
2423             if (ipv6StaticAddresses)
2424             {
2425                 handleIPv6StaticAddressesPatch(ifaceId, *ipv6StaticAddresses,
2426                                                ipv6Data, asyncResp);
2427             }
2428 
2429             if (ipv6StaticDefaultGateway)
2430             {
2431                 handleIPv6DefaultGateway(ifaceId, *ipv6StaticDefaultGateway,
2432                                          ipv6GatewayData, asyncResp);
2433             }
2434 
2435             if (interfaceEnabled)
2436             {
2437                 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled",
2438                                                  *interfaceEnabled, asyncResp);
2439             }
2440 
2441             if (mtuSize)
2442             {
2443                 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp);
2444             }
2445         });
2446     });
2447 
2448     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
2449         .privileges(redfish::privileges::deleteEthernetInterface)
2450         .methods(boost::beast::http::verb::delete_)(
2451             [&app](const crow::Request& req,
2452                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2453                    const std::string& ifaceId) {
2454         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2455         {
2456             return;
2457         }
2458 
2459         crow::connections::systemBus->async_method_call(
2460             [asyncResp, ifaceId](const boost::system::error_code& ec,
2461                                  const sdbusplus::message_t& m) {
2462             afterDelete(asyncResp, ifaceId, ec, m);
2463         },
2464             "xyz.openbmc_project.Network",
2465             std::string("/xyz/openbmc_project/network/") + ifaceId,
2466             "xyz.openbmc_project.Object.Delete", "Delete");
2467     });
2468 }
2469 
2470 } // namespace redfish
2471