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