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