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