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