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