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