1 #include <arpa/inet.h>
2 #include <dirent.h>
3 #include <net/if.h>
4 
5 #include <algorithm>
6 #include <chrono>
7 #include <ipmid/utils.hpp>
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <phosphor-logging/log.hpp>
10 #include <sdbusplus/message/types.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12 
13 namespace ipmi
14 {
15 
16 using namespace phosphor::logging;
17 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
18 
19 namespace network
20 {
21 
22 /** @brief checks if the given ip is Link Local Ip or not.
23  *  @param[in] ipaddress - IPAddress.
24  */
25 bool isLinkLocalIP(const std::string& ipaddress);
26 
27 } // namespace network
28 
29 // TODO There may be cases where an interface is implemented by multiple
30 //  objects,to handle such cases we are interested on that object
31 //  which are on interested busname.
32 //  Currently mapper doesn't give the readable busname(gives busid) so we can't
33 //  use busname to find the object,will do later once the support is there.
34 
35 DbusObjectInfo getDbusObject(sdbusplus::bus::bus& bus,
36                              const std::string& interface,
37                              const std::string& serviceRoot,
38                              const std::string& match)
39 {
40     std::vector<DbusInterface> interfaces;
41     interfaces.emplace_back(interface);
42 
43     auto depth = 0;
44 
45     auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
46                                           MAPPER_INTF, "GetSubTree");
47 
48     mapperCall.append(serviceRoot, depth, interfaces);
49 
50     auto mapperReply = bus.call(mapperCall);
51     if (mapperReply.is_method_error())
52     {
53         log<level::ERR>("Error in mapper call");
54         elog<InternalFailure>();
55     }
56 
57     ObjectTree objectTree;
58     mapperReply.read(objectTree);
59 
60     if (objectTree.empty())
61     {
62         log<level::ERR>("No Object has implemented the interface",
63                         entry("INTERFACE=%s", interface.c_str()));
64         elog<InternalFailure>();
65     }
66 
67     DbusObjectInfo objectInfo;
68 
69     // if match is empty then return the first object
70     if (match == "")
71     {
72         objectInfo = std::make_pair(
73             objectTree.begin()->first,
74             std::move(objectTree.begin()->second.begin()->first));
75         return objectInfo;
76     }
77 
78     // else search the match string in the object path
79     auto found = std::find_if(
80         objectTree.begin(), objectTree.end(), [&match](const auto& object) {
81             return (object.first.find(match) != std::string::npos);
82         });
83 
84     if (found == objectTree.end())
85     {
86         log<level::ERR>("Failed to find object which matches",
87                         entry("MATCH=%s", match.c_str()));
88         elog<InternalFailure>();
89         // elog<> throws an exception.
90     }
91 
92     return make_pair(found->first, std::move(found->second.begin()->first));
93 }
94 
95 DbusObjectInfo getIPObject(sdbusplus::bus::bus& bus,
96                            const std::string& interface,
97                            const std::string& serviceRoot,
98                            const std::string& match)
99 {
100     auto objectTree = getAllDbusObjects(bus, serviceRoot, interface, match);
101 
102     if (objectTree.empty())
103     {
104         log<level::ERR>("No Object has implemented the IP interface",
105                         entry("INTERFACE=%s", interface.c_str()));
106         elog<InternalFailure>();
107     }
108 
109     DbusObjectInfo objectInfo;
110 
111     for (auto& object : objectTree)
112     {
113         auto variant = ipmi::getDbusProperty(
114             bus, object.second.begin()->first, object.first,
115             ipmi::network::IP_INTERFACE, "Address");
116 
117         objectInfo = std::make_pair(object.first, object.second.begin()->first);
118 
119         // if LinkLocalIP found look for Non-LinkLocalIP
120         if (ipmi::network::isLinkLocalIP(std::get<std::string>(variant)))
121         {
122             continue;
123         }
124         else
125         {
126             break;
127         }
128     }
129     return objectInfo;
130 }
131 
132 Value getDbusProperty(sdbusplus::bus::bus& bus, const std::string& service,
133                       const std::string& objPath, const std::string& interface,
134                       const std::string& property,
135                       std::chrono::microseconds timeout)
136 {
137 
138     Value value;
139 
140     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
141                                       PROP_INTF, METHOD_GET);
142 
143     method.append(interface, property);
144 
145     auto reply = bus.call(method, timeout.count());
146 
147     if (reply.is_method_error())
148     {
149         log<level::ERR>("Failed to get property",
150                         entry("PROPERTY=%s", property.c_str()),
151                         entry("PATH=%s", objPath.c_str()),
152                         entry("INTERFACE=%s", interface.c_str()));
153         elog<InternalFailure>();
154     }
155 
156     reply.read(value);
157 
158     return value;
159 }
160 
161 PropertyMap getAllDbusProperties(sdbusplus::bus::bus& bus,
162                                  const std::string& service,
163                                  const std::string& objPath,
164                                  const std::string& interface,
165                                  std::chrono::microseconds timeout)
166 {
167     PropertyMap properties;
168 
169     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
170                                       PROP_INTF, METHOD_GET_ALL);
171 
172     method.append(interface);
173 
174     auto reply = bus.call(method, timeout.count());
175 
176     if (reply.is_method_error())
177     {
178         log<level::ERR>("Failed to get all properties",
179                         entry("PATH=%s", objPath.c_str()),
180                         entry("INTERFACE=%s", interface.c_str()));
181         elog<InternalFailure>();
182     }
183 
184     reply.read(properties);
185     return properties;
186 }
187 
188 ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
189                                   const std::string& service,
190                                   const std::string& objPath)
191 {
192     ipmi::ObjectValueTree interfaces;
193 
194     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
195                                       "org.freedesktop.DBus.ObjectManager",
196                                       "GetManagedObjects");
197 
198     auto reply = bus.call(method);
199 
200     if (reply.is_method_error())
201     {
202         log<level::ERR>("Failed to get managed objects",
203                         entry("PATH=%s", objPath.c_str()));
204         elog<InternalFailure>();
205     }
206 
207     reply.read(interfaces);
208     return interfaces;
209 }
210 
211 void setDbusProperty(sdbusplus::bus::bus& bus, const std::string& service,
212                      const std::string& objPath, const std::string& interface,
213                      const std::string& property, const Value& value,
214                      std::chrono::microseconds timeout)
215 {
216     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
217                                       PROP_INTF, METHOD_SET);
218 
219     method.append(interface, property, value);
220 
221     if (!bus.call(method, timeout.count()))
222     {
223         log<level::ERR>("Failed to set property",
224                         entry("PROPERTY=%s", property.c_str()),
225                         entry("PATH=%s", objPath.c_str()),
226                         entry("INTERFACE=%s", interface.c_str()));
227         elog<InternalFailure>();
228     }
229 }
230 
231 ServiceCache::ServiceCache(const std::string& intf, const std::string& path) :
232     intf(intf), path(path), cachedService(std::nullopt),
233     cachedBusName(std::nullopt)
234 {
235 }
236 
237 ServiceCache::ServiceCache(std::string&& intf, std::string&& path) :
238     intf(std::move(intf)), path(std::move(path)), cachedService(std::nullopt),
239     cachedBusName(std::nullopt)
240 {
241 }
242 
243 const std::string& ServiceCache::getService(sdbusplus::bus::bus& bus)
244 {
245     if (!isValid(bus))
246     {
247         cachedBusName = bus.get_unique_name();
248         cachedService = ::ipmi::getService(bus, intf, path);
249     }
250     return cachedService.value();
251 }
252 
253 void ServiceCache::invalidate()
254 {
255     cachedBusName = std::nullopt;
256     cachedService = std::nullopt;
257 }
258 
259 sdbusplus::message::message
260     ServiceCache::newMethodCall(sdbusplus::bus::bus& bus, const char* intf,
261                                 const char* method)
262 {
263     return bus.new_method_call(getService(bus).c_str(), path.c_str(), intf,
264                                method);
265 }
266 
267 bool ServiceCache::isValid(sdbusplus::bus::bus& bus) const
268 {
269     return cachedService && cachedBusName == bus.get_unique_name();
270 }
271 
272 std::string getService(sdbusplus::bus::bus& bus, const std::string& intf,
273                        const std::string& path)
274 {
275     auto mapperCall =
276         bus.new_method_call("xyz.openbmc_project.ObjectMapper",
277                             "/xyz/openbmc_project/object_mapper",
278                             "xyz.openbmc_project.ObjectMapper", "GetObject");
279 
280     mapperCall.append(path);
281     mapperCall.append(std::vector<std::string>({intf}));
282 
283     auto mapperResponseMsg = bus.call(mapperCall);
284 
285     if (mapperResponseMsg.is_method_error())
286     {
287         throw std::runtime_error("ERROR in mapper call");
288     }
289 
290     std::map<std::string, std::vector<std::string>> mapperResponse;
291     mapperResponseMsg.read(mapperResponse);
292 
293     if (mapperResponse.begin() == mapperResponse.end())
294     {
295         throw std::runtime_error("ERROR in reading the mapper response");
296     }
297 
298     return mapperResponse.begin()->first;
299 }
300 
301 ipmi::ObjectTree getAllDbusObjects(sdbusplus::bus::bus& bus,
302                                    const std::string& serviceRoot,
303                                    const std::string& interface,
304                                    const std::string& match)
305 {
306     std::vector<std::string> interfaces;
307     interfaces.emplace_back(interface);
308 
309     auto depth = 0;
310 
311     auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
312                                           MAPPER_INTF, "GetSubTree");
313 
314     mapperCall.append(serviceRoot, depth, interfaces);
315 
316     auto mapperReply = bus.call(mapperCall);
317     if (mapperReply.is_method_error())
318     {
319         log<level::ERR>("Error in mapper call",
320                         entry("SERVICEROOT=%s", serviceRoot.c_str()),
321                         entry("INTERFACE=%s", interface.c_str()));
322 
323         elog<InternalFailure>();
324     }
325 
326     ObjectTree objectTree;
327     mapperReply.read(objectTree);
328 
329     for (auto it = objectTree.begin(); it != objectTree.end();)
330     {
331         if (it->first.find(match) == std::string::npos)
332         {
333             it = objectTree.erase(it);
334         }
335         else
336         {
337             ++it;
338         }
339     }
340 
341     return objectTree;
342 }
343 
344 void deleteAllDbusObjects(sdbusplus::bus::bus& bus,
345                           const std::string& serviceRoot,
346                           const std::string& interface,
347                           const std::string& match)
348 {
349     try
350     {
351         auto objectTree = getAllDbusObjects(bus, serviceRoot, interface, match);
352 
353         for (auto& object : objectTree)
354         {
355             method_no_args::callDbusMethod(bus, object.second.begin()->first,
356                                            object.first, DELETE_INTERFACE,
357                                            "Delete");
358         }
359     }
360     catch (sdbusplus::exception::exception& e)
361     {
362         log<level::INFO>("sdbusplus exception - Unable to delete the objects",
363                          entry("ERROR=%s", e.what()),
364                          entry("INTERFACE=%s", interface.c_str()),
365                          entry("SERVICE=%s", serviceRoot.c_str()));
366     }
367 }
368 
369 ObjectTree getAllAncestors(sdbusplus::bus::bus& bus, const std::string& path,
370                            InterfaceList&& interfaces)
371 {
372     auto convertToString = [](InterfaceList& interfaces) -> std::string {
373         std::string intfStr;
374         for (const auto& intf : interfaces)
375         {
376             intfStr += "," + intf;
377         }
378         return intfStr;
379     };
380 
381     auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
382                                           MAPPER_INTF, "GetAncestors");
383     mapperCall.append(path, interfaces);
384 
385     auto mapperReply = bus.call(mapperCall);
386     if (mapperReply.is_method_error())
387     {
388         log<level::ERR>(
389             "Error in mapper call", entry("PATH=%s", path.c_str()),
390             entry("INTERFACES=%s", convertToString(interfaces).c_str()));
391 
392         elog<InternalFailure>();
393     }
394 
395     ObjectTree objectTree;
396     mapperReply.read(objectTree);
397 
398     if (objectTree.empty())
399     {
400         log<level::ERR>(
401             "No Object has implemented the interface",
402             entry("PATH=%s", path.c_str()),
403             entry("INTERFACES=%s", convertToString(interfaces).c_str()));
404         elog<InternalFailure>();
405     }
406 
407     return objectTree;
408 }
409 
410 namespace method_no_args
411 {
412 
413 void callDbusMethod(sdbusplus::bus::bus& bus, const std::string& service,
414                     const std::string& objPath, const std::string& interface,
415                     const std::string& method)
416 
417 {
418     auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(),
419                                          interface.c_str(), method.c_str());
420 
421     auto reply = bus.call(busMethod);
422 
423     if (reply.is_method_error())
424     {
425         log<level::ERR>("Failed to execute method",
426                         entry("METHOD=%s", method.c_str()),
427                         entry("PATH=%s", objPath.c_str()),
428                         entry("INTERFACE=%s", interface.c_str()));
429         elog<InternalFailure>();
430     }
431 }
432 
433 } // namespace method_no_args
434 
435 namespace network
436 {
437 
438 bool isLinkLocalIP(const std::string& address)
439 {
440     return address.find(IPV4_PREFIX) == 0 || address.find(IPV6_PREFIX) == 0;
441 }
442 
443 void createIP(sdbusplus::bus::bus& bus, const std::string& service,
444               const std::string& objPath, const std::string& protocolType,
445               const std::string& ipaddress, uint8_t prefix)
446 {
447     std::string gateway = "";
448 
449     auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(),
450                                          IP_CREATE_INTERFACE, "IP");
451 
452     busMethod.append(protocolType, ipaddress, prefix, gateway);
453 
454     auto reply = bus.call(busMethod);
455 
456     if (reply.is_method_error())
457     {
458         log<level::ERR>("Failed to execute method", entry("METHOD=%s", "IP"),
459                         entry("PATH=%s", objPath.c_str()));
460         elog<InternalFailure>();
461     }
462 }
463 
464 void createVLAN(sdbusplus::bus::bus& bus, const std::string& service,
465                 const std::string& objPath, const std::string& interfaceName,
466                 uint32_t vlanID)
467 {
468     auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(),
469                                          VLAN_CREATE_INTERFACE, "VLAN");
470 
471     busMethod.append(interfaceName, vlanID);
472 
473     auto reply = bus.call(busMethod);
474 
475     if (reply.is_method_error())
476     {
477         log<level::ERR>("Failed to execute method", entry("METHOD=%s", "VLAN"),
478                         entry("PATH=%s", objPath.c_str()));
479         elog<InternalFailure>();
480     }
481 }
482 
483 uint8_t toPrefix(int addressFamily, const std::string& subnetMask)
484 {
485     if (addressFamily == AF_INET6)
486     {
487         return 0;
488     }
489 
490     uint32_t buff{};
491 
492     auto rc = inet_pton(addressFamily, subnetMask.c_str(), &buff);
493     if (rc <= 0)
494     {
495         log<level::ERR>("inet_pton failed:",
496                         entry("SUBNETMASK=%s", subnetMask.c_str()));
497         return 0;
498     }
499 
500     buff = be32toh(buff);
501     // total no of bits - total no of leading zero == total no of ones
502     if (((sizeof(buff) * 8) - (__builtin_ctz(buff))) ==
503         __builtin_popcount(buff))
504     {
505         return __builtin_popcount(buff);
506     }
507     else
508     {
509         log<level::ERR>("Invalid Mask",
510                         entry("SUBNETMASK=%s", subnetMask.c_str()));
511         return 0;
512     }
513 }
514 
515 uint32_t getVLAN(const std::string& path)
516 {
517     // Path would be look like
518     // /xyz/openbmc_project/network/eth0_443/ipv4
519 
520     uint32_t vlanID = 0;
521     try
522     {
523         auto intfObjectPath = path.substr(0, path.find(IP_TYPE) - 1);
524 
525         auto intfName = intfObjectPath.substr(intfObjectPath.rfind("/") + 1);
526 
527         auto index = intfName.find("_");
528         if (index != std::string::npos)
529         {
530             auto str = intfName.substr(index + 1);
531             vlanID = std::stoul(str);
532         }
533     }
534     catch (std::exception& e)
535     {
536         log<level::ERR>("Exception occurred during getVLAN",
537                         entry("PATH=%s", path.c_str()),
538                         entry("EXCEPTION=%s", e.what()));
539     }
540     return vlanID;
541 }
542 
543 } // namespace network
544 } // namespace ipmi
545