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