xref: /openbmc/phosphor-networkd/src/util.cpp (revision a6c07570)
1 #include "util.hpp"
2 
3 #include "config_parser.hpp"
4 #include "types.hpp"
5 
6 #include <arpa/inet.h>
7 #include <dirent.h>
8 #include <fmt/compile.h>
9 #include <fmt/format.h>
10 #include <net/if.h>
11 #include <sys/wait.h>
12 
13 #include <algorithm>
14 #include <cctype>
15 #include <cstdlib>
16 #include <cstring>
17 #include <filesystem>
18 #include <fstream>
19 #include <list>
20 #ifdef SYNC_MAC_FROM_INVENTORY
21 #include <nlohmann/json.hpp>
22 #endif
23 #include <phosphor-logging/elog-errors.hpp>
24 #include <phosphor-logging/log.hpp>
25 #include <stdexcept>
26 #include <stdplus/raw.hpp>
27 #include <string>
28 #include <variant>
29 #include <xyz/openbmc_project/Common/error.hpp>
30 
31 namespace phosphor
32 {
33 namespace network
34 {
35 
36 using namespace phosphor::logging;
37 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
38 namespace fs = std::filesystem;
39 
40 namespace internal
41 {
42 
43 void executeCommandinChildProcess(const char* path, char** args)
44 {
45     using namespace std::string_literals;
46     pid_t pid = fork();
47     int status{};
48 
49     if (pid == 0)
50     {
51         execv(path, args);
52         auto error = errno;
53         // create the command from var args.
54         std::string command = path + " "s;
55 
56         for (int i = 0; args[i]; i++)
57         {
58             command += args[i] + " "s;
59         }
60 
61         log<level::ERR>("Couldn't exceute the command",
62                         entry("ERRNO=%d", error),
63                         entry("CMD=%s", command.c_str()));
64         elog<InternalFailure>();
65     }
66     else if (pid < 0)
67     {
68         auto error = errno;
69         log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
70         elog<InternalFailure>();
71     }
72     else if (pid > 0)
73     {
74         while (waitpid(pid, &status, 0) == -1)
75         {
76             if (errno != EINTR)
77             { // Error other than EINTR
78                 status = -1;
79                 break;
80             }
81         }
82 
83         if (status < 0)
84         {
85             std::string command = path + " "s;
86             for (int i = 0; args[i]; i++)
87             {
88                 command += args[i] + " "s;
89             }
90 
91             log<level::ERR>("Unable to execute the command",
92                             entry("CMD=%s", command.c_str()),
93                             entry("STATUS=%d", status));
94             elog<InternalFailure>();
95         }
96     }
97 }
98 
99 /** @brief Get ignored interfaces from environment */
100 std::string_view getIgnoredInterfacesEnv()
101 {
102     auto r = std::getenv("IGNORED_INTERFACES");
103     if (r == nullptr)
104     {
105         return "";
106     }
107     return r;
108 }
109 
110 /** @brief Parse the comma separated interface names */
111 std::set<std::string_view> parseInterfaces(std::string_view interfaces)
112 {
113     std::set<std::string_view> result;
114     while (true)
115     {
116         auto sep = interfaces.find(',');
117         auto interface = interfaces.substr(0, sep);
118         while (!interface.empty() && std::isspace(interface.front()))
119         {
120             interface.remove_prefix(1);
121         }
122         while (!interface.empty() && std::isspace(interface.back()))
123         {
124             interface.remove_suffix(1);
125         }
126         if (!interface.empty())
127         {
128             result.insert(interface);
129         }
130         if (sep == interfaces.npos)
131         {
132             break;
133         }
134         interfaces = interfaces.substr(sep + 1);
135     }
136     return result;
137 }
138 
139 /** @brief Get the ignored interfaces */
140 const std::set<std::string_view>& getIgnoredInterfaces()
141 {
142     static auto ignoredInterfaces = parseInterfaces(getIgnoredInterfacesEnv());
143     return ignoredInterfaces;
144 }
145 
146 } // namespace internal
147 
148 uint8_t toCidr(int addressFamily, const std::string& subnetMask)
149 {
150     uint32_t subnet[sizeof(in6_addr) / sizeof(uint32_t)];
151     if (inet_pton(addressFamily, subnetMask.c_str(), &subnet) != 1)
152     {
153         log<level::ERR>("inet_pton failed:",
154                         entry("SUBNETMASK=%s", subnetMask.c_str()));
155         return 0;
156     }
157 
158     static_assert(sizeof(in6_addr) % sizeof(uint32_t) == 0);
159     static_assert(sizeof(in_addr) % sizeof(uint32_t) == 0);
160     auto i = (addressFamily == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)) /
161              sizeof(uint32_t);
162     while (i > 0)
163     {
164         if (subnet[--i] != 0)
165         {
166             auto v = be32toh(subnet[i]);
167             static_assert(sizeof(unsigned) == sizeof(uint32_t));
168             auto trailing = __builtin_ctz(v);
169             auto ret = (i + 1) * 32 - trailing;
170             bool valid = ~v == 0 || 32 == trailing + __builtin_clz(~v);
171             while (i > 0 && (valid = (~subnet[--i] == 0) && valid))
172                 ;
173             if (!valid)
174             {
175                 log<level::ERR>("Invalid netmask",
176                                 entry("SUBNETMASK=%s", subnetMask.c_str()));
177                 return 0;
178             }
179             return ret;
180         }
181     }
182     return 0;
183 }
184 
185 std::string toMask(int addressFamily, uint8_t prefix)
186 {
187     if (addressFamily == AF_INET6)
188     {
189         // TODO:- conversion for v6
190         return "";
191     }
192 
193     if (prefix < 1 || prefix > 30)
194     {
195         log<level::ERR>("Invalid Prefix", entry("PREFIX=%d", prefix));
196         return "";
197     }
198     /* Create the netmask from the number of bits */
199     unsigned long mask = 0;
200     for (auto i = 0; i < prefix; i++)
201     {
202         mask |= 1 << (31 - i);
203     }
204     struct in_addr netmask;
205     netmask.s_addr = htonl(mask);
206     return inet_ntoa(netmask);
207 }
208 
209 InAddrAny addrFromBuf(int addressFamily, std::string_view buf)
210 {
211     if (addressFamily == AF_INET)
212     {
213         struct in_addr ret;
214         if (buf.size() != sizeof(ret))
215         {
216             throw std::runtime_error("Buf not in_addr sized");
217         }
218         memcpy(&ret, buf.data(), sizeof(ret));
219         return ret;
220     }
221     else if (addressFamily == AF_INET6)
222     {
223         struct in6_addr ret;
224         if (buf.size() != sizeof(ret))
225         {
226             throw std::runtime_error("Buf not in6_addr sized");
227         }
228         memcpy(&ret, buf.data(), sizeof(ret));
229         return ret;
230     }
231 
232     throw std::runtime_error("Unsupported address family");
233 }
234 
235 std::string toString(const struct in_addr& addr)
236 {
237     std::string ip(INET_ADDRSTRLEN, '\0');
238     if (inet_ntop(AF_INET, &addr, ip.data(), ip.size()) == nullptr)
239     {
240         throw std::runtime_error("Failed to convert IP4 to string");
241     }
242 
243     ip.resize(strlen(ip.c_str()));
244     return ip;
245 }
246 
247 std::string toString(const struct in6_addr& addr)
248 {
249     std::string ip(INET6_ADDRSTRLEN, '\0');
250     if (inet_ntop(AF_INET6, &addr, ip.data(), ip.size()) == nullptr)
251     {
252         throw std::runtime_error("Failed to convert IP6 to string");
253     }
254 
255     ip.resize(strlen(ip.c_str()));
256     return ip;
257 }
258 
259 std::string toString(const InAddrAny& addr)
260 {
261     if (std::holds_alternative<struct in_addr>(addr))
262     {
263         const auto& v = std::get<struct in_addr>(addr);
264         return toString(v);
265     }
266     else if (std::holds_alternative<struct in6_addr>(addr))
267     {
268         const auto& v = std::get<struct in6_addr>(addr);
269         return toString(v);
270     }
271 
272     throw std::runtime_error("Invalid addr type");
273 }
274 
275 bool isLinkLocalIP(const std::string& address)
276 {
277     return address.find(IPV4_PREFIX) == 0 || address.find(IPV6_PREFIX) == 0;
278 }
279 
280 bool isValidIP(int addressFamily, const std::string& address)
281 {
282     unsigned char buf[sizeof(struct in6_addr)];
283 
284     return inet_pton(addressFamily, address.c_str(), buf) > 0;
285 }
286 
287 bool isValidPrefix(int addressFamily, uint8_t prefixLength)
288 {
289     if (addressFamily == AF_INET)
290     {
291         if (prefixLength < IPV4_MIN_PREFIX_LENGTH ||
292             prefixLength > IPV4_MAX_PREFIX_LENGTH)
293         {
294             return false;
295         }
296     }
297 
298     if (addressFamily == AF_INET6)
299     {
300         if (prefixLength < IPV4_MIN_PREFIX_LENGTH ||
301             prefixLength > IPV6_MAX_PREFIX_LENGTH)
302         {
303             return false;
304         }
305     }
306 
307     return true;
308 }
309 
310 IntfAddrMap getInterfaceAddrs()
311 {
312     IntfAddrMap intfMap{};
313     struct ifaddrs* ifaddr = nullptr;
314 
315     // attempt to fill struct with ifaddrs
316     if (getifaddrs(&ifaddr) == -1)
317     {
318         auto error = errno;
319         log<level::ERR>("Error occurred during the getifaddrs call",
320                         entry("ERRNO=%s", strerror(error)));
321         elog<InternalFailure>();
322     }
323 
324     AddrPtr ifaddrPtr(ifaddr);
325     ifaddr = nullptr;
326 
327     std::string intfName{};
328 
329     for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
330     {
331         // walk interfaces
332         if (ifa->ifa_addr == nullptr)
333         {
334             continue;
335         }
336 
337         // get only INET interfaces not ipv6
338         if (ifa->ifa_addr->sa_family == AF_INET ||
339             ifa->ifa_addr->sa_family == AF_INET6)
340         {
341             // if loopback, or not running ignore
342             if ((ifa->ifa_flags & IFF_LOOPBACK) ||
343                 !(ifa->ifa_flags & IFF_RUNNING))
344             {
345                 continue;
346             }
347             intfName = ifa->ifa_name;
348             AddrInfo info{};
349             char ip[INET6_ADDRSTRLEN] = {0};
350             char subnetMask[INET6_ADDRSTRLEN] = {0};
351 
352             if (ifa->ifa_addr->sa_family == AF_INET)
353             {
354 
355                 inet_ntop(ifa->ifa_addr->sa_family,
356                           &(((struct sockaddr_in*)(ifa->ifa_addr))->sin_addr),
357                           ip, sizeof(ip));
358 
359                 inet_ntop(
360                     ifa->ifa_addr->sa_family,
361                     &(((struct sockaddr_in*)(ifa->ifa_netmask))->sin_addr),
362                     subnetMask, sizeof(subnetMask));
363             }
364             else
365             {
366                 inet_ntop(ifa->ifa_addr->sa_family,
367                           &(((struct sockaddr_in6*)(ifa->ifa_addr))->sin6_addr),
368                           ip, sizeof(ip));
369 
370                 inet_ntop(
371                     ifa->ifa_addr->sa_family,
372                     &(((struct sockaddr_in6*)(ifa->ifa_netmask))->sin6_addr),
373                     subnetMask, sizeof(subnetMask));
374             }
375 
376             info.addrType = ifa->ifa_addr->sa_family;
377             info.ipaddress = ip;
378             info.prefix = toCidr(info.addrType, std::string(subnetMask));
379             intfMap[intfName].push_back(info);
380         }
381     }
382     return intfMap;
383 }
384 
385 InterfaceList getInterfaces()
386 {
387     InterfaceList interfaces{};
388     struct ifaddrs* ifaddr = nullptr;
389 
390     // attempt to fill struct with ifaddrs
391     if (getifaddrs(&ifaddr) == -1)
392     {
393         auto error = errno;
394         log<level::ERR>("Error occurred during the getifaddrs call",
395                         entry("ERRNO=%d", error));
396         elog<InternalFailure>();
397     }
398 
399     AddrPtr ifaddrPtr(ifaddr);
400     ifaddr = nullptr;
401     const auto& ignoredInterfaces = internal::getIgnoredInterfaces();
402 
403     for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
404     {
405         // walk interfaces
406         // if loopback ignore
407         if (ifa->ifa_flags & IFF_LOOPBACK ||
408             ignoredInterfaces.find(ifa->ifa_name) != ignoredInterfaces.end())
409         {
410             continue;
411         }
412         interfaces.emplace(ifa->ifa_name);
413     }
414     return interfaces;
415 }
416 
417 void deleteInterface(const std::string& intf)
418 {
419     pid_t pid = fork();
420     int status{};
421 
422     if (pid == 0)
423     {
424 
425         execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr);
426         auto error = errno;
427         log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error),
428                         entry("INTF=%s", intf.c_str()));
429         elog<InternalFailure>();
430     }
431     else if (pid < 0)
432     {
433         auto error = errno;
434         log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
435         elog<InternalFailure>();
436     }
437     else if (pid > 0)
438     {
439         while (waitpid(pid, &status, 0) == -1)
440         {
441             if (errno != EINTR)
442             { /* Error other than EINTR */
443                 status = -1;
444                 break;
445             }
446         }
447 
448         if (status < 0)
449         {
450             log<level::ERR>("Unable to delete the interface",
451                             entry("INTF=%s", intf.c_str()),
452                             entry("STATUS=%d", status));
453             elog<InternalFailure>();
454         }
455     }
456 }
457 
458 std::optional<std::string> interfaceToUbootEthAddr(const char* intf)
459 {
460     constexpr char ethPrefix[] = "eth";
461     constexpr size_t ethPrefixLen = sizeof(ethPrefix) - 1;
462     if (strncmp(ethPrefix, intf, ethPrefixLen) != 0)
463     {
464         return std::nullopt;
465     }
466     const auto intfSuffix = intf + ethPrefixLen;
467     if (intfSuffix[0] == '\0')
468     {
469         return std::nullopt;
470     }
471     char* end;
472     unsigned long idx = strtoul(intfSuffix, &end, 10);
473     if (end[0] != '\0')
474     {
475         return std::nullopt;
476     }
477     if (idx == 0)
478     {
479         return "ethaddr";
480     }
481     return "eth" + std::to_string(idx) + "addr";
482 }
483 
484 EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
485                                              const std::string& intf)
486 {
487     EthernetInterfaceIntf::DHCPConf dhcp =
488         EthernetInterfaceIntf::DHCPConf::none;
489     // Get the interface mode value from systemd conf
490     // using namespace std::string_literals;
491     fs::path confPath = confDir;
492     std::string fileName = systemd::config::networkFilePrefix + intf +
493                            systemd::config::networkFileSuffix;
494     confPath /= fileName;
495 
496     auto rc = config::ReturnCode::SUCCESS;
497     config::ValueList values;
498     config::Parser parser(confPath.string());
499 
500     std::tie(rc, values) = parser.getValues("Network", "DHCP");
501     if (rc != config::ReturnCode::SUCCESS)
502     {
503         log<level::DEBUG>("Unable to get the value for Network[DHCP]",
504                           entry("RC=%d", rc));
505         return dhcp;
506     }
507     // There will be only single value for DHCP key.
508     if (values[0] == "true")
509     {
510         dhcp = EthernetInterfaceIntf::DHCPConf::both;
511     }
512     else if (values[0] == "ipv4")
513     {
514         dhcp = EthernetInterfaceIntf::DHCPConf::v4;
515     }
516     else if (values[0] == "ipv6")
517     {
518         dhcp = EthernetInterfaceIntf::DHCPConf::v6;
519     }
520     return dhcp;
521 }
522 
523 namespace mac_address
524 {
525 
526 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
527 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
528 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
529 constexpr auto propIntf = "org.freedesktop.DBus.Properties";
530 constexpr auto methodGet = "Get";
531 constexpr auto configFile = "/usr/share/network/config.json";
532 
533 using DbusObjectPath = std::string;
534 using DbusService = std::string;
535 using DbusInterface = std::string;
536 using ObjectTree =
537     std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
538 
539 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
540 constexpr auto invNetworkIntf =
541     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
542 constexpr auto invRoot = "/xyz/openbmc_project/inventory";
543 
544 ether_addr getfromInventory(sdbusplus::bus::bus& bus,
545                             const std::string& intfName)
546 {
547 
548     std::string interfaceName = intfName;
549 
550 #ifdef SYNC_MAC_FROM_INVENTORY
551     // load the config JSON from the Read Only Path
552     std::ifstream in(configFile);
553     nlohmann::json configJson;
554     in >> configJson;
555     interfaceName = configJson[intfName];
556 #endif
557 
558     std::vector<DbusInterface> interfaces;
559     interfaces.emplace_back(invNetworkIntf);
560 
561     auto depth = 0;
562 
563     auto mapperCall =
564         bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
565 
566     mapperCall.append(invRoot, depth, interfaces);
567 
568     auto mapperReply = bus.call(mapperCall);
569     if (mapperReply.is_method_error())
570     {
571         log<level::ERR>("Error in mapper call");
572         elog<InternalFailure>();
573     }
574 
575     ObjectTree objectTree;
576     mapperReply.read(objectTree);
577 
578     if (objectTree.empty())
579     {
580         log<level::ERR>("No Object has implemented the interface",
581                         entry("INTERFACE=%s", invNetworkIntf));
582         elog<InternalFailure>();
583     }
584 
585     DbusObjectPath objPath;
586     DbusService service;
587 
588     if (1 == objectTree.size())
589     {
590         objPath = objectTree.begin()->first;
591         service = objectTree.begin()->second.begin()->first;
592     }
593     else
594     {
595         // If there are more than 2 objects, object path must contain the
596         // interface name
597         for (auto const& object : objectTree)
598         {
599             log<level::INFO>("interface",
600                              entry("INT=%s", interfaceName.c_str()));
601             log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
602 
603             if (std::string::npos != object.first.find(interfaceName.c_str()))
604             {
605                 objPath = object.first;
606                 service = object.second.begin()->first;
607                 break;
608             }
609         }
610 
611         if (objPath.empty())
612         {
613             log<level::ERR>("Can't find the object for the interface",
614                             entry("intfName=%s", interfaceName.c_str()));
615             elog<InternalFailure>();
616         }
617     }
618 
619     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
620                                       propIntf, methodGet);
621 
622     method.append(invNetworkIntf, "MACAddress");
623 
624     auto reply = bus.call(method);
625     if (reply.is_method_error())
626     {
627         log<level::ERR>("Failed to get MACAddress",
628                         entry("PATH=%s", objPath.c_str()),
629                         entry("INTERFACE=%s", invNetworkIntf));
630         elog<InternalFailure>();
631     }
632 
633     std::variant<std::string> value;
634     reply.read(value);
635     return fromString(std::get<std::string>(value));
636 }
637 
638 ether_addr fromString(const char* str)
639 {
640     std::string genstr;
641 
642     // MAC address without colons
643     std::string_view strv = str;
644     if (strv.size() == 12 && strv.find(":") == strv.npos)
645     {
646         genstr =
647             fmt::format(FMT_COMPILE("{}:{}:{}:{}:{}:{}"), strv.substr(0, 2),
648                         strv.substr(2, 2), strv.substr(4, 2), strv.substr(6, 2),
649                         strv.substr(8, 2), strv.substr(10, 2));
650         str = genstr.c_str();
651     }
652 
653     ether_addr addr;
654     if (ether_aton_r(str, &addr) == nullptr)
655     {
656         throw std::invalid_argument("Invalid MAC Address");
657     }
658     return addr;
659 }
660 
661 std::string toString(const ether_addr& mac)
662 {
663     char buf[18] = {0};
664     snprintf(buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", mac.ether_addr_octet[0],
665              mac.ether_addr_octet[1], mac.ether_addr_octet[2],
666              mac.ether_addr_octet[3], mac.ether_addr_octet[4],
667              mac.ether_addr_octet[5]);
668     return buf;
669 }
670 
671 bool isEmpty(const ether_addr& mac)
672 {
673     return stdplus::raw::equal(mac, ether_addr{});
674 }
675 
676 bool isMulticast(const ether_addr& mac)
677 {
678     return mac.ether_addr_octet[0] & 0b1;
679 }
680 
681 bool isUnicast(const ether_addr& mac)
682 {
683     return !isEmpty(mac) && !isMulticast(mac);
684 }
685 
686 } // namespace mac_address
687 } // namespace network
688 } // namespace phosphor
689