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 deleteIPv4(const std::string& ifaceId, const std::string& ipHash,
638                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
639 {
640     crow::connections::systemBus->async_method_call(
641         [asyncResp](const boost::system::error_code ec) {
642         if (ec)
643         {
644             messages::internalError(asyncResp->res);
645         }
646         },
647         "xyz.openbmc_project.Network",
648         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
649         "xyz.openbmc_project.Object.Delete", "Delete");
650 }
651 
652 inline void updateIPv4DefaultGateway(
653     const std::string& ifaceId, const std::string& gateway,
654     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
655 {
656     crow::connections::systemBus->async_method_call(
657         [asyncResp](const boost::system::error_code ec) {
658         if (ec)
659         {
660             messages::internalError(asyncResp->res);
661             return;
662         }
663         asyncResp->res.result(boost::beast::http::status::no_content);
664         },
665         "xyz.openbmc_project.Network",
666         "/xyz/openbmc_project/network/" + ifaceId,
667         "org.freedesktop.DBus.Properties", "Set",
668         "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway",
669         dbus::utility::DbusVariantType(gateway));
670 }
671 /**
672  * @brief Creates a static IPv4 entry
673  *
674  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
675  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
676  * @param[in] gateway      IPv4 address of this interfaces gateway
677  * @param[in] address      IPv4 address to assign to this interface
678  * @param[io] asyncResp    Response object that will be returned to client
679  *
680  * @return None
681  */
682 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength,
683                        const std::string& gateway, const std::string& address,
684                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
685 {
686     auto createIpHandler =
687         [asyncResp, ifaceId, gateway](const boost::system::error_code ec) {
688         if (ec)
689         {
690             messages::internalError(asyncResp->res);
691             return;
692         }
693         updateIPv4DefaultGateway(ifaceId, gateway, asyncResp);
694     };
695 
696     crow::connections::systemBus->async_method_call(
697         std::move(createIpHandler), "xyz.openbmc_project.Network",
698         "/xyz/openbmc_project/network/" + ifaceId,
699         "xyz.openbmc_project.Network.IP.Create", "IP",
700         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength,
701         gateway);
702 }
703 
704 /**
705  * @brief Deletes the IPv4 entry for this interface and creates a replacement
706  * static IPv4 entry
707  *
708  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
709  * @param[in] id           The unique hash entry identifying the DBus entry
710  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
711  * @param[in] gateway      IPv4 address of this interfaces gateway
712  * @param[in] address      IPv4 address to assign to this interface
713  * @param[io] asyncResp    Response object that will be returned to client
714  *
715  * @return None
716  */
717 inline void
718     deleteAndCreateIPv4(const std::string& ifaceId, const std::string& id,
719                         uint8_t prefixLength, const std::string& gateway,
720                         const std::string& address,
721                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
722 {
723     crow::connections::systemBus->async_method_call(
724         [asyncResp, ifaceId, address, prefixLength,
725          gateway](const boost::system::error_code ec) {
726         if (ec)
727         {
728             messages::internalError(asyncResp->res);
729             return;
730         }
731 
732         crow::connections::systemBus->async_method_call(
733             [asyncResp, ifaceId, gateway](const boost::system::error_code ec2) {
734             if (ec2)
735             {
736                 messages::internalError(asyncResp->res);
737                 return;
738             }
739             updateIPv4DefaultGateway(ifaceId, gateway, asyncResp);
740             },
741             "xyz.openbmc_project.Network",
742             "/xyz/openbmc_project/network/" + ifaceId,
743             "xyz.openbmc_project.Network.IP.Create", "IP",
744             "xyz.openbmc_project.Network.IP.Protocol.IPv4", address,
745             prefixLength, gateway);
746         },
747         "xyz.openbmc_project.Network",
748         +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id,
749         "xyz.openbmc_project.Object.Delete", "Delete");
750 }
751 
752 /**
753  * @brief Deletes given IPv6
754  *
755  * @param[in] ifaceId     Id of interface whose IP should be deleted
756  * @param[in] ipHash      DBus Hash id of IP that should be deleted
757  * @param[io] asyncResp   Response object that will be returned to client
758  *
759  * @return None
760  */
761 inline void deleteIPv6(const std::string& ifaceId, const std::string& ipHash,
762                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
763 {
764     crow::connections::systemBus->async_method_call(
765         [asyncResp](const boost::system::error_code ec) {
766         if (ec)
767         {
768             messages::internalError(asyncResp->res);
769         }
770         },
771         "xyz.openbmc_project.Network",
772         "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash,
773         "xyz.openbmc_project.Object.Delete", "Delete");
774 }
775 
776 /**
777  * @brief Deletes the IPv6 entry for this interface and creates a replacement
778  * static IPv6 entry
779  *
780  * @param[in] ifaceId      Id of interface upon which to create the IPv6 entry
781  * @param[in] id           The unique hash entry identifying the DBus entry
782  * @param[in] prefixLength IPv6 prefix syntax for the subnet mask
783  * @param[in] address      IPv6 address to assign to this interface
784  * @param[io] asyncResp    Response object that will be returned to client
785  *
786  * @return None
787  */
788 inline void
789     deleteAndCreateIPv6(const std::string& ifaceId, const std::string& id,
790                         uint8_t prefixLength, const std::string& address,
791                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
792 {
793     crow::connections::systemBus->async_method_call(
794         [asyncResp, ifaceId, address,
795          prefixLength](const boost::system::error_code ec) {
796         if (ec)
797         {
798             messages::internalError(asyncResp->res);
799         }
800         crow::connections::systemBus->async_method_call(
801             [asyncResp](const boost::system::error_code ec2) {
802             if (ec2)
803             {
804                 messages::internalError(asyncResp->res);
805             }
806             },
807             "xyz.openbmc_project.Network",
808             "/xyz/openbmc_project/network/" + ifaceId,
809             "xyz.openbmc_project.Network.IP.Create", "IP",
810             "xyz.openbmc_project.Network.IP.Protocol.IPv6", address,
811             prefixLength, "");
812         },
813         "xyz.openbmc_project.Network",
814         +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id,
815         "xyz.openbmc_project.Object.Delete", "Delete");
816 }
817 
818 /**
819  * @brief Creates IPv6 with given data
820  *
821  * @param[in] ifaceId      Id of interface whose IP should be added
822  * @param[in] prefixLength Prefix length that needs to be added
823  * @param[in] address      IP address that needs to be added
824  * @param[io] asyncResp    Response object that will be returned to client
825  *
826  * @return None
827  */
828 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength,
829                        const std::string& address,
830                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
831 {
832     auto createIpHandler = [asyncResp](const boost::system::error_code ec) {
833         if (ec)
834         {
835             messages::internalError(asyncResp->res);
836         }
837     };
838     // Passing null for gateway, as per redfish spec IPv6StaticAddresses object
839     // does not have associated gateway property
840     crow::connections::systemBus->async_method_call(
841         std::move(createIpHandler), "xyz.openbmc_project.Network",
842         "/xyz/openbmc_project/network/" + ifaceId,
843         "xyz.openbmc_project.Network.IP.Create", "IP",
844         "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength,
845         "");
846 }
847 
848 /**
849  * Function that retrieves all properties for given Ethernet Interface
850  * Object
851  * from EntityManager Network Manager
852  * @param ethiface_id a eth interface id to query on DBus
853  * @param callback a function that shall be called to convert Dbus output
854  * into JSON
855  */
856 template <typename CallbackFunc>
857 void getEthernetIfaceData(const std::string& ethifaceId,
858                           CallbackFunc&& callback)
859 {
860     crow::connections::systemBus->async_method_call(
861         [ethifaceId{std::string{ethifaceId}},
862          callback{std::forward<CallbackFunc>(callback)}](
863             const boost::system::error_code errorCode,
864             const dbus::utility::ManagedObjectType& resp) {
865         EthernetInterfaceData ethData{};
866         boost::container::flat_set<IPv4AddressData> ipv4Data;
867         boost::container::flat_set<IPv6AddressData> ipv6Data;
868 
869         if (errorCode)
870         {
871             callback(false, ethData, ipv4Data, ipv6Data);
872             return;
873         }
874 
875         bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData);
876         if (!found)
877         {
878             callback(false, ethData, ipv4Data, ipv6Data);
879             return;
880         }
881 
882         extractIPData(ethifaceId, resp, ipv4Data);
883         // Fix global GW
884         for (IPv4AddressData& ipv4 : ipv4Data)
885         {
886             if (((ipv4.linktype == LinkType::Global) &&
887                  (ipv4.gateway == "0.0.0.0")) ||
888                 (ipv4.origin == "DHCP") || (ipv4.origin == "Static"))
889             {
890                 ipv4.gateway = ethData.defaultGateway;
891             }
892         }
893 
894         extractIPV6Data(ethifaceId, resp, ipv6Data);
895         // Finally make a callback with useful data
896         callback(true, ethData, ipv4Data, ipv6Data);
897         },
898         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
899         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
900 }
901 
902 /**
903  * Function that retrieves all Ethernet Interfaces available through Network
904  * Manager
905  * @param callback a function that shall be called to convert Dbus output
906  * into JSON.
907  */
908 template <typename CallbackFunc>
909 void getEthernetIfaceList(CallbackFunc&& callback)
910 {
911     crow::connections::systemBus->async_method_call(
912         [callback{std::forward<CallbackFunc>(callback)}](
913             const boost::system::error_code errorCode,
914             dbus::utility::ManagedObjectType& resp) {
915         // Callback requires vector<string> to retrieve all available
916         // ethernet interfaces
917         boost::container::flat_set<std::string> ifaceList;
918         ifaceList.reserve(resp.size());
919         if (errorCode)
920         {
921             callback(false, ifaceList);
922             return;
923         }
924 
925         // Iterate over all retrieved ObjectPaths.
926         for (const auto& objpath : resp)
927         {
928             // And all interfaces available for certain ObjectPath.
929             for (const auto& interface : objpath.second)
930             {
931                 // If interface is
932                 // xyz.openbmc_project.Network.EthernetInterface, this is
933                 // what we're looking for.
934                 if (interface.first ==
935                     "xyz.openbmc_project.Network.EthernetInterface")
936                 {
937                     std::string ifaceId = objpath.first.filename();
938                     if (ifaceId.empty())
939                     {
940                         continue;
941                     }
942                     // and put it into output vector.
943                     ifaceList.emplace(ifaceId);
944                 }
945             }
946         }
947         // Finally make a callback with useful data
948         callback(true, ifaceList);
949         },
950         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
951         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
952 }
953 
954 inline void
955     handleHostnamePatch(const std::string& hostname,
956                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
957 {
958     // SHOULD handle host names of up to 255 characters(RFC 1123)
959     if (hostname.length() > 255)
960     {
961         messages::propertyValueFormatError(asyncResp->res, hostname,
962                                            "HostName");
963         return;
964     }
965     crow::connections::systemBus->async_method_call(
966         [asyncResp](const boost::system::error_code ec) {
967         if (ec)
968         {
969             messages::internalError(asyncResp->res);
970         }
971         },
972         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config",
973         "org.freedesktop.DBus.Properties", "Set",
974         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
975         dbus::utility::DbusVariantType(hostname));
976 }
977 
978 inline void
979     handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize,
980                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
981 {
982     sdbusplus::message::object_path objPath =
983         "/xyz/openbmc_project/network/" + ifaceId;
984     crow::connections::systemBus->async_method_call(
985         [asyncResp](const boost::system::error_code ec) {
986         if (ec)
987         {
988             messages::internalError(asyncResp->res);
989         }
990         },
991         "xyz.openbmc_project.Network", objPath,
992         "org.freedesktop.DBus.Properties", "Set",
993         "xyz.openbmc_project.Network.EthernetInterface", "MTU",
994         std::variant<size_t>(mtuSize));
995 }
996 
997 inline void
998     handleDomainnamePatch(const std::string& ifaceId,
999                           const std::string& domainname,
1000                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1001 {
1002     std::vector<std::string> vectorDomainname = {domainname};
1003     crow::connections::systemBus->async_method_call(
1004         [asyncResp](const boost::system::error_code ec) {
1005         if (ec)
1006         {
1007             messages::internalError(asyncResp->res);
1008         }
1009         },
1010         "xyz.openbmc_project.Network",
1011         "/xyz/openbmc_project/network/" + ifaceId,
1012         "org.freedesktop.DBus.Properties", "Set",
1013         "xyz.openbmc_project.Network.EthernetInterface", "DomainName",
1014         dbus::utility::DbusVariantType(vectorDomainname));
1015 }
1016 
1017 inline bool isHostnameValid(const std::string& hostname)
1018 {
1019     // A valid host name can never have the dotted-decimal form (RFC 1123)
1020     if (std::all_of(hostname.begin(), hostname.end(), ::isdigit))
1021     {
1022         return false;
1023     }
1024     // Each label(hostname/subdomains) within a valid FQDN
1025     // MUST handle host names of up to 63 characters (RFC 1123)
1026     // labels cannot start or end with hyphens (RFC 952)
1027     // labels can start with numbers (RFC 1123)
1028     const std::regex pattern(
1029         "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
1030 
1031     return std::regex_match(hostname, pattern);
1032 }
1033 
1034 inline bool isDomainnameValid(const std::string& domainname)
1035 {
1036     // Can have multiple subdomains
1037     // Top Level Domain's min length is 2 character
1038     const std::regex pattern(
1039         "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$");
1040 
1041     return std::regex_match(domainname, pattern);
1042 }
1043 
1044 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn,
1045                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1046 {
1047     // Total length of FQDN must not exceed 255 characters(RFC 1035)
1048     if (fqdn.length() > 255)
1049     {
1050         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1051         return;
1052     }
1053 
1054     size_t pos = fqdn.find('.');
1055     if (pos == std::string::npos)
1056     {
1057         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1058         return;
1059     }
1060 
1061     std::string hostname;
1062     std::string domainname;
1063     domainname = (fqdn).substr(pos + 1);
1064     hostname = (fqdn).substr(0, pos);
1065 
1066     if (!isHostnameValid(hostname) || !isDomainnameValid(domainname))
1067     {
1068         messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1069         return;
1070     }
1071 
1072     handleHostnamePatch(hostname, asyncResp);
1073     handleDomainnamePatch(ifaceId, domainname, asyncResp);
1074 }
1075 
1076 inline void
1077     handleMACAddressPatch(const std::string& ifaceId,
1078                           const std::string& macAddress,
1079                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1080 {
1081     static constexpr std::string_view dbusNotAllowedError =
1082         "xyz.openbmc_project.Common.Error.NotAllowed";
1083 
1084     crow::connections::systemBus->async_method_call(
1085         [asyncResp, macAddress](const boost::system::error_code ec,
1086                                 const sdbusplus::message_t& msg) {
1087         if (ec)
1088         {
1089             const sd_bus_error* err = msg.get_error();
1090             if (err == nullptr)
1091             {
1092                 messages::internalError(asyncResp->res);
1093                 return;
1094             }
1095             if (err->name == dbusNotAllowedError)
1096             {
1097                 messages::propertyNotWritable(asyncResp->res, "MACAddress");
1098                 return;
1099             }
1100             messages::internalError(asyncResp->res);
1101             return;
1102         }
1103         },
1104         "xyz.openbmc_project.Network",
1105         "/xyz/openbmc_project/network/" + ifaceId,
1106         "org.freedesktop.DBus.Properties", "Set",
1107         "xyz.openbmc_project.Network.MACAddress", "MACAddress",
1108         dbus::utility::DbusVariantType(macAddress));
1109 }
1110 
1111 inline void setDHCPEnabled(const std::string& ifaceId,
1112                            const std::string& propertyName, const bool v4Value,
1113                            const bool v6Value,
1114                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1115 {
1116     const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value);
1117     crow::connections::systemBus->async_method_call(
1118         [asyncResp](const boost::system::error_code ec) {
1119         if (ec)
1120         {
1121             BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1122             messages::internalError(asyncResp->res);
1123             return;
1124         }
1125         messages::success(asyncResp->res);
1126         },
1127         "xyz.openbmc_project.Network",
1128         "/xyz/openbmc_project/network/" + ifaceId,
1129         "org.freedesktop.DBus.Properties", "Set",
1130         "xyz.openbmc_project.Network.EthernetInterface", propertyName,
1131         dbus::utility::DbusVariantType{dhcp});
1132 }
1133 
1134 inline void setEthernetInterfaceBoolProperty(
1135     const std::string& ifaceId, const std::string& propertyName,
1136     const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1137 {
1138     crow::connections::systemBus->async_method_call(
1139         [asyncResp](const boost::system::error_code ec) {
1140         if (ec)
1141         {
1142             BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1143             messages::internalError(asyncResp->res);
1144             return;
1145         }
1146         },
1147         "xyz.openbmc_project.Network",
1148         "/xyz/openbmc_project/network/" + ifaceId,
1149         "org.freedesktop.DBus.Properties", "Set",
1150         "xyz.openbmc_project.Network.EthernetInterface", propertyName,
1151         dbus::utility::DbusVariantType{value});
1152 }
1153 
1154 inline void setDHCPv4Config(const std::string& propertyName, const bool& value,
1155                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1156 {
1157     BMCWEB_LOG_DEBUG << propertyName << " = " << value;
1158     crow::connections::systemBus->async_method_call(
1159         [asyncResp](const boost::system::error_code ec) {
1160         if (ec)
1161         {
1162             BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1163             messages::internalError(asyncResp->res);
1164             return;
1165         }
1166         },
1167         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/dhcp",
1168         "org.freedesktop.DBus.Properties", "Set",
1169         "xyz.openbmc_project.Network.DHCPConfiguration", propertyName,
1170         dbus::utility::DbusVariantType{value});
1171 }
1172 
1173 inline void handleDHCPPatch(const std::string& ifaceId,
1174                             const EthernetInterfaceData& ethData,
1175                             const DHCPParameters& v4dhcpParms,
1176                             const DHCPParameters& v6dhcpParms,
1177                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1178 {
1179     bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1180     bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false);
1181 
1182     bool nextv4DHCPState =
1183         v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active;
1184 
1185     bool nextv6DHCPState{};
1186     if (v6dhcpParms.dhcpv6OperatingMode)
1187     {
1188         if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") &&
1189             (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") &&
1190             (*v6dhcpParms.dhcpv6OperatingMode != "Disabled"))
1191         {
1192             messages::propertyValueFormatError(asyncResp->res,
1193                                                *v6dhcpParms.dhcpv6OperatingMode,
1194                                                "OperatingMode");
1195             return;
1196         }
1197         nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful");
1198     }
1199     else
1200     {
1201         nextv6DHCPState = ipv6Active;
1202     }
1203 
1204     bool nextDNS{};
1205     if (v4dhcpParms.useDnsServers && v6dhcpParms.useDnsServers)
1206     {
1207         if (*v4dhcpParms.useDnsServers != *v6dhcpParms.useDnsServers)
1208         {
1209             messages::generalError(asyncResp->res);
1210             return;
1211         }
1212         nextDNS = *v4dhcpParms.useDnsServers;
1213     }
1214     else if (v4dhcpParms.useDnsServers)
1215     {
1216         nextDNS = *v4dhcpParms.useDnsServers;
1217     }
1218     else if (v6dhcpParms.useDnsServers)
1219     {
1220         nextDNS = *v6dhcpParms.useDnsServers;
1221     }
1222     else
1223     {
1224         nextDNS = ethData.dnsEnabled;
1225     }
1226 
1227     bool nextNTP{};
1228     if (v4dhcpParms.useNtpServers && v6dhcpParms.useNtpServers)
1229     {
1230         if (*v4dhcpParms.useNtpServers != *v6dhcpParms.useNtpServers)
1231         {
1232             messages::generalError(asyncResp->res);
1233             return;
1234         }
1235         nextNTP = *v4dhcpParms.useNtpServers;
1236     }
1237     else if (v4dhcpParms.useNtpServers)
1238     {
1239         nextNTP = *v4dhcpParms.useNtpServers;
1240     }
1241     else if (v6dhcpParms.useNtpServers)
1242     {
1243         nextNTP = *v6dhcpParms.useNtpServers;
1244     }
1245     else
1246     {
1247         nextNTP = ethData.ntpEnabled;
1248     }
1249 
1250     bool nextUseDomain{};
1251     if (v4dhcpParms.useDomainName && v6dhcpParms.useDomainName)
1252     {
1253         if (*v4dhcpParms.useDomainName != *v6dhcpParms.useDomainName)
1254         {
1255             messages::generalError(asyncResp->res);
1256             return;
1257         }
1258         nextUseDomain = *v4dhcpParms.useDomainName;
1259     }
1260     else if (v4dhcpParms.useDomainName)
1261     {
1262         nextUseDomain = *v4dhcpParms.useDomainName;
1263     }
1264     else if (v6dhcpParms.useDomainName)
1265     {
1266         nextUseDomain = *v6dhcpParms.useDomainName;
1267     }
1268     else
1269     {
1270         nextUseDomain = ethData.hostNameEnabled;
1271     }
1272 
1273     BMCWEB_LOG_DEBUG << "set DHCPEnabled...";
1274     setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState,
1275                    asyncResp);
1276     BMCWEB_LOG_DEBUG << "set DNSEnabled...";
1277     setDHCPv4Config("DNSEnabled", nextDNS, asyncResp);
1278     BMCWEB_LOG_DEBUG << "set NTPEnabled...";
1279     setDHCPv4Config("NTPEnabled", nextNTP, asyncResp);
1280     BMCWEB_LOG_DEBUG << "set HostNameEnabled...";
1281     setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp);
1282 }
1283 
1284 inline boost::container::flat_set<IPv4AddressData>::const_iterator
1285     getNextStaticIpEntry(
1286         const boost::container::flat_set<IPv4AddressData>::const_iterator& head,
1287         const boost::container::flat_set<IPv4AddressData>::const_iterator& end)
1288 {
1289     return std::find_if(head, end, [](const IPv4AddressData& value) {
1290         return value.origin == "Static";
1291     });
1292 }
1293 
1294 inline boost::container::flat_set<IPv6AddressData>::const_iterator
1295     getNextStaticIpEntry(
1296         const boost::container::flat_set<IPv6AddressData>::const_iterator& head,
1297         const boost::container::flat_set<IPv6AddressData>::const_iterator& end)
1298 {
1299     return std::find_if(head, end, [](const IPv6AddressData& value) {
1300         return value.origin == "Static";
1301     });
1302 }
1303 
1304 inline void handleIPv4StaticPatch(
1305     const std::string& ifaceId, nlohmann::json& input,
1306     const boost::container::flat_set<IPv4AddressData>& ipv4Data,
1307     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1308 {
1309     if ((!input.is_array()) || input.empty())
1310     {
1311         messages::propertyValueTypeError(
1312             asyncResp->res,
1313             input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace),
1314             "IPv4StaticAddresses");
1315         return;
1316     }
1317 
1318     unsigned entryIdx = 1;
1319     // Find the first static IP address currently active on the NIC and
1320     // match it to the first JSON element in the IPv4StaticAddresses array.
1321     // Match each subsequent JSON element to the next static IP programmed
1322     // into the NIC.
1323     boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry =
1324         getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend());
1325 
1326     for (nlohmann::json& thisJson : input)
1327     {
1328         std::string pathString =
1329             "IPv4StaticAddresses/" + std::to_string(entryIdx);
1330 
1331         if (!thisJson.is_null() && !thisJson.empty())
1332         {
1333             std::optional<std::string> address;
1334             std::optional<std::string> subnetMask;
1335             std::optional<std::string> gateway;
1336 
1337             if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1338                                      address, "SubnetMask", subnetMask,
1339                                      "Gateway", gateway))
1340             {
1341                 messages::propertyValueFormatError(
1342                     asyncResp->res,
1343                     thisJson.dump(2, ' ', true,
1344                                   nlohmann::json::error_handler_t::replace),
1345                     pathString);
1346                 return;
1347             }
1348 
1349             // Find the address/subnet/gateway values. Any values that are
1350             // not explicitly provided are assumed to be unmodified from the
1351             // current state of the interface. Merge existing state into the
1352             // current request.
1353             const std::string* addr = nullptr;
1354             const std::string* gw = nullptr;
1355             uint8_t prefixLength = 0;
1356             bool errorInEntry = false;
1357             if (address)
1358             {
1359                 if (ip_util::ipv4VerifyIpAndGetBitcount(*address))
1360                 {
1361                     addr = &(*address);
1362                 }
1363                 else
1364                 {
1365                     messages::propertyValueFormatError(asyncResp->res, *address,
1366                                                        pathString + "/Address");
1367                     errorInEntry = true;
1368                 }
1369             }
1370             else if (nicIpEntry != ipv4Data.cend())
1371             {
1372                 addr = &(nicIpEntry->address);
1373             }
1374             else
1375             {
1376                 messages::propertyMissing(asyncResp->res,
1377                                           pathString + "/Address");
1378                 errorInEntry = true;
1379             }
1380 
1381             if (subnetMask)
1382             {
1383                 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask,
1384                                                          &prefixLength))
1385                 {
1386                     messages::propertyValueFormatError(
1387                         asyncResp->res, *subnetMask,
1388                         pathString + "/SubnetMask");
1389                     errorInEntry = true;
1390                 }
1391             }
1392             else if (nicIpEntry != ipv4Data.cend())
1393             {
1394                 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask,
1395                                                          &prefixLength))
1396                 {
1397                     messages::propertyValueFormatError(
1398                         asyncResp->res, nicIpEntry->netmask,
1399                         pathString + "/SubnetMask");
1400                     errorInEntry = true;
1401                 }
1402             }
1403             else
1404             {
1405                 messages::propertyMissing(asyncResp->res,
1406                                           pathString + "/SubnetMask");
1407                 errorInEntry = true;
1408             }
1409 
1410             if (gateway)
1411             {
1412                 if (ip_util::ipv4VerifyIpAndGetBitcount(*gateway))
1413                 {
1414                     gw = &(*gateway);
1415                 }
1416                 else
1417                 {
1418                     messages::propertyValueFormatError(asyncResp->res, *gateway,
1419                                                        pathString + "/Gateway");
1420                     errorInEntry = true;
1421                 }
1422             }
1423             else if (nicIpEntry != ipv4Data.cend())
1424             {
1425                 gw = &nicIpEntry->gateway;
1426             }
1427             else
1428             {
1429                 messages::propertyMissing(asyncResp->res,
1430                                           pathString + "/Gateway");
1431                 errorInEntry = true;
1432             }
1433 
1434             if (errorInEntry)
1435             {
1436                 return;
1437             }
1438 
1439             if (nicIpEntry != ipv4Data.cend())
1440             {
1441                 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw,
1442                                     *addr, asyncResp);
1443                 nicIpEntry =
1444                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1445             }
1446             else
1447             {
1448                 createIPv4(ifaceId, prefixLength, *gateway, *address,
1449                            asyncResp);
1450             }
1451             entryIdx++;
1452         }
1453         else
1454         {
1455             if (nicIpEntry == ipv4Data.cend())
1456             {
1457                 // Requesting a DELETE/DO NOT MODIFY action for an item
1458                 // that isn't present on the eth(n) interface. Input JSON is
1459                 // in error, so bail out.
1460                 if (thisJson.is_null())
1461                 {
1462                     messages::resourceCannotBeDeleted(asyncResp->res);
1463                     return;
1464                 }
1465                 messages::propertyValueFormatError(
1466                     asyncResp->res,
1467                     thisJson.dump(2, ' ', true,
1468                                   nlohmann::json::error_handler_t::replace),
1469                     pathString);
1470                 return;
1471             }
1472 
1473             if (thisJson.is_null())
1474             {
1475                 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp);
1476             }
1477             if (nicIpEntry != ipv4Data.cend())
1478             {
1479                 nicIpEntry =
1480                     getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1481             }
1482             entryIdx++;
1483         }
1484     }
1485 }
1486 
1487 inline void handleStaticNameServersPatch(
1488     const std::string& ifaceId,
1489     const std::vector<std::string>& updatedStaticNameServers,
1490     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1491 {
1492     crow::connections::systemBus->async_method_call(
1493         [asyncResp](const boost::system::error_code ec) {
1494         if (ec)
1495         {
1496             messages::internalError(asyncResp->res);
1497             return;
1498         }
1499         },
1500         "xyz.openbmc_project.Network",
1501         "/xyz/openbmc_project/network/" + ifaceId,
1502         "org.freedesktop.DBus.Properties", "Set",
1503         "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers",
1504         dbus::utility::DbusVariantType{updatedStaticNameServers});
1505 }
1506 
1507 inline void handleIPv6StaticAddressesPatch(
1508     const std::string& ifaceId, const nlohmann::json& input,
1509     const boost::container::flat_set<IPv6AddressData>& ipv6Data,
1510     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1511 {
1512     if (!input.is_array() || input.empty())
1513     {
1514         messages::propertyValueTypeError(
1515             asyncResp->res,
1516             input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace),
1517             "IPv6StaticAddresses");
1518         return;
1519     }
1520     size_t entryIdx = 1;
1521     boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry =
1522         getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend());
1523     for (const nlohmann::json& thisJson : input)
1524     {
1525         std::string pathString =
1526             "IPv6StaticAddresses/" + std::to_string(entryIdx);
1527 
1528         if (!thisJson.is_null() && !thisJson.empty())
1529         {
1530             std::optional<std::string> address;
1531             std::optional<uint8_t> prefixLength;
1532             nlohmann::json thisJsonCopy = thisJson;
1533             if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address",
1534                                      address, "PrefixLength", prefixLength))
1535             {
1536                 messages::propertyValueFormatError(
1537                     asyncResp->res,
1538                     thisJson.dump(2, ' ', true,
1539                                   nlohmann::json::error_handler_t::replace),
1540                     pathString);
1541                 return;
1542             }
1543 
1544             const std::string* addr = nullptr;
1545             uint8_t prefix = 0;
1546 
1547             // Find the address and prefixLength values. Any values that are
1548             // not explicitly provided are assumed to be unmodified from the
1549             // current state of the interface. Merge existing state into the
1550             // current request.
1551             if (address)
1552             {
1553                 addr = &(*address);
1554             }
1555             else if (nicIpEntry != ipv6Data.end())
1556             {
1557                 addr = &(nicIpEntry->address);
1558             }
1559             else
1560             {
1561                 messages::propertyMissing(asyncResp->res,
1562                                           pathString + "/Address");
1563                 return;
1564             }
1565 
1566             if (prefixLength)
1567             {
1568                 prefix = *prefixLength;
1569             }
1570             else if (nicIpEntry != ipv6Data.end())
1571             {
1572                 prefix = nicIpEntry->prefixLength;
1573             }
1574             else
1575             {
1576                 messages::propertyMissing(asyncResp->res,
1577                                           pathString + "/PrefixLength");
1578                 return;
1579             }
1580 
1581             if (nicIpEntry != ipv6Data.end())
1582             {
1583                 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr,
1584                                     asyncResp);
1585                 nicIpEntry =
1586                     getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1587             }
1588             else
1589             {
1590                 createIPv6(ifaceId, *prefixLength, *addr, asyncResp);
1591             }
1592             entryIdx++;
1593         }
1594         else
1595         {
1596             if (nicIpEntry == ipv6Data.end())
1597             {
1598                 // Requesting a DELETE/DO NOT MODIFY action for an item
1599                 // that isn't present on the eth(n) interface. Input JSON is
1600                 // in error, so bail out.
1601                 if (thisJson.is_null())
1602                 {
1603                     messages::resourceCannotBeDeleted(asyncResp->res);
1604                     return;
1605                 }
1606                 messages::propertyValueFormatError(
1607                     asyncResp->res,
1608                     thisJson.dump(2, ' ', true,
1609                                   nlohmann::json::error_handler_t::replace),
1610                     pathString);
1611                 return;
1612             }
1613 
1614             if (thisJson.is_null())
1615             {
1616                 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp);
1617             }
1618             if (nicIpEntry != ipv6Data.cend())
1619             {
1620                 nicIpEntry =
1621                     getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1622             }
1623             entryIdx++;
1624         }
1625     }
1626 }
1627 
1628 inline void parseInterfaceData(
1629     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1630     const std::string& ifaceId, const EthernetInterfaceData& ethData,
1631     const boost::container::flat_set<IPv4AddressData>& ipv4Data,
1632     const boost::container::flat_set<IPv6AddressData>& ipv6Data)
1633 {
1634     constexpr std::array<std::string_view, 1> inventoryForEthernet = {
1635         "xyz.openbmc_project.Inventory.Item.Ethernet"};
1636 
1637     nlohmann::json& jsonResponse = asyncResp->res.jsonValue;
1638     jsonResponse["Id"] = ifaceId;
1639     jsonResponse["@odata.id"] = crow::utility::urlFromPieces(
1640         "redfish", "v1", "Managers", "bmc", "EthernetInterfaces", ifaceId);
1641     jsonResponse["InterfaceEnabled"] = ethData.nicEnabled;
1642 
1643     auto health = std::make_shared<HealthPopulate>(asyncResp);
1644 
1645     dbus::utility::getSubTreePaths(
1646         "/", 0, inventoryForEthernet,
1647         [health](const boost::system::error_code& ec,
1648                  const dbus::utility::MapperGetSubTreePathsResponse& resp) {
1649         if (ec)
1650         {
1651             return;
1652         }
1653 
1654         health->inventory = resp;
1655         });
1656 
1657     health->populate();
1658 
1659     if (ethData.nicEnabled)
1660     {
1661         jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown";
1662         jsonResponse["Status"]["State"] = "Enabled";
1663     }
1664     else
1665     {
1666         jsonResponse["LinkStatus"] = "NoLink";
1667         jsonResponse["Status"]["State"] = "Disabled";
1668     }
1669 
1670     jsonResponse["SpeedMbps"] = ethData.speed;
1671     jsonResponse["MTUSize"] = ethData.mtuSize;
1672     jsonResponse["MACAddress"] = ethData.macAddress;
1673     jsonResponse["DHCPv4"]["DHCPEnabled"] =
1674         translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1675     jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpEnabled;
1676     jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsEnabled;
1677     jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNameEnabled;
1678 
1679     jsonResponse["DHCPv6"]["OperatingMode"] =
1680         translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Stateful"
1681                                                                : "Disabled";
1682     jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpEnabled;
1683     jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsEnabled;
1684     jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNameEnabled;
1685 
1686     if (!ethData.hostName.empty())
1687     {
1688         jsonResponse["HostName"] = ethData.hostName;
1689 
1690         // When domain name is empty then it means, that it is a network
1691         // without domain names, and the host name itself must be treated as
1692         // FQDN
1693         std::string fqdn = ethData.hostName;
1694         if (!ethData.domainnames.empty())
1695         {
1696             fqdn += "." + ethData.domainnames[0];
1697         }
1698         jsonResponse["FQDN"] = fqdn;
1699     }
1700 
1701     jsonResponse["VLANs"]["@odata.id"] =
1702         crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc",
1703                                      "EthernetInterfaces", ifaceId, "VLANs");
1704 
1705     jsonResponse["NameServers"] = ethData.nameServers;
1706     jsonResponse["StaticNameServers"] = ethData.staticNameServers;
1707 
1708     nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"];
1709     nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"];
1710     ipv4Array = nlohmann::json::array();
1711     ipv4StaticArray = nlohmann::json::array();
1712     for (const auto& ipv4Config : ipv4Data)
1713     {
1714         std::string gatewayStr = ipv4Config.gateway;
1715         if (gatewayStr.empty())
1716         {
1717             gatewayStr = "0.0.0.0";
1718         }
1719         nlohmann::json::object_t ipv4;
1720         ipv4["AddressOrigin"] = ipv4Config.origin;
1721         ipv4["SubnetMask"] = ipv4Config.netmask;
1722         ipv4["Address"] = ipv4Config.address;
1723         ipv4["Gateway"] = gatewayStr;
1724 
1725         if (ipv4Config.origin == "Static")
1726         {
1727             ipv4StaticArray.push_back(ipv4);
1728         }
1729 
1730         ipv4Array.push_back(std::move(ipv4));
1731     }
1732 
1733     std::string ipv6GatewayStr = ethData.ipv6DefaultGateway;
1734     if (ipv6GatewayStr.empty())
1735     {
1736         ipv6GatewayStr = "0:0:0:0:0:0:0:0";
1737     }
1738 
1739     jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr;
1740 
1741     nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"];
1742     nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"];
1743     ipv6Array = nlohmann::json::array();
1744     ipv6StaticArray = nlohmann::json::array();
1745     nlohmann::json& ipv6AddrPolicyTable =
1746         jsonResponse["IPv6AddressPolicyTable"];
1747     ipv6AddrPolicyTable = nlohmann::json::array();
1748     for (const auto& ipv6Config : ipv6Data)
1749     {
1750         nlohmann::json::object_t ipv6;
1751         ipv6["Address"] = ipv6Config.address;
1752         ipv6["PrefixLength"] = ipv6Config.prefixLength;
1753         ipv6["AddressOrigin"] = ipv6Config.origin;
1754         ipv6["AddressState"] = nullptr;
1755         ipv6Array.push_back(std::move(ipv6));
1756         if (ipv6Config.origin == "Static")
1757         {
1758             nlohmann::json::object_t ipv6Static;
1759             ipv6Static["Address"] = ipv6Config.address;
1760             ipv6Static["PrefixLength"] = ipv6Config.prefixLength;
1761             ipv6StaticArray.push_back(std::move(ipv6Static));
1762         }
1763     }
1764 }
1765 
1766 inline bool verifyNames(const std::string& parent, const std::string& iface)
1767 {
1768     return iface.starts_with(parent + "_");
1769 }
1770 
1771 inline void requestEthernetInterfacesRoutes(App& app)
1772 {
1773     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
1774         .privileges(redfish::privileges::getEthernetInterfaceCollection)
1775         .methods(boost::beast::http::verb::get)(
1776             [&app](const crow::Request& req,
1777                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1778         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1779         {
1780             return;
1781         }
1782 
1783         asyncResp->res.jsonValue["@odata.type"] =
1784             "#EthernetInterfaceCollection.EthernetInterfaceCollection";
1785         asyncResp->res.jsonValue["@odata.id"] =
1786             "/redfish/v1/Managers/bmc/EthernetInterfaces";
1787         asyncResp->res.jsonValue["Name"] =
1788             "Ethernet Network Interface Collection";
1789         asyncResp->res.jsonValue["Description"] =
1790             "Collection of EthernetInterfaces for this Manager";
1791 
1792         // Get eth interface list, and call the below callback for JSON
1793         // preparation
1794         getEthernetIfaceList(
1795             [asyncResp](
1796                 const bool& success,
1797                 const boost::container::flat_set<std::string>& ifaceList) {
1798             if (!success)
1799             {
1800                 messages::internalError(asyncResp->res);
1801                 return;
1802             }
1803 
1804             nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"];
1805             ifaceArray = nlohmann::json::array();
1806             std::string tag = "_";
1807             for (const std::string& ifaceItem : ifaceList)
1808             {
1809                 std::size_t found = ifaceItem.find(tag);
1810                 if (found == std::string::npos)
1811                 {
1812                     nlohmann::json::object_t iface;
1813                     iface["@odata.id"] = crow::utility::urlFromPieces(
1814                         "redfish", "v1", "Managers", "bmc",
1815                         "EthernetInterfaces", ifaceItem);
1816                     ifaceArray.push_back(std::move(iface));
1817                 }
1818             }
1819 
1820             asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size();
1821             asyncResp->res.jsonValue["@odata.id"] =
1822                 "/redfish/v1/Managers/bmc/EthernetInterfaces";
1823         });
1824         });
1825 
1826     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
1827         .privileges(redfish::privileges::getEthernetInterface)
1828         .methods(boost::beast::http::verb::get)(
1829             [&app](const crow::Request& req,
1830                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1831                    const std::string& ifaceId) {
1832         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1833         {
1834             return;
1835         }
1836         getEthernetIfaceData(
1837             ifaceId,
1838             [asyncResp, ifaceId](
1839                 const bool& success, const EthernetInterfaceData& ethData,
1840                 const boost::container::flat_set<IPv4AddressData>& ipv4Data,
1841                 const boost::container::flat_set<IPv6AddressData>& ipv6Data) {
1842             if (!success)
1843             {
1844                 // TODO(Pawel)consider distinguish between non
1845                 // existing object, and other errors
1846                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
1847                                            ifaceId);
1848                 return;
1849             }
1850 
1851             // Keep using the v1.6.0 schema here as currently bmcweb have to use
1852             // "VLANs" property deprecated in v1.7.0 for VLAN creation/deletion.
1853             asyncResp->res.jsonValue["@odata.type"] =
1854                 "#EthernetInterface.v1_6_0.EthernetInterface";
1855             asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface";
1856             asyncResp->res.jsonValue["Description"] =
1857                 "Management Network Interface";
1858 
1859             parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data);
1860             });
1861         });
1862 
1863     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
1864         .privileges(redfish::privileges::patchEthernetInterface)
1865         .methods(boost::beast::http::verb::patch)(
1866             [&app](const crow::Request& req,
1867                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1868                    const std::string& ifaceId) {
1869         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1870         {
1871             return;
1872         }
1873         std::optional<std::string> hostname;
1874         std::optional<std::string> fqdn;
1875         std::optional<std::string> macAddress;
1876         std::optional<std::string> ipv6DefaultGateway;
1877         std::optional<nlohmann::json> ipv4StaticAddresses;
1878         std::optional<nlohmann::json> ipv6StaticAddresses;
1879         std::optional<std::vector<std::string>> staticNameServers;
1880         std::optional<nlohmann::json> dhcpv4;
1881         std::optional<nlohmann::json> dhcpv6;
1882         std::optional<bool> interfaceEnabled;
1883         std::optional<size_t> mtuSize;
1884         DHCPParameters v4dhcpParms;
1885         DHCPParameters v6dhcpParms;
1886 
1887         if (!json_util::readJsonPatch(
1888                 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn,
1889                 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress",
1890                 macAddress, "StaticNameServers", staticNameServers,
1891                 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses",
1892                 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6,
1893                 "MTUSize", mtuSize, "InterfaceEnabled", interfaceEnabled))
1894         {
1895             return;
1896         }
1897         if (dhcpv4)
1898         {
1899             if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled",
1900                                      v4dhcpParms.dhcpv4Enabled, "UseDNSServers",
1901                                      v4dhcpParms.useDnsServers, "UseNTPServers",
1902                                      v4dhcpParms.useNtpServers, "UseDomainName",
1903                                      v4dhcpParms.useDomainName))
1904             {
1905                 return;
1906             }
1907         }
1908 
1909         if (dhcpv6)
1910         {
1911             if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode",
1912                                      v6dhcpParms.dhcpv6OperatingMode,
1913                                      "UseDNSServers", v6dhcpParms.useDnsServers,
1914                                      "UseNTPServers", v6dhcpParms.useNtpServers,
1915                                      "UseDomainName",
1916                                      v6dhcpParms.useDomainName))
1917             {
1918                 return;
1919             }
1920         }
1921 
1922         // Get single eth interface data, and call the below callback
1923         // for JSON preparation
1924         getEthernetIfaceData(
1925             ifaceId,
1926             [asyncResp, ifaceId, hostname = std::move(hostname),
1927              fqdn = std::move(fqdn), macAddress = std::move(macAddress),
1928              ipv4StaticAddresses = std::move(ipv4StaticAddresses),
1929              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
1930              ipv6StaticAddresses = std::move(ipv6StaticAddresses),
1931              staticNameServers = std::move(staticNameServers),
1932              dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize,
1933              v4dhcpParms = std::move(v4dhcpParms),
1934              v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled](
1935                 const bool& success, const EthernetInterfaceData& ethData,
1936                 const boost::container::flat_set<IPv4AddressData>& ipv4Data,
1937                 const boost::container::flat_set<IPv6AddressData>& ipv6Data) {
1938             if (!success)
1939             {
1940                 // ... otherwise return error
1941                 // TODO(Pawel)consider distinguish between non
1942                 // existing object, and other errors
1943                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
1944                                            ifaceId);
1945                 return;
1946             }
1947 
1948             if (dhcpv4 || dhcpv6)
1949             {
1950                 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms,
1951                                 asyncResp);
1952             }
1953 
1954             if (hostname)
1955             {
1956                 handleHostnamePatch(*hostname, asyncResp);
1957             }
1958 
1959             if (fqdn)
1960             {
1961                 handleFqdnPatch(ifaceId, *fqdn, asyncResp);
1962             }
1963 
1964             if (macAddress)
1965             {
1966                 handleMACAddressPatch(ifaceId, *macAddress, asyncResp);
1967             }
1968 
1969             if (ipv4StaticAddresses)
1970             {
1971                 // TODO(ed) for some reason the capture of
1972                 // ipv4Addresses above is returning a const value,
1973                 // not a non-const value. This doesn't really work
1974                 // for us, as we need to be able to efficiently move
1975                 // out the intermedia nlohmann::json objects. This
1976                 // makes a copy of the structure, and operates on
1977                 // that, but could be done more efficiently
1978                 nlohmann::json ipv4Static = *ipv4StaticAddresses;
1979                 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp);
1980             }
1981 
1982             if (staticNameServers)
1983             {
1984                 handleStaticNameServersPatch(ifaceId, *staticNameServers,
1985                                              asyncResp);
1986             }
1987 
1988             if (ipv6DefaultGateway)
1989             {
1990                 messages::propertyNotWritable(asyncResp->res,
1991                                               "IPv6DefaultGateway");
1992             }
1993 
1994             if (ipv6StaticAddresses)
1995             {
1996                 const nlohmann::json& ipv6Static = *ipv6StaticAddresses;
1997                 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, ipv6Data,
1998                                                asyncResp);
1999             }
2000 
2001             if (interfaceEnabled)
2002             {
2003                 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled",
2004                                                  *interfaceEnabled, asyncResp);
2005             }
2006 
2007             if (mtuSize)
2008             {
2009                 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp);
2010             }
2011             });
2012         });
2013 
2014     BMCWEB_ROUTE(
2015         app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/")
2016         .privileges(redfish::privileges::getVLanNetworkInterface)
2017         .methods(boost::beast::http::verb::get)(
2018             [&app](const crow::Request& req,
2019                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2020                    const std::string& parentIfaceId,
2021                    const std::string& ifaceId) {
2022         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2023         {
2024             return;
2025         }
2026         asyncResp->res.jsonValue["@odata.type"] =
2027             "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
2028         asyncResp->res.jsonValue["Name"] = "VLAN Network Interface";
2029 
2030         if (!verifyNames(parentIfaceId, ifaceId))
2031         {
2032             return;
2033         }
2034 
2035         // Get single eth interface data, and call the below callback
2036         // for JSON preparation
2037         getEthernetIfaceData(
2038             ifaceId,
2039             [asyncResp, parentIfaceId,
2040              ifaceId](const bool& success, const EthernetInterfaceData& ethData,
2041                       const boost::container::flat_set<IPv4AddressData>&,
2042                       const boost::container::flat_set<IPv6AddressData>&) {
2043             if (success && ethData.vlanId)
2044             {
2045                 asyncResp->res.jsonValue["Id"] = ifaceId;
2046                 asyncResp->res.jsonValue["@odata.id"] =
2047                     crow::utility::urlFromPieces(
2048                         "redfish", "v1", "Managers", "bmc",
2049                         "EthernetInterfaces", parentIfaceId, "VLANs", ifaceId);
2050 
2051                 asyncResp->res.jsonValue["VLANEnable"] = ethData.nicEnabled;
2052                 asyncResp->res.jsonValue["VLANId"] = *ethData.vlanId;
2053             }
2054             else
2055             {
2056                 // ... otherwise return error
2057                 // TODO(Pawel)consider distinguish between non
2058                 // existing object, and other errors
2059                 messages::resourceNotFound(asyncResp->res,
2060                                            "VLanNetworkInterface", ifaceId);
2061             }
2062             });
2063         });
2064 
2065     BMCWEB_ROUTE(
2066         app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/")
2067         .privileges(redfish::privileges::patchVLanNetworkInterface)
2068         .methods(boost::beast::http::verb::patch)(
2069             [&app](const crow::Request& req,
2070                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2071                    const std::string& parentIfaceId,
2072                    const std::string& ifaceId) {
2073         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2074         {
2075             return;
2076         }
2077         if (!verifyNames(parentIfaceId, ifaceId))
2078         {
2079             messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface",
2080                                        ifaceId);
2081             return;
2082         }
2083 
2084         std::optional<bool> vlanEnable;
2085         std::optional<uint32_t> vlanId;
2086 
2087         if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable",
2088                                       vlanEnable, "VLANId", vlanId))
2089         {
2090             return;
2091         }
2092 
2093         if (vlanId)
2094         {
2095             messages::propertyNotWritable(asyncResp->res, "VLANId");
2096             return;
2097         }
2098 
2099         // Get single eth interface data, and call the below callback
2100         // for JSON preparation
2101         getEthernetIfaceData(
2102             ifaceId,
2103             [asyncResp, parentIfaceId, ifaceId, vlanEnable](
2104                 const bool& success, const EthernetInterfaceData& ethData,
2105                 const boost::container::flat_set<IPv4AddressData>&,
2106                 const boost::container::flat_set<IPv6AddressData>&) {
2107             if (success && ethData.vlanId)
2108             {
2109                 if (vlanEnable)
2110                 {
2111                     crow::connections::systemBus->async_method_call(
2112                         [asyncResp](const boost::system::error_code ec) {
2113                         if (ec)
2114                         {
2115                             messages::internalError(asyncResp->res);
2116                             return;
2117                         }
2118                         },
2119                         "xyz.openbmc_project.Network",
2120                         "/xyz/openbmc_project/network/" + ifaceId,
2121                         "org.freedesktop.DBus.Properties", "Set",
2122                         "xyz.openbmc_project.Network.EthernetInterface",
2123                         "NICEnabled",
2124                         dbus::utility::DbusVariantType(*vlanEnable));
2125                 }
2126             }
2127             else
2128             {
2129                 // TODO(Pawel)consider distinguish between non
2130                 // existing object, and other errors
2131                 messages::resourceNotFound(asyncResp->res,
2132                                            "VLanNetworkInterface", ifaceId);
2133                 return;
2134             }
2135             });
2136         });
2137 
2138     BMCWEB_ROUTE(
2139         app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/")
2140         .privileges(redfish::privileges::deleteVLanNetworkInterface)
2141         .methods(boost::beast::http::verb::delete_)(
2142             [&app](const crow::Request& req,
2143                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2144                    const std::string& parentIfaceId,
2145                    const std::string& ifaceId) {
2146         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2147         {
2148             return;
2149         }
2150         if (!verifyNames(parentIfaceId, ifaceId))
2151         {
2152             messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface",
2153                                        ifaceId);
2154             return;
2155         }
2156 
2157         // Get single eth interface data, and call the below callback
2158         // for JSON preparation
2159         getEthernetIfaceData(
2160             ifaceId,
2161             [asyncResp, parentIfaceId,
2162              ifaceId](const bool& success, const EthernetInterfaceData& ethData,
2163                       const boost::container::flat_set<IPv4AddressData>&,
2164                       const boost::container::flat_set<IPv6AddressData>&) {
2165             if (success && ethData.vlanId)
2166             {
2167                 auto callback =
2168                     [asyncResp](const boost::system::error_code ec) {
2169                     if (ec)
2170                     {
2171                         messages::internalError(asyncResp->res);
2172                     }
2173                 };
2174                 crow::connections::systemBus->async_method_call(
2175                     std::move(callback), "xyz.openbmc_project.Network",
2176                     std::string("/xyz/openbmc_project/network/") + ifaceId,
2177                     "xyz.openbmc_project.Object.Delete", "Delete");
2178             }
2179             else
2180             {
2181                 // ... otherwise return error
2182                 // TODO(Pawel)consider distinguish between non
2183                 // existing object, and other errors
2184                 messages::resourceNotFound(asyncResp->res,
2185                                            "VLanNetworkInterface", ifaceId);
2186             }
2187             });
2188         });
2189 
2190     BMCWEB_ROUTE(app,
2191                  "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/")
2192 
2193         .privileges(redfish::privileges::getVLanNetworkInterfaceCollection)
2194         .methods(boost::beast::http::verb::get)(
2195             [&app](const crow::Request& req,
2196                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2197                    const std::string& rootInterfaceName) {
2198         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2199         {
2200             return;
2201         }
2202         // Get eth interface list, and call the below callback for JSON
2203         // preparation
2204         getEthernetIfaceList(
2205             [asyncResp, rootInterfaceName](
2206                 const bool& success,
2207                 const boost::container::flat_set<std::string>& ifaceList) {
2208             if (!success)
2209             {
2210                 messages::internalError(asyncResp->res);
2211                 return;
2212             }
2213 
2214             if (ifaceList.find(rootInterfaceName) == ifaceList.end())
2215             {
2216                 messages::resourceNotFound(asyncResp->res,
2217                                            "VLanNetworkInterfaceCollection",
2218                                            rootInterfaceName);
2219                 return;
2220             }
2221 
2222             asyncResp->res.jsonValue["@odata.type"] =
2223                 "#VLanNetworkInterfaceCollection."
2224                 "VLanNetworkInterfaceCollection";
2225             asyncResp->res.jsonValue["Name"] =
2226                 "VLAN Network Interface Collection";
2227 
2228             nlohmann::json ifaceArray = nlohmann::json::array();
2229 
2230             for (const std::string& ifaceItem : ifaceList)
2231             {
2232                 if (ifaceItem.starts_with(rootInterfaceName + "_"))
2233                 {
2234                     nlohmann::json::object_t iface;
2235                     iface["@odata.id"] = crow::utility::urlFromPieces(
2236                         "redfish", "v1", "Managers", "bmc",
2237                         "EthernetInterfaces", rootInterfaceName, "VLANs",
2238                         ifaceItem);
2239                     ifaceArray.push_back(std::move(iface));
2240                 }
2241             }
2242 
2243             asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size();
2244             asyncResp->res.jsonValue["Members"] = std::move(ifaceArray);
2245             asyncResp->res.jsonValue["@odata.id"] =
2246                 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc",
2247                                              "EthernetInterfaces",
2248                                              rootInterfaceName, "VLANs");
2249         });
2250         });
2251 
2252     BMCWEB_ROUTE(app,
2253                  "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/")
2254         .privileges(redfish::privileges::postVLanNetworkInterfaceCollection)
2255         .methods(boost::beast::http::verb::post)(
2256             [&app](const crow::Request& req,
2257                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2258                    const std::string& rootInterfaceName) {
2259         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2260         {
2261             return;
2262         }
2263         bool vlanEnable = false;
2264         uint32_t vlanId = 0;
2265         if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId", vlanId,
2266                                       "VLANEnable", vlanEnable))
2267         {
2268             return;
2269         }
2270         // Need both vlanId and vlanEnable to service this request
2271         if (vlanId == 0U)
2272         {
2273             messages::propertyMissing(asyncResp->res, "VLANId");
2274         }
2275         if (!vlanEnable)
2276         {
2277             messages::propertyMissing(asyncResp->res, "VLANEnable");
2278         }
2279         if (static_cast<bool>(vlanId) ^ vlanEnable)
2280         {
2281             return;
2282         }
2283 
2284         auto callback = [asyncResp](const boost::system::error_code ec) {
2285             if (ec)
2286             {
2287                 // TODO(ed) make more consistent error messages
2288                 // based on phosphor-network responses
2289                 messages::internalError(asyncResp->res);
2290                 return;
2291             }
2292             messages::created(asyncResp->res);
2293         };
2294         crow::connections::systemBus->async_method_call(
2295             std::move(callback), "xyz.openbmc_project.Network",
2296             "/xyz/openbmc_project/network",
2297             "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
2298             rootInterfaceName, vlanId);
2299         });
2300 }
2301 
2302 } // namespace redfish
2303