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