xref: /openbmc/bmcweb/redfish-core/lib/ethernet.hpp (revision 7d95f5f6db1df37fe6438aa0e87479275c82807a)
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 "node.hpp"
19  #include <boost/container/flat_map.hpp>
20  
21  namespace redfish {
22  
23  /**
24   * DBus types primitives for several generic DBus interfaces
25   * TODO(Pawel) consider move this to separate file into boost::dbus
26   */
27  using PropertiesMapType =
28      boost::container::flat_map<std::string, dbus::dbus_variant>;
29  
30  using GetManagedObjectsType = boost::container::flat_map<
31      dbus::object_path,
32      boost::container::flat_map<std::string, PropertiesMapType>>;
33  
34  using GetAllPropertiesType = PropertiesMapType;
35  
36  /**
37   * Structure for keeping IPv4 data required by Redfish
38   * TODO(Pawel) consider change everything to ptr, or to non-ptr values.
39   */
40  struct IPv4AddressData {
41    const std::string *address;
42    const std::string *domain;
43    const std::string *gateway;
44    std::string netmask;
45    std::string origin;
46    bool global;
47  };
48  
49  /**
50   * Structure for keeping basic single Ethernet Interface information
51   * available from DBus
52   */
53  struct EthernetInterfaceData {
54    const unsigned int *speed;
55    const bool *auto_neg;
56    const std::string *hostname;
57    const std::string *default_gateway;
58    const std::string *mac_address;
59    const unsigned int *vlan_id;
60  };
61  
62  /**
63   * OnDemandEthernetProvider
64   * Ethernet provider class that retrieves data directly from dbus, before seting
65   * it into JSON output. This does not cache any data.
66   *
67   * TODO(Pawel)
68   * This perhaps shall be different file, which has to be chosen on compile time
69   * depending on OEM needs
70   */
71  class OnDemandEthernetProvider {
72   private:
73    // Consts that may have influence on EthernetProvider performance/memory usage
74    const size_t MAX_IPV4_ADDRESSES_PER_INTERFACE = 10;
75  
76    // Helper function that allows to extract GetAllPropertiesType from
77    // GetManagedObjectsType, based on object path, and interface name
78    const PropertiesMapType *extractInterfaceProperties(
79        const dbus::object_path &objpath, const std::string &interface,
80        const GetManagedObjectsType &dbus_data) {
81      const auto &dbus_obj = dbus_data.find(objpath);
82      if (dbus_obj != dbus_data.end()) {
83        const auto &iface = dbus_obj->second.find(interface);
84        if (iface != dbus_obj->second.end()) {
85          return &iface->second;
86        }
87      }
88      return nullptr;
89    }
90  
91    // Helper Wrapper that does inline object_path conversion from string
92    // into dbus::object_path type
93    inline const PropertiesMapType *extractInterfaceProperties(
94        const std::string &objpath, const std::string &interface,
95        const GetManagedObjectsType &dbus_data) {
96      const auto &dbus_obj = dbus::object_path{objpath};
97      return extractInterfaceProperties(dbus_obj, interface, dbus_data);
98    }
99  
100    // Helper function that allows to get pointer to the property from
101    // GetAllPropertiesType native, or extracted by GetAllPropertiesType
102    template <typename T>
103    inline const T *extractProperty(const PropertiesMapType &properties,
104                                    const std::string &name) {
105      const auto &property = properties.find(name);
106      if (property != properties.end()) {
107        return boost::get<T>(&property->second);
108      }
109      return nullptr;
110    }
111    // TODO(Pawel) Consider to move the above functions to dbus
112    // generic_interfaces.hpp
113  
114    // Helper function that extracts data from several dbus objects and several
115    // interfaces required by single ethernet interface instance
116    void extractEthernetInterfaceData(const std::string &ethiface_id,
117                                      const GetManagedObjectsType &dbus_data,
118                                      EthernetInterfaceData &eth_data) {
119      // Extract data that contains MAC Address
120      const PropertiesMapType *mac_properties = extractInterfaceProperties(
121          "/xyz/openbmc_project/network/" + ethiface_id,
122          "xyz.openbmc_project.Network.MACAddress", dbus_data);
123  
124      if (mac_properties != nullptr) {
125        eth_data.mac_address =
126            extractProperty<std::string>(*mac_properties, "MACAddress");
127      }
128  
129      const PropertiesMapType *vlan_properties = extractInterfaceProperties(
130          "/xyz/openbmc_project/network/" + ethiface_id,
131          "xyz.openbmc_project.Network.VLAN", dbus_data);
132  
133      if (vlan_properties != nullptr) {
134        eth_data.vlan_id = extractProperty<unsigned int>(*vlan_properties, "Id");
135      }
136  
137      // Extract data that contains link information (auto negotiation and speed)
138      const PropertiesMapType *eth_properties = extractInterfaceProperties(
139          "/xyz/openbmc_project/network/" + ethiface_id,
140          "xyz.openbmc_project.Network.EthernetInterface", dbus_data);
141  
142      if (eth_properties != nullptr) {
143        eth_data.auto_neg = extractProperty<bool>(*eth_properties, "AutoNeg");
144        eth_data.speed = extractProperty<unsigned int>(*eth_properties, "Speed");
145      }
146  
147      // Extract data that contains network config (HostName and DefaultGW)
148      const PropertiesMapType *config_properties = extractInterfaceProperties(
149          "/xyz/openbmc_project/network/config",
150          "xyz.openbmc_project.Network.SystemConfiguration", dbus_data);
151  
152      if (config_properties != nullptr) {
153        eth_data.hostname =
154            extractProperty<std::string>(*config_properties, "HostName");
155        eth_data.default_gateway =
156            extractProperty<std::string>(*config_properties, "DefaultGateway");
157      }
158    }
159  
160    // Helper function that changes bits netmask notation (i.e. /24)
161    // into full dot notation
162    inline std::string getNetmask(unsigned int bits) {
163      uint32_t value = 0xffffffff << (32 - bits);
164      std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
165                            std::to_string((value >> 16) & 0xff) + "." +
166                            std::to_string((value >> 8) & 0xff) + "." +
167                            std::to_string(value & 0xff);
168      return netmask;
169    }
170  
171    // Helper function that extracts data for single ethernet ipv4 address
172    void extractIPv4Data(const std::string &ethiface_id,
173                         const GetManagedObjectsType &dbus_data,
174                         std::vector<IPv4AddressData> &ipv4_config) {
175      // Since there might be several IPv4 configurations aligned with
176      // single ethernet interface, loop over all of them
177      for (auto &objpath : dbus_data) {
178        // Check if propper patter for object path appears
179        if (boost::starts_with(
180                objpath.first.value,
181                "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/")) {
182          // and get approrpiate interface
183          const auto &interface =
184              objpath.second.find("xyz.openbmc_project.Network.IP");
185          if (interface != objpath.second.end()) {
186            // Make a properties 'shortcut', to make everything more readable
187            const PropertiesMapType &properties = interface->second;
188            // Instance IPv4AddressData structure, and set as appropriate
189            IPv4AddressData ipv4_address;
190            // IPv4 address
191            ipv4_address.address =
192                extractProperty<std::string>(properties, "Address");
193            // IPv4 gateway
194            ipv4_address.gateway =
195                extractProperty<std::string>(properties, "Gateway");
196  
197            // Origin is kind of DBus object so fetch pointer...
198            const std::string *origin =
199                extractProperty<std::string>(properties, "Origin");
200            if (origin != nullptr) {
201              // ... and get everything after last dot
202              int last = origin->rfind(".");
203              if (last != std::string::npos) {
204                ipv4_address.origin = origin->substr(last + 1);
205              }
206            }
207  
208            // Netmask is presented as PrefixLength
209            const auto *mask =
210                extractProperty<uint8_t>(properties, "PrefixLength");
211            if (mask != nullptr) {
212              // convert it to the string
213              ipv4_address.netmask = getNetmask(*mask);
214            }
215  
216            // Attach IPv4 only if address is present
217            if (ipv4_address.address != nullptr) {
218              // Check if given addres is local, or global
219              if (boost::starts_with(*ipv4_address.address, "169.254")) {
220                ipv4_address.global = false;
221              } else {
222                ipv4_address.global = true;
223              }
224              ipv4_config.emplace_back(std::move(ipv4_address));
225            }
226          }
227        }
228      }
229    }
230  
231   public:
232    /**
233     * Function that retrieves all properties for given Ethernet Interface Object
234     * from EntityManager Network Manager
235     * @param ethiface_id a eth interface id to query on DBus
236     * @param callback a function that shall be called to convert Dbus output into
237     * JSON
238     */
239    template <typename CallbackFunc>
240    void getEthernetIfaceData(const std::string &ethiface_id,
241                              CallbackFunc &&callback) {
242      crow::connections::system_bus->async_method_call(
243          [
244            this, ethiface_id{std::move(ethiface_id)},
245            callback{std::move(callback)}
246          ](const boost::system::error_code error_code,
247            const GetManagedObjectsType &resp) {
248  
249            EthernetInterfaceData eth_data{};
250            std::vector<IPv4AddressData> ipv4_data;
251            ipv4_data.reserve(MAX_IPV4_ADDRESSES_PER_INTERFACE);
252  
253            if (error_code) {
254              // Something wrong on DBus, the error_code is not important at this
255              // moment, just return success=false, and empty output. Since size
256              // of vector may vary depending on information from Network Manager,
257              // and empty output could not be treated same way as error.
258              callback(false, eth_data, ipv4_data);
259              return;
260            }
261  
262            extractEthernetInterfaceData(ethiface_id, resp, eth_data);
263            extractIPv4Data(ethiface_id, resp, ipv4_data);
264  
265            // Fix global GW
266            for (IPv4AddressData &ipv4 : ipv4_data) {
267              if ((ipv4.global) &&
268                  ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) {
269                ipv4.gateway = eth_data.default_gateway;
270              }
271            }
272  
273            // Finally make a callback with usefull data
274            callback(true, eth_data, ipv4_data);
275          },
276          {"xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
277           "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"});
278    };
279  
280    /**
281     * Function that retrieves all Ethernet Interfaces available through Network
282     * Manager
283     * @param callback a function that shall be called to convert Dbus output into
284     * JSON.
285     */
286    template <typename CallbackFunc>
287    void getEthernetIfaceList(CallbackFunc &&callback) {
288      crow::connections::system_bus->async_method_call(
289          [ this, callback{std::move(callback)} ](
290              const boost::system::error_code error_code,
291              const GetManagedObjectsType &resp) {
292            // Callback requires vector<string> to retrieve all available ethernet
293            // interfaces
294            std::vector<std::string> iface_list;
295            iface_list.reserve(resp.size());
296            if (error_code) {
297              // Something wrong on DBus, the error_code is not important at this
298              // moment, just return success=false, and empty output. Since size
299              // of vector may vary depending on information from Network Manager,
300              // and empty output could not be treated same way as error.
301              callback(false, iface_list);
302              return;
303            }
304  
305            // Iterate over all retrieved ObjectPaths.
306            for (auto &objpath : resp) {
307              // And all interfaces available for certain ObjectPath.
308              for (auto &interface : objpath.second) {
309                // If interface is xyz.openbmc_project.Network.EthernetInterface,
310                // this is what we're looking for.
311                if (interface.first ==
312                    "xyz.openbmc_project.Network.EthernetInterface") {
313                  // Cut out everyting until last "/", ...
314                  const std::string &iface_id = objpath.first.value;
315                  std::size_t last_pos = iface_id.rfind("/");
316                  if (last_pos != std::string::npos) {
317                    // and put it into output vector.
318                    iface_list.emplace_back(iface_id.substr(last_pos + 1));
319                  }
320                }
321              }
322            }
323            // Finally make a callback with usefull data
324            callback(true, iface_list);
325          },
326          {"xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
327           "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"});
328    };
329  };
330  
331  /**
332   * EthernetCollection derived class for delivering Ethernet Collection Schema
333   */
334  class EthernetCollection : public Node {
335   public:
336    template <typename CrowApp>
337    // TODO(Pawel) Remove line from below, where we assume that there is only one
338    // manager called openbmc This shall be generic, but requires to update
339    // GetSubroutes method
340    EthernetCollection(CrowApp &app)
341        : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") {
342      Node::json["@odata.type"] =
343          "#EthernetInterfaceCollection.EthernetInterfaceCollection";
344      Node::json["@odata.context"] =
345          "/redfish/v1/"
346          "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection";
347      Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces";
348      Node::json["Name"] = "Ethernet Network Interface Collection";
349      Node::json["Description"] =
350          "Collection of EthernetInterfaces for this Manager";
351  
352      entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
353                          {crow::HTTPMethod::HEAD, {{"Login"}}},
354                          {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}},
355                          {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}},
356                          {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}},
357                          {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}};
358    }
359  
360   private:
361    /**
362     * Functions triggers appropriate requests on DBus
363     */
364    void doGet(crow::response &res, const crow::request &req,
365               const std::vector<std::string> &params) override {
366      // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
367      // any Manager, not only hardcoded 'openbmc'.
368      std::string manager_id = "openbmc";
369  
370      // Get eth interface list, and call the below callback for JSON preparation
371      ethernet_provider.getEthernetIfaceList(
372          [&, manager_id{std::move(manager_id)} ](
373              const bool &success, const std::vector<std::string> &iface_list) {
374            if (success) {
375              nlohmann::json iface_array = nlohmann::json::array();
376              for (const std::string &iface_item : iface_list) {
377                iface_array.push_back(
378                    {{"@odata.id", "/redfish/v1/Managers/" + manager_id +
379                                       "/EthernetInterfaces/" + iface_item}});
380              }
381              Node::json["Members"] = iface_array;
382              Node::json["Members@odata.count"] = iface_array.size();
383              Node::json["@odata.id"] =
384                  "/redfish/v1/Managers/" + manager_id + "/EthernetInterfaces";
385              res.json_value = Node::json;
386            } else {
387              // No success, best what we can do is return INTERNALL ERROR
388              res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
389            }
390            res.end();
391          });
392    }
393  
394    // Ethernet Provider object
395    // TODO(Pawel) consider move it to singleton
396    OnDemandEthernetProvider ethernet_provider;
397  };
398  
399  /**
400   * EthernetInterface derived class for delivering Ethernet Schema
401   */
402  class EthernetInterface : public Node {
403   public:
404    /*
405     * Default Constructor
406     */
407    template <typename CrowApp>
408    // TODO(Pawel) Remove line from below, where we assume that there is only one
409    // manager called openbmc This shall be generic, but requires to update
410    // GetSubroutes method
411    EthernetInterface(CrowApp &app)
412        : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/",
413               std::string()) {
414      Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface";
415      Node::json["@odata.context"] =
416          "/redfish/v1/$metadata#EthernetInterface.EthernetInterface";
417      Node::json["Name"] = "Manager Ethernet Interface";
418      Node::json["Description"] = "Management Network Interface";
419  
420      entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
421                          {crow::HTTPMethod::HEAD, {{"Login"}}},
422                          {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}},
423                          {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}},
424                          {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}},
425                          {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}};
426    }
427  
428   private:
429    /**
430     * Functions triggers appropriate requests on DBus
431     */
432    void doGet(crow::response &res, const crow::request &req,
433               const std::vector<std::string> &params) override {
434      // TODO(Pawel) this shall be parametrized call (two params) to get
435      // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
436      // Check if there is required param, truly entering this shall be
437      // impossible.
438      if (params.size() != 1) {
439        res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
440        res.end();
441        return;
442      }
443  
444      const std::string &iface_id = params[0];
445  
446      // Get single eth interface data, and call the below callback for JSON
447      // preparation
448      ethernet_provider.getEthernetIfaceData(
449          iface_id, [&, iface_id](const bool &success,
450                                  const EthernetInterfaceData &eth_data,
451                                  const std::vector<IPv4AddressData> &ipv4_data) {
452            if (success) {
453              // Copy JSON object to avoid race condition
454              nlohmann::json json_response(Node::json);
455  
456              // Fill out obvious data...
457              json_response["Id"] = iface_id;
458              json_response["@odata.id"] =
459                  "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id;
460  
461              // ... then the one from DBus, regarding eth iface...
462              if (eth_data.speed != nullptr)
463                json_response["SpeedMbps"] = *eth_data.speed;
464  
465              if (eth_data.mac_address != nullptr)
466                json_response["MACAddress"] = *eth_data.mac_address;
467  
468              if (eth_data.hostname != nullptr)
469                json_response["HostName"] = *eth_data.hostname;
470  
471              if (eth_data.vlan_id != nullptr) {
472                json_response["VLAN"]["VLANEnable"] = true;
473                json_response["VLAN"]["VLANId"] = *eth_data.vlan_id;
474              }
475  
476              // ... at last, check if there are IPv4 data and prepare appropriate
477              // collection
478              if (ipv4_data.size() > 0) {
479                nlohmann::json ipv4_array = nlohmann::json::array();
480                for (auto &ipv4_config : ipv4_data) {
481                  nlohmann::json json_ipv4;
482                  if (ipv4_config.address != nullptr) {
483                    json_ipv4["Address"] = *ipv4_config.address;
484                    if (ipv4_config.gateway != nullptr)
485                      json_ipv4["Gateway"] = *ipv4_config.gateway;
486  
487                    json_ipv4["AddressOrigin"] = ipv4_config.origin;
488                    json_ipv4["SubnetMask"] = ipv4_config.netmask;
489  
490                    ipv4_array.push_back(json_ipv4);
491                  }
492                }
493                json_response["IPv4Addresses"] = ipv4_array;
494              }
495              res.json_value = std::move(json_response);
496            } else {
497              // ... otherwise return error
498              // TODO(Pawel)consider distinguish between non existing object, and
499              // other errors
500              res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
501            }
502            res.end();
503          });
504    }
505  
506    // Ethernet Provider object
507    // TODO(Pawel) consider move it to singleton
508    OnDemandEthernetProvider ethernet_provider;
509  };
510  
511  }  // namespace redfish
512