xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision 4c9afe436f9a60e8d8fe0131850f0351d746362b)
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 <boost/container/flat_map.hpp>
19 #include <boost/container/flat_set.hpp>
20 #include <dbus_singleton.hpp>
21 #include <error_messages.hpp>
22 #include <node.hpp>
23 #include <optional>
24 #include <utils/json_utils.hpp>
25 #include <variant>
26 
27 namespace redfish
28 {
29 
30 /**
31  * DBus types primitives for several generic DBus interfaces
32  * TODO(Pawel) consider move this to separate file into boost::dbus
33  */
34 using PropertiesMapType = boost::container::flat_map<
35     std::string, std::variant<std::string, bool, uint8_t, int16_t, uint16_t,
36                               int32_t, uint32_t, int64_t, uint64_t, double>>;
37 
38 using GetManagedObjects = std::vector<std::pair<
39     sdbusplus::message::object_path,
40     std::vector<std::pair<
41         std::string,
42         boost::container::flat_map<
43             std::string, sdbusplus::message::variant<
44                              std::string, bool, uint8_t, int16_t, uint16_t,
45                              int32_t, uint32_t, int64_t, uint64_t, double,
46                              std::vector<std::string>>>>>>>;
47 
48 enum class LinkType
49 {
50     Local,
51     Global
52 };
53 
54 /**
55  * Structure for keeping IPv4 data required by Redfish
56  */
57 struct IPv4AddressData
58 {
59     std::string id;
60     std::string address;
61     std::string domain;
62     std::string gateway;
63     std::string netmask;
64     std::string origin;
65     LinkType linktype;
66 
67     bool operator<(const IPv4AddressData &obj) const
68     {
69         return id < obj.id;
70     }
71 };
72 
73 /**
74  * Structure for keeping basic single Ethernet Interface information
75  * available from DBus
76  */
77 struct EthernetInterfaceData
78 {
79     uint32_t speed;
80     bool auto_neg;
81     bool DHCPEnabled;
82     std::string hostname;
83     std::string default_gateway;
84     std::string ipv6_default_gateway;
85     std::string mac_address;
86     std::vector<std::uint32_t> vlan_id;
87     std::vector<std::string> nameservers;
88 };
89 
90 // Helper function that changes bits netmask notation (i.e. /24)
91 // into full dot notation
92 inline std::string getNetmask(unsigned int bits)
93 {
94     uint32_t value = 0xffffffff << (32 - bits);
95     std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
96                           std::to_string((value >> 16) & 0xff) + "." +
97                           std::to_string((value >> 8) & 0xff) + "." +
98                           std::to_string(value & 0xff);
99     return netmask;
100 }
101 
102 inline std::string
103     translateAddressOriginDbusToRedfish(const std::string &inputOrigin,
104                                         bool isIPv4)
105 {
106     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
107     {
108         return "Static";
109     }
110     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal")
111     {
112         if (isIPv4)
113         {
114             return "IPv4LinkLocal";
115         }
116         else
117         {
118             return "LinkLocal";
119         }
120     }
121     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP")
122     {
123         if (isIPv4)
124         {
125             return "DHCP";
126         }
127         else
128         {
129             return "DHCPv6";
130         }
131     }
132     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC")
133     {
134         return "SLAAC";
135     }
136     return "";
137 }
138 
139 inline std::string
140     translateAddressOriginRedfishToDbus(const std::string &inputOrigin)
141 {
142     if (inputOrigin == "Static")
143     {
144         return "xyz.openbmc_project.Network.IP.AddressOrigin.Static";
145     }
146     if (inputOrigin == "DHCP" || inputOrigin == "DHCPv6")
147     {
148         return "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP";
149     }
150     if (inputOrigin == "IPv4LinkLocal" || inputOrigin == "LinkLocal")
151     {
152         return "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal";
153     }
154     if (inputOrigin == "SLAAC")
155     {
156         return "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC";
157     }
158     return "";
159 }
160 
161 inline bool extractEthernetInterfaceData(const std::string &ethiface_id,
162                                          const GetManagedObjects &dbus_data,
163                                          EthernetInterfaceData &ethData)
164 {
165     bool idFound = false;
166     for (const auto &objpath : dbus_data)
167     {
168         for (const auto &ifacePair : objpath.second)
169         {
170             if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id)
171             {
172                 idFound = true;
173                 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress")
174                 {
175                     for (const auto &propertyPair : ifacePair.second)
176                     {
177                         if (propertyPair.first == "MACAddress")
178                         {
179                             const std::string *mac =
180                                 std::get_if<std::string>(&propertyPair.second);
181                             if (mac != nullptr)
182                             {
183                                 ethData.mac_address = *mac;
184                             }
185                         }
186                     }
187                 }
188                 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN")
189                 {
190                     for (const auto &propertyPair : ifacePair.second)
191                     {
192                         if (propertyPair.first == "Id")
193                         {
194                             const uint32_t *id =
195                                 std::get_if<uint32_t>(&propertyPair.second);
196                             if (id != nullptr)
197                             {
198                                 ethData.vlan_id.push_back(*id);
199                             }
200                         }
201                     }
202                 }
203                 else if (ifacePair.first ==
204                          "xyz.openbmc_project.Network.EthernetInterface")
205                 {
206                     for (const auto &propertyPair : ifacePair.second)
207                     {
208                         if (propertyPair.first == "AutoNeg")
209                         {
210                             const bool *auto_neg =
211                                 std::get_if<bool>(&propertyPair.second);
212                             if (auto_neg != nullptr)
213                             {
214                                 ethData.auto_neg = *auto_neg;
215                             }
216                         }
217                         else if (propertyPair.first == "Speed")
218                         {
219                             const uint32_t *speed =
220                                 std::get_if<uint32_t>(&propertyPair.second);
221                             if (speed != nullptr)
222                             {
223                                 ethData.speed = *speed;
224                             }
225                         }
226                         else if (propertyPair.first == "Nameservers")
227                         {
228                             const std::vector<std::string> *nameservers =
229                                 sdbusplus::message::variant_ns::get_if<
230                                     std::vector<std::string>>(
231                                     &propertyPair.second);
232                             if (nameservers != nullptr)
233                             {
234                                 ethData.nameservers = std::move(*nameservers);
235                             }
236                         }
237                         else if (propertyPair.first == "DHCPEnabled")
238                         {
239                             const bool *DHCPEnabled =
240                                 std::get_if<bool>(&propertyPair.second);
241                             if (DHCPEnabled != nullptr)
242                             {
243                                 ethData.DHCPEnabled = *DHCPEnabled;
244                             }
245                         }
246                     }
247                 }
248             }
249             // System configuration shows up in the global namespace, so no need
250             // to check eth number
251             if (ifacePair.first ==
252                 "xyz.openbmc_project.Network.SystemConfiguration")
253             {
254                 for (const auto &propertyPair : ifacePair.second)
255                 {
256                     if (propertyPair.first == "HostName")
257                     {
258                         const std::string *hostname =
259                             sdbusplus::message::variant_ns::get_if<std::string>(
260                                 &propertyPair.second);
261                         if (hostname != nullptr)
262                         {
263                             ethData.hostname = *hostname;
264                         }
265                     }
266                     else if (propertyPair.first == "DefaultGateway")
267                     {
268                         const std::string *defaultGateway =
269                             sdbusplus::message::variant_ns::get_if<std::string>(
270                                 &propertyPair.second);
271                         if (defaultGateway != nullptr)
272                         {
273                             ethData.default_gateway = *defaultGateway;
274                         }
275                     }
276                     else if (propertyPair.first == "DefaultGateway6")
277                     {
278                         const std::string *defaultGateway6 =
279                             sdbusplus::message::variant_ns::get_if<std::string>(
280                                 &propertyPair.second);
281                         if (defaultGateway6 != nullptr)
282                         {
283                             ethData.ipv6_default_gateway = *defaultGateway6;
284                         }
285                     }
286                 }
287             }
288         }
289     }
290     return idFound;
291 }
292 
293 // Helper function that extracts data for single ethernet ipv4 address
294 inline void
295     extractIPData(const std::string &ethiface_id,
296                   const GetManagedObjects &dbus_data,
297                   boost::container::flat_set<IPv4AddressData> &ipv4_config)
298 {
299     const std::string ipv4PathStart =
300         "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/";
301 
302     // Since there might be several IPv4 configurations aligned with
303     // single ethernet interface, loop over all of them
304     for (const auto &objpath : dbus_data)
305     {
306         // Check if proper pattern for object path appears
307         if (boost::starts_with(objpath.first.str, ipv4PathStart))
308         {
309             for (auto &interface : objpath.second)
310             {
311                 if (interface.first == "xyz.openbmc_project.Network.IP")
312                 {
313                     // Instance IPv4AddressData structure, and set as
314                     // appropriate
315                     std::pair<
316                         boost::container::flat_set<IPv4AddressData>::iterator,
317                         bool>
318                         it = ipv4_config.insert(
319                             {objpath.first.str.substr(ipv4PathStart.size())});
320                     IPv4AddressData &ipv4_address = *it.first;
321                     for (auto &property : interface.second)
322                     {
323                         if (property.first == "Address")
324                         {
325                             const std::string *address =
326                                 std::get_if<std::string>(&property.second);
327                             if (address != nullptr)
328                             {
329                                 ipv4_address.address = *address;
330                             }
331                         }
332                         else if (property.first == "Gateway")
333                         {
334                             const std::string *gateway =
335                                 std::get_if<std::string>(&property.second);
336                             if (gateway != nullptr)
337                             {
338                                 ipv4_address.gateway = *gateway;
339                             }
340                         }
341                         else if (property.first == "Origin")
342                         {
343                             const std::string *origin =
344                                 std::get_if<std::string>(&property.second);
345                             if (origin != nullptr)
346                             {
347                                 ipv4_address.origin =
348                                     translateAddressOriginDbusToRedfish(*origin,
349                                                                         true);
350                             }
351                         }
352                         else if (property.first == "PrefixLength")
353                         {
354                             const uint8_t *mask =
355                                 std::get_if<uint8_t>(&property.second);
356                             if (mask != nullptr)
357                             {
358                                 // convert it to the string
359                                 ipv4_address.netmask = getNetmask(*mask);
360                             }
361                         }
362                         else
363                         {
364                             BMCWEB_LOG_ERROR
365                                 << "Got extra property: " << property.first
366                                 << " on the " << objpath.first.str << " object";
367                         }
368                     }
369                     // Check if given address is local, or global
370                     ipv4_address.linktype =
371                         boost::starts_with(ipv4_address.address, "169.254.")
372                             ? LinkType::Global
373                             : LinkType::Local;
374                 }
375             }
376         }
377     }
378 }
379 
380 /**
381  * @brief Sets given Id on the given VLAN interface through D-Bus
382  *
383  * @param[in] ifaceId       Id of VLAN interface that should be modified
384  * @param[in] inputVlanId   New ID of the VLAN
385  * @param[in] callback      Function that will be called after the operation
386  *
387  * @return None.
388  */
389 template <typename CallbackFunc>
390 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId,
391                   CallbackFunc &&callback)
392 {
393     crow::connections::systemBus->async_method_call(
394         callback, "xyz.openbmc_project.Network",
395         std::string("/xyz/openbmc_project/network/") + ifaceId,
396         "org.freedesktop.DBus.Properties", "Set",
397         "xyz.openbmc_project.Network.VLAN", "Id",
398         std::variant<uint32_t>(inputVlanId));
399 }
400 
401 /**
402  * @brief Helper function that verifies IP address to check if it is in
403  *        proper format. If bits pointer is provided, also calculates active
404  *        bit count for Subnet Mask.
405  *
406  * @param[in]  ip     IP that will be verified
407  * @param[out] bits   Calculated mask in bits notation
408  *
409  * @return true in case of success, false otherwise
410  */
411 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip,
412                                        uint8_t *bits = nullptr)
413 {
414     std::vector<std::string> bytesInMask;
415 
416     boost::split(bytesInMask, ip, boost::is_any_of("."));
417 
418     static const constexpr int ipV4AddressSectionsCount = 4;
419     if (bytesInMask.size() != ipV4AddressSectionsCount)
420     {
421         return false;
422     }
423 
424     if (bits != nullptr)
425     {
426         *bits = 0;
427     }
428 
429     char *endPtr;
430     long previousValue = 255;
431     bool firstZeroInByteHit;
432     for (const std::string &byte : bytesInMask)
433     {
434         if (byte.empty())
435         {
436             return false;
437         }
438 
439         // Use strtol instead of stroi to avoid exceptions
440         long value = std::strtol(byte.c_str(), &endPtr, 10);
441 
442         // endPtr should point to the end of the string, otherwise given string
443         // is not 100% number
444         if (*endPtr != '\0')
445         {
446             return false;
447         }
448 
449         // Value should be contained in byte
450         if (value < 0 || value > 255)
451         {
452             return false;
453         }
454 
455         if (bits != nullptr)
456         {
457             // Mask has to be continuous between bytes
458             if (previousValue != 255 && value != 0)
459             {
460                 return false;
461             }
462 
463             // Mask has to be continuous inside bytes
464             firstZeroInByteHit = false;
465 
466             // Count bits
467             for (int bitIdx = 7; bitIdx >= 0; bitIdx--)
468             {
469                 if (value & (1 << bitIdx))
470                 {
471                     if (firstZeroInByteHit)
472                     {
473                         // Continuity not preserved
474                         return false;
475                     }
476                     else
477                     {
478                         (*bits)++;
479                     }
480                 }
481                 else
482                 {
483                     firstZeroInByteHit = true;
484                 }
485             }
486         }
487 
488         previousValue = value;
489     }
490 
491     return true;
492 }
493 
494 /**
495  * @brief Changes IPv4 address type property (Address, Gateway)
496  *
497  * @param[in] ifaceId     Id of interface whose IP should be modified
498  * @param[in] ipIdx       Index of IP in input array that should be modified
499  * @param[in] ipHash      DBus Hash id of modified IP
500  * @param[in] name        Name of field in JSON representation
501  * @param[in] newValue    New value that should be written
502  * @param[io] asyncResp   Response object that will be returned to client
503  *
504  * @return true if give IP is valid and has been sent do D-Bus, false
505  * otherwise
506  */
507 inline void changeIPv4AddressProperty(
508     const std::string &ifaceId, int ipIdx, const std::string &ipHash,
509     const std::string &name, const std::string &newValue,
510     const std::shared_ptr<AsyncResp> asyncResp)
511 {
512     auto callback = [asyncResp, ipIdx, name{std::string(name)},
513                      newValue{std::move(newValue)}](
514                         const boost::system::error_code ec) {
515         if (ec)
516         {
517             messages::internalError(asyncResp->res);
518         }
519         else
520         {
521             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx][name] = newValue;
522         }
523     };
524 
525     crow::connections::systemBus->async_method_call(
526         std::move(callback), "xyz.openbmc_project.Network",
527         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
528         "org.freedesktop.DBus.Properties", "Set",
529         "xyz.openbmc_project.Network.IP", name,
530         std::variant<std::string>(newValue));
531 }
532 
533 /**
534  * @brief Changes IPv4 address origin property
535  *
536  * @param[in] ifaceId       Id of interface whose IP should be modified
537  * @param[in] ipIdx         Index of IP in input array that should be
538  * modified
539  * @param[in] ipHash        DBus Hash id of modified IP
540  * @param[in] newValue      New value in Redfish format
541  * @param[in] newValueDbus  New value in D-Bus format
542  * @param[io] asyncResp     Response object that will be returned to client
543  *
544  * @return true if give IP is valid and has been sent do D-Bus, false
545  * otherwise
546  */
547 inline void changeIPv4Origin(const std::string &ifaceId, int ipIdx,
548                              const std::string &ipHash,
549                              const std::string &newValue,
550                              const std::string &newValueDbus,
551                              const std::shared_ptr<AsyncResp> asyncResp)
552 {
553     auto callback = [asyncResp, ipIdx, newValue{std::move(newValue)}](
554                         const boost::system::error_code ec) {
555         if (ec)
556         {
557             messages::internalError(asyncResp->res);
558         }
559         else
560         {
561             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["AddressOrigin"] =
562                 newValue;
563         }
564     };
565 
566     crow::connections::systemBus->async_method_call(
567         std::move(callback), "xyz.openbmc_project.Network",
568         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
569         "org.freedesktop.DBus.Properties", "Set",
570         "xyz.openbmc_project.Network.IP", "Origin",
571         std::variant<std::string>(newValueDbus));
572 }
573 
574 /**
575  * @brief Modifies SubnetMask for given IP
576  *
577  * @param[in] ifaceId      Id of interface whose IP should be modified
578  * @param[in] ipIdx        Index of IP in input array that should be
579  * modified
580  * @param[in] ipHash       DBus Hash id of modified IP
581  * @param[in] newValueStr  Mask in dot notation as string
582  * @param[in] newValue     Mask as PrefixLength in bitcount
583  * @param[io] asyncResp   Response object that will be returned to client
584  *
585  * @return None
586  */
587 inline void changeIPv4SubnetMaskProperty(const std::string &ifaceId, int ipIdx,
588                                          const std::string &ipHash,
589                                          const std::string &newValueStr,
590                                          uint8_t &newValue,
591                                          std::shared_ptr<AsyncResp> asyncResp)
592 {
593     auto callback = [asyncResp, ipIdx, newValueStr{std::move(newValueStr)}](
594                         const boost::system::error_code ec) {
595         if (ec)
596         {
597             messages::internalError(asyncResp->res);
598         }
599         else
600         {
601             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["SubnetMask"] =
602                 newValueStr;
603         }
604     };
605 
606     crow::connections::systemBus->async_method_call(
607         std::move(callback), "xyz.openbmc_project.Network",
608         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
609         "org.freedesktop.DBus.Properties", "Set",
610         "xyz.openbmc_project.Network.IP", "PrefixLength",
611         std::variant<uint8_t>(newValue));
612 }
613 
614 /**
615  * @brief Deletes given IPv4
616  *
617  * @param[in] ifaceId     Id of interface whose IP should be deleted
618  * @param[in] ipIdx       Index of IP in input array that should be deleted
619  * @param[in] ipHash      DBus Hash id of IP that should be deleted
620  * @param[io] asyncResp   Response object that will be returned to client
621  *
622  * @return None
623  */
624 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash,
625                        unsigned int ipIdx,
626                        const std::shared_ptr<AsyncResp> asyncResp)
627 {
628     crow::connections::systemBus->async_method_call(
629         [ipIdx, asyncResp](const boost::system::error_code ec) {
630             if (ec)
631             {
632                 messages::internalError(asyncResp->res);
633             }
634             else
635             {
636                 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx] = nullptr;
637             }
638         },
639         "xyz.openbmc_project.Network",
640         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
641         "xyz.openbmc_project.Object.Delete", "Delete");
642 }
643 
644 /**
645  * @brief Creates IPv4 with given data
646  *
647  * @param[in] ifaceId     Id of interface whose IP should be deleted
648  * @param[in] ipIdx       Index of IP in input array that should be deleted
649  * @param[in] ipHash      DBus Hash id of IP that should be deleted
650  * @param[io] asyncResp   Response object that will be returned to client
651  *
652  * @return None
653  */
654 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx,
655                        uint8_t subnetMask, const std::string &gateway,
656                        const std::string &address,
657                        std::shared_ptr<AsyncResp> asyncResp)
658 {
659     auto createIpHandler = [asyncResp](const boost::system::error_code ec) {
660         if (ec)
661         {
662             messages::internalError(asyncResp->res);
663         }
664     };
665 
666     crow::connections::systemBus->async_method_call(
667         std::move(createIpHandler), "xyz.openbmc_project.Network",
668         "/xyz/openbmc_project/network/" + ifaceId,
669         "xyz.openbmc_project.Network.IP.Create", "IP",
670         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, subnetMask,
671         gateway);
672 }
673 using GetAllPropertiesType =
674     boost::container::flat_map<std::string, sdbusplus::message::variant<bool>>;
675 
676 inline void getDHCPConfigData(const std::shared_ptr<AsyncResp> asyncResp)
677 {
678     auto getConfig = [asyncResp](const boost::system::error_code error_code,
679                                  const GetAllPropertiesType &dbus_data) {
680         if (error_code)
681         {
682             BMCWEB_LOG_ERROR << "D-Bus response error: " << error_code;
683             messages::internalError(asyncResp->res);
684             return;
685         }
686         nlohmann::json &DHCPConfigTypeJson = asyncResp->res.jsonValue["DHCPv4"];
687         for (const auto &property : dbus_data)
688         {
689             auto value =
690                 sdbusplus::message::variant_ns::get_if<bool>(&property.second);
691 
692             if (value == nullptr)
693             {
694                 continue;
695             }
696             if (property.first == "DNSEnabled")
697             {
698                 DHCPConfigTypeJson["UseDNSServers"] = *value;
699             }
700             else if (property.first == "HostNameEnabled")
701             {
702                 DHCPConfigTypeJson["UseDomainName"] = *value;
703             }
704             else if (property.first == "NTPEnabled")
705             {
706                 DHCPConfigTypeJson["UseNTPServers"] = *value;
707             }
708         }
709     };
710     crow::connections::systemBus->async_method_call(
711         std::move(getConfig), "xyz.openbmc_project.Network",
712         "/xyz/openbmc_project/network/config/dhcp",
713         "org.freedesktop.DBus.Properties", "GetAll",
714         "xyz.openbmc_project.Network.DHCPConfiguration");
715 }
716 
717 /**
718  * Function that retrieves all properties for given Ethernet Interface
719  * Object
720  * from EntityManager Network Manager
721  * @param ethiface_id a eth interface id to query on DBus
722  * @param callback a function that shall be called to convert Dbus output
723  * into JSON
724  */
725 template <typename CallbackFunc>
726 void getEthernetIfaceData(const std::string &ethiface_id,
727                           CallbackFunc &&callback)
728 {
729     crow::connections::systemBus->async_method_call(
730         [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}](
731             const boost::system::error_code error_code,
732             const GetManagedObjects &resp) {
733             EthernetInterfaceData ethData{};
734             boost::container::flat_set<IPv4AddressData> ipv4Data;
735 
736             if (error_code)
737             {
738                 callback(false, ethData, ipv4Data);
739                 return;
740             }
741 
742             bool found =
743                 extractEthernetInterfaceData(ethiface_id, resp, ethData);
744             if (!found)
745             {
746                 callback(false, ethData, ipv4Data);
747                 return;
748             }
749 
750             extractIPData(ethiface_id, resp, ipv4Data);
751 
752             // Fix global GW
753             for (IPv4AddressData &ipv4 : ipv4Data)
754             {
755                 if ((ipv4.linktype == LinkType::Global) &&
756                     (ipv4.gateway == "0.0.0.0"))
757                 {
758                     ipv4.gateway = ethData.default_gateway;
759                 }
760             }
761 
762             // Finally make a callback with usefull data
763             callback(true, ethData, ipv4Data);
764         },
765         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
766         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
767 };
768 
769 /**
770  * Function that retrieves all Ethernet Interfaces available through Network
771  * Manager
772  * @param callback a function that shall be called to convert Dbus output
773  * into JSON.
774  */
775 template <typename CallbackFunc>
776 void getEthernetIfaceList(CallbackFunc &&callback)
777 {
778     crow::connections::systemBus->async_method_call(
779         [callback{std::move(callback)}](
780             const boost::system::error_code error_code,
781             GetManagedObjects &resp) {
782             // Callback requires vector<string> to retrieve all available
783             // ethernet interfaces
784             boost::container::flat_set<std::string> iface_list;
785             iface_list.reserve(resp.size());
786             if (error_code)
787             {
788                 callback(false, iface_list);
789                 return;
790             }
791 
792             // Iterate over all retrieved ObjectPaths.
793             for (const auto &objpath : resp)
794             {
795                 // And all interfaces available for certain ObjectPath.
796                 for (const auto &interface : objpath.second)
797                 {
798                     // If interface is
799                     // xyz.openbmc_project.Network.EthernetInterface, this is
800                     // what we're looking for.
801                     if (interface.first ==
802                         "xyz.openbmc_project.Network.EthernetInterface")
803                     {
804                         // Cut out everyting until last "/", ...
805                         const std::string &iface_id = objpath.first.str;
806                         std::size_t last_pos = iface_id.rfind("/");
807                         if (last_pos != std::string::npos)
808                         {
809                             // and put it into output vector.
810                             iface_list.emplace(iface_id.substr(last_pos + 1));
811                         }
812                     }
813                 }
814             }
815             // Finally make a callback with useful data
816             callback(true, iface_list);
817         },
818         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
819         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
820 };
821 
822 /**
823  * EthernetCollection derived class for delivering Ethernet Collection Schema
824  */
825 class EthernetCollection : public Node
826 {
827   public:
828     template <typename CrowApp>
829     EthernetCollection(CrowApp &app) :
830         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
831     {
832         entityPrivileges = {
833             {boost::beast::http::verb::get, {{"Login"}}},
834             {boost::beast::http::verb::head, {{"Login"}}},
835             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
836             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
837             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
838             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
839     }
840 
841   private:
842     /**
843      * Functions triggers appropriate requests on DBus
844      */
845     void doGet(crow::Response &res, const crow::Request &req,
846                const std::vector<std::string> &params) override
847     {
848         res.jsonValue["@odata.type"] =
849             "#EthernetInterfaceCollection.EthernetInterfaceCollection";
850         res.jsonValue["@odata.context"] =
851             "/redfish/v1/"
852             "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection";
853         res.jsonValue["@odata.id"] =
854             "/redfish/v1/Managers/bmc/EthernetInterfaces";
855         res.jsonValue["Name"] = "Ethernet Network Interface Collection";
856         res.jsonValue["Description"] =
857             "Collection of EthernetInterfaces for this Manager";
858         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
859         // Get eth interface list, and call the below callback for JSON
860         // preparation
861         getEthernetIfaceList(
862             [asyncResp](
863                 const bool &success,
864                 const boost::container::flat_set<std::string> &iface_list) {
865                 if (!success)
866                 {
867                     messages::internalError(asyncResp->res);
868                     return;
869                 }
870 
871                 nlohmann::json &iface_array =
872                     asyncResp->res.jsonValue["Members"];
873                 iface_array = nlohmann::json::array();
874                 std::string tag = "_";
875                 for (const std::string &iface_item : iface_list)
876                 {
877                     std::size_t found = iface_item.find(tag);
878                     if (found == std::string::npos)
879                     {
880                         iface_array.push_back(
881                             {{"@odata.id",
882                               "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
883                                   iface_item}});
884                     }
885                 }
886 
887                 asyncResp->res.jsonValue["Members@odata.count"] =
888                     iface_array.size();
889                 asyncResp->res.jsonValue["@odata.id"] =
890                     "/redfish/v1/Managers/bmc/EthernetInterfaces";
891             });
892     }
893 };
894 
895 /**
896  * EthernetInterface derived class for delivering Ethernet Schema
897  */
898 class EthernetInterface : public Node
899 {
900   public:
901     /*
902      * Default Constructor
903      */
904     template <typename CrowApp>
905     EthernetInterface(CrowApp &app) :
906         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/",
907              std::string())
908     {
909         entityPrivileges = {
910             {boost::beast::http::verb::get, {{"Login"}}},
911             {boost::beast::http::verb::head, {{"Login"}}},
912             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
913             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
914             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
915             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
916     }
917 
918   private:
919     void handleHostnamePatch(const std::string &hostname,
920                              const std::shared_ptr<AsyncResp> asyncResp)
921     {
922         asyncResp->res.jsonValue["HostName"] = hostname;
923         crow::connections::systemBus->async_method_call(
924             [asyncResp](const boost::system::error_code ec) {
925                 if (ec)
926                 {
927                     messages::internalError(asyncResp->res);
928                 }
929             },
930             "xyz.openbmc_project.Network",
931             "/xyz/openbmc_project/network/config",
932             "org.freedesktop.DBus.Properties", "Set",
933             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
934             std::variant<std::string>(hostname));
935     }
936 
937     void handleMACAddressPatch(const std::string &ifaceId,
938                                const std::string &macAddress,
939                                const std::shared_ptr<AsyncResp> &asyncResp)
940     {
941         crow::connections::systemBus->async_method_call(
942             [asyncResp, macAddress](const boost::system::error_code ec) {
943                 if (ec)
944                 {
945                     messages::internalError(asyncResp->res);
946                     return;
947                 }
948                 asyncResp->res.jsonValue["MACAddress"] = std::move(macAddress);
949             },
950             "xyz.openbmc_project.Network",
951             "/xyz/openbmc_project/network/" + ifaceId,
952             "org.freedesktop.DBus.Properties", "Set",
953             "xyz.openbmc_project.Network.MACAddress", "MACAddress",
954             std::variant<std::string>(macAddress));
955     }
956 
957     void handleIPv4Patch(
958         const std::string &ifaceId, nlohmann::json &input,
959         const boost::container::flat_set<IPv4AddressData> &ipv4Data,
960         const std::shared_ptr<AsyncResp> asyncResp)
961     {
962         if (!input.is_array())
963         {
964             messages::propertyValueTypeError(asyncResp->res, input.dump(),
965                                              "IPv4Addresses");
966             return;
967         }
968 
969         int entryIdx = 0;
970         boost::container::flat_set<IPv4AddressData>::const_iterator thisData =
971             ipv4Data.begin();
972         for (nlohmann::json &thisJson : input)
973         {
974             std::string pathString =
975                 "IPv4Addresses/" + std::to_string(entryIdx);
976 
977             if (thisJson.is_null())
978             {
979                 if (thisData != ipv4Data.end())
980                 {
981                     deleteIPv4(ifaceId, thisData->id, entryIdx, asyncResp);
982                     thisData++;
983                 }
984                 else
985                 {
986                     messages::propertyValueFormatError(
987                         asyncResp->res, input.dump(), pathString);
988                     return;
989                     // TODO(ratagupt) Not sure about the property where value is
990                     // list and if unable to update one of the
991                     // list value then should we proceed further or
992                     // break there, would ask in the redfish forum
993                     // till then we stop processing the next list item.
994                 }
995                 entryIdx++;
996                 continue; // not an error as per the redfish spec.
997             }
998 
999             if (thisJson.empty())
1000             {
1001                 if (thisData != ipv4Data.end())
1002                 {
1003                     thisData++;
1004                 }
1005                 else
1006                 {
1007                     messages::propertyMissing(asyncResp->res,
1008                                               pathString + "/Address");
1009                     return;
1010                     // TODO(ratagupt) Not sure about the property where value is
1011                     // list and if unable to update one of the
1012                     // list value then should we proceed further or
1013                     // break there, would ask in the redfish forum
1014                     // till then we stop processing the next list item.
1015                 }
1016                 entryIdx++;
1017                 continue; // not an error as per the redfish spec.
1018             }
1019 
1020             std::optional<std::string> address;
1021             std::optional<std::string> addressOrigin;
1022             std::optional<std::string> subnetMask;
1023             std::optional<std::string> gateway;
1024 
1025             if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1026                                      address, "AddressOrigin", addressOrigin,
1027                                      "SubnetMask", subnetMask, "Gateway",
1028                                      gateway))
1029             {
1030                 return;
1031             }
1032 
1033             if (address)
1034             {
1035                 if (!ipv4VerifyIpAndGetBitcount(*address))
1036                 {
1037                     messages::propertyValueFormatError(asyncResp->res, *address,
1038                                                        pathString + "/Address");
1039                     return;
1040                 }
1041             }
1042 
1043             uint8_t prefixLength = 0;
1044             if (subnetMask)
1045             {
1046                 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength))
1047                 {
1048                     messages::propertyValueFormatError(
1049                         asyncResp->res, *subnetMask,
1050                         pathString + "/SubnetMask");
1051                     return;
1052                 }
1053             }
1054             std::string addressOriginInDBusFormat;
1055             if (addressOrigin)
1056             {
1057                 // Get Address origin in proper format
1058                 addressOriginInDBusFormat =
1059                     translateAddressOriginRedfishToDbus(*addressOrigin);
1060                 if (addressOriginInDBusFormat.empty())
1061                 {
1062                     messages::propertyValueNotInList(
1063                         asyncResp->res, *addressOrigin,
1064                         pathString + "/AddressOrigin");
1065                     return;
1066                 }
1067             }
1068 
1069             if (gateway)
1070             {
1071                 if (!ipv4VerifyIpAndGetBitcount(*gateway))
1072                 {
1073                     messages::propertyValueFormatError(asyncResp->res, *gateway,
1074                                                        pathString + "/Gateway");
1075                     return;
1076                 }
1077             }
1078 
1079             // if IP address exist then  modify it.
1080             if (thisData != ipv4Data.end())
1081             {
1082                 // Apply changes
1083                 if (address)
1084                 {
1085                     auto callback = [asyncResp, entryIdx,
1086                                      address{std::string(*address)}](
1087                                         const boost::system::error_code ec) {
1088                         if (ec)
1089                         {
1090                             messages::internalError(asyncResp->res);
1091                             return;
1092                         }
1093                         asyncResp->res
1094                             .jsonValue["IPv4Addresses"][entryIdx]["Address"] =
1095                             std::move(address);
1096                     };
1097 
1098                     crow::connections::systemBus->async_method_call(
1099                         std::move(callback), "xyz.openbmc_project.Network",
1100                         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" +
1101                             thisData->id,
1102                         "org.freedesktop.DBus.Properties", "Set",
1103                         "xyz.openbmc_project.Network.IP", "Address",
1104                         std::variant<std::string>(*address));
1105                 }
1106 
1107                 if (subnetMask)
1108                 {
1109                     changeIPv4SubnetMaskProperty(ifaceId, entryIdx,
1110                                                  thisData->id, *subnetMask,
1111                                                  prefixLength, asyncResp);
1112                 }
1113 
1114                 if (addressOrigin)
1115                 {
1116                     changeIPv4Origin(ifaceId, entryIdx, thisData->id,
1117                                      *addressOrigin, addressOriginInDBusFormat,
1118                                      asyncResp);
1119                 }
1120 
1121                 if (gateway)
1122                 {
1123                     auto callback = [asyncResp, entryIdx,
1124                                      gateway{std::string(*gateway)}](
1125                                         const boost::system::error_code ec) {
1126                         if (ec)
1127                         {
1128                             messages::internalError(asyncResp->res);
1129                             return;
1130                         }
1131                         asyncResp->res
1132                             .jsonValue["IPv4Addresses"][entryIdx]["Gateway"] =
1133                             std::move(gateway);
1134                     };
1135 
1136                     crow::connections::systemBus->async_method_call(
1137                         std::move(callback), "xyz.openbmc_project.Network",
1138                         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" +
1139                             thisData->id,
1140                         "org.freedesktop.DBus.Properties", "Set",
1141                         "xyz.openbmc_project.Network.IP", "Gateway",
1142                         std::variant<std::string>(*gateway));
1143                 }
1144 
1145                 thisData++;
1146             }
1147             else
1148             {
1149                 // Create IPv4 with provided data
1150                 if (!gateway)
1151                 {
1152                     messages::propertyMissing(asyncResp->res,
1153                                               pathString + "/Gateway");
1154                     continue;
1155                 }
1156 
1157                 if (!address)
1158                 {
1159                     messages::propertyMissing(asyncResp->res,
1160                                               pathString + "/Address");
1161                     continue;
1162                 }
1163 
1164                 if (!subnetMask)
1165                 {
1166                     messages::propertyMissing(asyncResp->res,
1167                                               pathString + "/SubnetMask");
1168                     continue;
1169                 }
1170 
1171                 createIPv4(ifaceId, entryIdx, prefixLength, *gateway, *address,
1172                            asyncResp);
1173 
1174                 nlohmann::json &ipv4AddressJson =
1175                     asyncResp->res.jsonValue["IPv4Addresses"][entryIdx];
1176                 ipv4AddressJson["Address"] = *address;
1177                 ipv4AddressJson["SubnetMask"] = *subnetMask;
1178                 ipv4AddressJson["Gateway"] = *gateway;
1179             }
1180             entryIdx++;
1181         }
1182     }
1183 
1184     void handleStaticNameServersPatch(
1185         const std::string &ifaceId,
1186         const std::vector<std::string> &updatedStaticNameServers,
1187         const std::shared_ptr<AsyncResp> &asyncResp)
1188     {
1189         crow::connections::systemBus->async_method_call(
1190             [asyncResp,
1191              updatedStaticNameServers](const boost::system::error_code ec) {
1192                 if (ec)
1193                 {
1194                     messages::internalError(asyncResp->res);
1195                     return;
1196                 }
1197                 asyncResp->res.jsonValue["NameServers"] =
1198                     updatedStaticNameServers;
1199                 asyncResp->res.jsonValue["StaticNameServers"] =
1200                     updatedStaticNameServers;
1201             },
1202             "xyz.openbmc_project.Network",
1203             "/xyz/openbmc_project/network/" + ifaceId,
1204             "org.freedesktop.DBus.Properties", "Set",
1205             "xyz.openbmc_project.Network.EthernetInterface", "Nameservers",
1206             std::variant<std::vector<std::string>>{updatedStaticNameServers});
1207     }
1208 
1209     void parseInterfaceData(
1210         nlohmann::json &json_response, const std::string &iface_id,
1211         const EthernetInterfaceData &ethData,
1212         const boost::container::flat_set<IPv4AddressData> &ipv4Data)
1213     {
1214         json_response["Id"] = iface_id;
1215         json_response["@odata.id"] =
1216             "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id;
1217         json_response["InterfaceEnabled"] = true;
1218         if (ethData.speed == 0)
1219         {
1220             json_response["LinkStatus"] = "NoLink";
1221             json_response["Status"] = {
1222                 {"Health", "OK"},
1223                 {"State", "Disabled"},
1224             };
1225         }
1226         else
1227         {
1228             json_response["LinkStatus"] = "LinkUp";
1229             json_response["Status"] = {
1230                 {"Health", "OK"},
1231                 {"State", "Enabled"},
1232             };
1233         }
1234         json_response["SpeedMbps"] = ethData.speed;
1235         json_response["MACAddress"] = ethData.mac_address;
1236         json_response["DHCPv4"]["DHCPEnabled"] = ethData.DHCPEnabled;
1237 
1238         if (!ethData.hostname.empty())
1239         {
1240             json_response["HostName"] = ethData.hostname;
1241         }
1242 
1243         json_response["VLANs"] = {
1244             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1245                               iface_id + "/VLANs"}};
1246 
1247         json_response["NameServers"] = ethData.nameservers;
1248         json_response["StaticNameServers"] = ethData.nameservers;
1249 
1250         if (ipv4Data.size() > 0)
1251         {
1252             nlohmann::json &ipv4_array = json_response["IPv4Addresses"];
1253             ipv4_array = nlohmann::json::array();
1254             for (auto &ipv4_config : ipv4Data)
1255             {
1256 
1257                 std::string gatewayStr = ipv4_config.gateway;
1258                 if (gatewayStr.empty())
1259                 {
1260                     gatewayStr = "0.0.0.0";
1261                 }
1262 
1263                 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin},
1264                                       {"SubnetMask", ipv4_config.netmask},
1265                                       {"Address", ipv4_config.address},
1266                                       {"Gateway", gatewayStr}});
1267             }
1268         }
1269         json_response["IPv6DefaultGateway"] = ethData.ipv6_default_gateway;
1270     }
1271 
1272     /**
1273      * Functions triggers appropriate requests on DBus
1274      */
1275     void doGet(crow::Response &res, const crow::Request &req,
1276                const std::vector<std::string> &params) override
1277     {
1278         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1279         if (params.size() != 1)
1280         {
1281             messages::internalError(asyncResp->res);
1282             return;
1283         }
1284 
1285         getEthernetIfaceData(
1286             params[0],
1287             [this, asyncResp, iface_id{std::string(params[0])}](
1288                 const bool &success, const EthernetInterfaceData &ethData,
1289                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1290                 if (!success)
1291                 {
1292                     // TODO(Pawel)consider distinguish between non existing
1293                     // object, and other errors
1294                     messages::resourceNotFound(asyncResp->res,
1295                                                "EthernetInterface", iface_id);
1296                     return;
1297                 }
1298 
1299                 // because this has no dependence on the interface at this
1300                 // point, it needs to be done after we know the interface
1301                 // exists, not before.
1302                 getDHCPConfigData(asyncResp);
1303 
1304                 asyncResp->res.jsonValue["@odata.type"] =
1305                     "#EthernetInterface.v1_4_1.EthernetInterface";
1306                 asyncResp->res.jsonValue["@odata.context"] =
1307                     "/redfish/v1/$metadata#EthernetInterface.EthernetInterface";
1308                 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface";
1309                 asyncResp->res.jsonValue["Description"] =
1310                     "Management Network Interface";
1311 
1312                 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData,
1313                                    ipv4Data);
1314             });
1315     }
1316 
1317     void doPatch(crow::Response &res, const crow::Request &req,
1318                  const std::vector<std::string> &params) override
1319     {
1320         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1321         if (params.size() != 1)
1322         {
1323             messages::internalError(asyncResp->res);
1324             return;
1325         }
1326 
1327         const std::string &iface_id = params[0];
1328 
1329         std::optional<std::string> hostname;
1330         std::optional<std::string> macAddress;
1331         std::optional<std::string> ipv6DefaultGateway;
1332         std::optional<nlohmann::json> ipv4Addresses;
1333         std::optional<nlohmann::json> ipv6Addresses;
1334         std::optional<std::vector<std::string>> staticNameServers;
1335         std::optional<std::vector<std::string>> nameServers;
1336 
1337         if (!json_util::readJson(
1338                 req, res, "HostName", hostname, "IPv4Addresses", ipv4Addresses,
1339                 "IPv6Addresses", ipv6Addresses, "MACAddress", macAddress,
1340                 "StaticNameServers", staticNameServers, "IPv6DefaultGateway",
1341                 ipv6DefaultGateway, "NameServers", nameServers))
1342         {
1343             return;
1344         }
1345 
1346         // Get single eth interface data, and call the below callback for JSON
1347         // preparation
1348         getEthernetIfaceData(
1349             iface_id,
1350             [this, asyncResp, iface_id, hostname = std::move(hostname),
1351              macAddress = std::move(macAddress),
1352              ipv4Addresses = std::move(ipv4Addresses),
1353              ipv6Addresses = std::move(ipv6Addresses),
1354              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
1355              staticNameServers = std::move(staticNameServers),
1356              nameServers = std::move(nameServers)](
1357                 const bool &success, const EthernetInterfaceData &ethData,
1358                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1359                 if (!success)
1360                 {
1361                     // ... otherwise return error
1362                     // TODO(Pawel)consider distinguish between non existing
1363                     // object, and other errors
1364                     messages::resourceNotFound(asyncResp->res,
1365                                                "Ethernet Interface", iface_id);
1366                     return;
1367                 }
1368 
1369                 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData,
1370                                    ipv4Data);
1371 
1372                 if (hostname)
1373                 {
1374                     handleHostnamePatch(*hostname, asyncResp);
1375                 }
1376 
1377                 if (macAddress)
1378                 {
1379                     handleMACAddressPatch(iface_id, *macAddress, asyncResp);
1380                 }
1381 
1382                 if (ipv4Addresses)
1383                 {
1384                     // TODO(ed) for some reason the capture of ipv4Addresses
1385                     // above is returning a const value, not a non-const value.
1386                     // This doesn't really work for us, as we need to be able to
1387                     // efficiently move out the intermedia nlohmann::json
1388                     // objects. This makes a copy of the structure, and operates
1389                     // on that, but could be done more efficiently
1390                     nlohmann::json ipv4 = std::move(*ipv4Addresses);
1391                     handleIPv4Patch(iface_id, ipv4, ipv4Data, asyncResp);
1392                 }
1393 
1394                 if (nameServers)
1395                 {
1396                     // Data.Permissions is read-only
1397                     messages::propertyNotWritable(asyncResp->res,
1398                                                   "NameServers");
1399                 }
1400 
1401                 if (ipv6Addresses)
1402                 {
1403                     // TODO(kkowalsk) IPv6 Not supported on D-Bus yet
1404                     messages::propertyNotWritable(asyncResp->res,
1405                                                   "IPv6Addresses");
1406                 }
1407 
1408                 if (staticNameServers)
1409                 {
1410                     handleStaticNameServersPatch(iface_id, *staticNameServers,
1411                                                  asyncResp);
1412                 }
1413 
1414                 if (ipv6DefaultGateway)
1415                 {
1416                     messages::propertyNotWritable(asyncResp->res,
1417                                                   "IPv6DefaultGateway");
1418                 }
1419             });
1420     }
1421 };
1422 
1423 /**
1424  * VlanNetworkInterface derived class for delivering VLANNetworkInterface
1425  * Schema
1426  */
1427 class VlanNetworkInterface : public Node
1428 {
1429   public:
1430     /*
1431      * Default Constructor
1432      */
1433     template <typename CrowApp>
1434     VlanNetworkInterface(CrowApp &app) :
1435         Node(app,
1436              "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>",
1437              std::string(), std::string())
1438     {
1439         entityPrivileges = {
1440             {boost::beast::http::verb::get, {{"Login"}}},
1441             {boost::beast::http::verb::head, {{"Login"}}},
1442             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1443             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1444             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1445             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1446     }
1447 
1448   private:
1449     void parseInterfaceData(
1450         nlohmann::json &json_response, const std::string &parent_iface_id,
1451         const std::string &iface_id, const EthernetInterfaceData &ethData,
1452         const boost::container::flat_set<IPv4AddressData> &ipv4Data)
1453     {
1454         // Fill out obvious data...
1455         json_response["Id"] = iface_id;
1456         json_response["@odata.id"] =
1457             "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id +
1458             "/VLANs/" + iface_id;
1459 
1460         json_response["VLANEnable"] = true;
1461         if (!ethData.vlan_id.empty())
1462         {
1463             json_response["VLANId"] = ethData.vlan_id.back();
1464         }
1465     }
1466 
1467     bool verifyNames(const std::string &parent, const std::string &iface)
1468     {
1469         if (!boost::starts_with(iface, parent + "_"))
1470         {
1471             return false;
1472         }
1473         else
1474         {
1475             return true;
1476         }
1477     }
1478 
1479     /**
1480      * Functions triggers appropriate requests on DBus
1481      */
1482     void doGet(crow::Response &res, const crow::Request &req,
1483                const std::vector<std::string> &params) override
1484     {
1485         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1486         // TODO(Pawel) this shall be parameterized call (two params) to get
1487         // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1488         // Check if there is required param, truly entering this shall be
1489         // impossible.
1490         if (params.size() != 2)
1491         {
1492             messages::internalError(res);
1493             res.end();
1494             return;
1495         }
1496 
1497         const std::string &parent_iface_id = params[0];
1498         const std::string &iface_id = params[1];
1499         res.jsonValue["@odata.type"] =
1500             "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
1501         res.jsonValue["@odata.context"] =
1502             "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface";
1503         res.jsonValue["Name"] = "VLAN Network Interface";
1504 
1505         if (!verifyNames(parent_iface_id, iface_id))
1506         {
1507             return;
1508         }
1509 
1510         // Get single eth interface data, and call the below callback for JSON
1511         // preparation
1512         getEthernetIfaceData(
1513             params[1],
1514             [this, asyncResp, parent_iface_id{std::string(params[0])},
1515              iface_id{std::string(params[1])}](
1516                 const bool &success, const EthernetInterfaceData &ethData,
1517                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1518                 if (success && ethData.vlan_id.size() != 0)
1519                 {
1520                     parseInterfaceData(asyncResp->res.jsonValue,
1521                                        parent_iface_id, iface_id, ethData,
1522                                        ipv4Data);
1523                 }
1524                 else
1525                 {
1526                     // ... otherwise return error
1527                     // TODO(Pawel)consider distinguish between non existing
1528                     // object, and other errors
1529                     messages::resourceNotFound(
1530                         asyncResp->res, "VLAN Network Interface", iface_id);
1531                 }
1532             });
1533     }
1534 
1535     void doPatch(crow::Response &res, const crow::Request &req,
1536                  const std::vector<std::string> &params) override
1537     {
1538         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1539         if (params.size() != 2)
1540         {
1541             messages::internalError(asyncResp->res);
1542             return;
1543         }
1544 
1545         const std::string &parentIfaceId = params[0];
1546         const std::string &ifaceId = params[1];
1547 
1548         if (!verifyNames(parentIfaceId, ifaceId))
1549         {
1550             messages::resourceNotFound(asyncResp->res, "VLAN Network Interface",
1551                                        ifaceId);
1552             return;
1553         }
1554 
1555         bool vlanEnable = false;
1556         uint64_t vlanId = 0;
1557 
1558         if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId",
1559                                  vlanId))
1560         {
1561             return;
1562         }
1563 
1564         // Get single eth interface data, and call the below callback for JSON
1565         // preparation
1566         getEthernetIfaceData(params[1], [this, asyncResp,
1567                                          parentIfaceId{std::string(params[0])},
1568                                          ifaceId{std::string(params[1])},
1569                                          &vlanEnable, &vlanId](
1570                                             const bool &success,
1571                                             const EthernetInterfaceData
1572                                                 &ethData,
1573                                             const boost::container::flat_set<
1574                                                 IPv4AddressData> &ipv4Data) {
1575             if (success && !ethData.vlan_id.empty())
1576             {
1577                 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId,
1578                                    ifaceId, ethData, ipv4Data);
1579                 auto callback =
1580                     [asyncResp](const boost::system::error_code ec) {
1581                         if (ec)
1582                         {
1583                             messages::internalError(asyncResp->res);
1584                         }
1585                     };
1586 
1587                 if (vlanEnable == true)
1588                 {
1589                     crow::connections::systemBus->async_method_call(
1590                         std::move(callback), "xyz.openbmc_project.Network",
1591                         "/xyz/openbmc_project/network/" + ifaceId,
1592                         "org.freedesktop.DBus.Properties", "Set",
1593                         "xyz.openbmc_project.Network.VLAN", "Id",
1594                         std::variant<uint32_t>(vlanId));
1595                 }
1596                 else
1597                 {
1598                     BMCWEB_LOG_DEBUG
1599                         << "vlanEnable is false. Deleting the vlan interface";
1600                     crow::connections::systemBus->async_method_call(
1601                         std::move(callback), "xyz.openbmc_project.Network",
1602                         std::string("/xyz/openbmc_project/network/") + ifaceId,
1603                         "xyz.openbmc_project.Object.Delete", "Delete");
1604                 }
1605             }
1606             else
1607             {
1608                 // TODO(Pawel)consider distinguish between non existing
1609                 // object, and other errors
1610                 messages::resourceNotFound(asyncResp->res,
1611                                            "VLAN Network Interface", ifaceId);
1612                 return;
1613             }
1614         });
1615     }
1616 
1617     void doDelete(crow::Response &res, const crow::Request &req,
1618                   const std::vector<std::string> &params) override
1619     {
1620         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1621         if (params.size() != 2)
1622         {
1623             messages::internalError(asyncResp->res);
1624             return;
1625         }
1626 
1627         const std::string &parentIfaceId = params[0];
1628         const std::string &ifaceId = params[1];
1629 
1630         if (!verifyNames(parentIfaceId, ifaceId))
1631         {
1632             messages::resourceNotFound(asyncResp->res, "VLAN Network Interface",
1633                                        ifaceId);
1634             return;
1635         }
1636 
1637         // Get single eth interface data, and call the below callback for JSON
1638         // preparation
1639         getEthernetIfaceData(
1640             params[1],
1641             [this, asyncResp, parentIfaceId{std::string(params[0])},
1642              ifaceId{std::string(params[1])}](
1643                 const bool &success, const EthernetInterfaceData &ethData,
1644                 const boost::container::flat_set<IPv4AddressData> &ipv4Data) {
1645                 if (success && !ethData.vlan_id.empty())
1646                 {
1647                     parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId,
1648                                        ifaceId, ethData, ipv4Data);
1649 
1650                     auto callback =
1651                         [asyncResp](const boost::system::error_code ec) {
1652                             if (ec)
1653                             {
1654                                 messages::internalError(asyncResp->res);
1655                             }
1656                         };
1657                     crow::connections::systemBus->async_method_call(
1658                         std::move(callback), "xyz.openbmc_project.Network",
1659                         std::string("/xyz/openbmc_project/network/") + ifaceId,
1660                         "xyz.openbmc_project.Object.Delete", "Delete");
1661                 }
1662                 else
1663                 {
1664                     // ... otherwise return error
1665                     // TODO(Pawel)consider distinguish between non existing
1666                     // object, and other errors
1667                     messages::resourceNotFound(
1668                         asyncResp->res, "VLAN Network Interface", ifaceId);
1669                 }
1670             });
1671     }
1672 };
1673 
1674 /**
1675  * VlanNetworkInterfaceCollection derived class for delivering
1676  * VLANNetworkInterface Collection Schema
1677  */
1678 class VlanNetworkInterfaceCollection : public Node
1679 {
1680   public:
1681     template <typename CrowApp>
1682     VlanNetworkInterfaceCollection(CrowApp &app) :
1683         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/",
1684              std::string())
1685     {
1686         entityPrivileges = {
1687             {boost::beast::http::verb::get, {{"Login"}}},
1688             {boost::beast::http::verb::head, {{"Login"}}},
1689             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1690             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1691             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1692             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1693     }
1694 
1695   private:
1696     /**
1697      * Functions triggers appropriate requests on DBus
1698      */
1699     void doGet(crow::Response &res, const crow::Request &req,
1700                const std::vector<std::string> &params) override
1701     {
1702         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1703         if (params.size() != 1)
1704         {
1705             // This means there is a problem with the router
1706             messages::internalError(asyncResp->res);
1707             return;
1708         }
1709 
1710         const std::string &rootInterfaceName = params[0];
1711 
1712         // Get eth interface list, and call the below callback for JSON
1713         // preparation
1714         getEthernetIfaceList(
1715             [asyncResp, rootInterfaceName{std::string(rootInterfaceName)}](
1716                 const bool &success,
1717                 const boost::container::flat_set<std::string> &iface_list) {
1718                 if (!success)
1719                 {
1720                     messages::internalError(asyncResp->res);
1721                     return;
1722                 }
1723 
1724                 if (iface_list.find(rootInterfaceName) == iface_list.end())
1725                 {
1726                     messages::resourceNotFound(asyncResp->res,
1727                                                "VLanNetworkInterfaceCollection",
1728                                                rootInterfaceName);
1729                     return;
1730                 }
1731 
1732                 asyncResp->res.jsonValue["@odata.type"] =
1733                     "#VLanNetworkInterfaceCollection."
1734                     "VLanNetworkInterfaceCollection";
1735                 asyncResp->res.jsonValue["@odata.context"] =
1736                     "/redfish/v1/$metadata"
1737                     "#VLanNetworkInterfaceCollection."
1738                     "VLanNetworkInterfaceCollection";
1739                 asyncResp->res.jsonValue["Name"] =
1740                     "VLAN Network Interface Collection";
1741 
1742                 nlohmann::json iface_array = nlohmann::json::array();
1743 
1744                 for (const std::string &iface_item : iface_list)
1745                 {
1746                     if (boost::starts_with(iface_item, rootInterfaceName + "_"))
1747                     {
1748                         iface_array.push_back(
1749                             {{"@odata.id",
1750                               "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1751                                   rootInterfaceName + "/VLANs/" + iface_item}});
1752                     }
1753                 }
1754 
1755                 asyncResp->res.jsonValue["Members@odata.count"] =
1756                     iface_array.size();
1757                 asyncResp->res.jsonValue["Members"] = std::move(iface_array);
1758                 asyncResp->res.jsonValue["@odata.id"] =
1759                     "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1760                     rootInterfaceName + "/VLANs";
1761             });
1762     }
1763 
1764     void doPost(crow::Response &res, const crow::Request &req,
1765                 const std::vector<std::string> &params) override
1766     {
1767         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1768         if (params.size() != 1)
1769         {
1770             messages::internalError(asyncResp->res);
1771             return;
1772         }
1773         bool vlanEnable = false;
1774         uint32_t vlanId = 0;
1775         if (!json_util::readJson(req, res, "VLANId", vlanId, "VLANEnable",
1776                                  vlanEnable))
1777         {
1778             return;
1779         }
1780         // Need both vlanId and vlanEnable to service this request
1781         if (!vlanId)
1782         {
1783             messages::propertyMissing(asyncResp->res, "VLANId");
1784         }
1785         if (!vlanEnable)
1786         {
1787             messages::propertyMissing(asyncResp->res, "VLANEnable");
1788         }
1789         if (static_cast<bool>(vlanId) ^ static_cast<bool>(vlanEnable))
1790         {
1791             return;
1792         }
1793 
1794         const std::string &rootInterfaceName = params[0];
1795         auto callback = [asyncResp](const boost::system::error_code ec) {
1796             if (ec)
1797             {
1798                 // TODO(ed) make more consistent error messages based on
1799                 // phosphor-network responses
1800                 messages::internalError(asyncResp->res);
1801                 return;
1802             }
1803             messages::created(asyncResp->res);
1804         };
1805         crow::connections::systemBus->async_method_call(
1806             std::move(callback), "xyz.openbmc_project.Network",
1807             "/xyz/openbmc_project/network",
1808             "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
1809             rootInterfaceName, vlanId);
1810     }
1811 };
1812 } // namespace redfish
1813