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