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