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