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