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