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