xref: /openbmc/phosphor-networkd/src/util.cpp (revision 4b604171f5bbcfc97db33908f46cfe30cf30e95d)
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 std::string toMask(int addressFamily, uint8_t prefix)
149 {
150     if (addressFamily == AF_INET6)
151     {
152         // TODO:- conversion for v6
153         return "";
154     }
155 
156     if (prefix < 1 || prefix > 30)
157     {
158         log<level::ERR>("Invalid Prefix", entry("PREFIX=%d", prefix));
159         return "";
160     }
161     /* Create the netmask from the number of bits */
162     unsigned long mask = 0;
163     for (auto i = 0; i < prefix; i++)
164     {
165         mask |= 1 << (31 - i);
166     }
167     struct in_addr netmask;
168     netmask.s_addr = htonl(mask);
169     return inet_ntoa(netmask);
170 }
171 
172 InAddrAny addrFromBuf(int addressFamily, std::string_view buf)
173 {
174     if (addressFamily == AF_INET)
175     {
176         struct in_addr ret;
177         if (buf.size() != sizeof(ret))
178         {
179             throw std::runtime_error("Buf not in_addr sized");
180         }
181         memcpy(&ret, buf.data(), sizeof(ret));
182         return ret;
183     }
184     else if (addressFamily == AF_INET6)
185     {
186         struct in6_addr ret;
187         if (buf.size() != sizeof(ret))
188         {
189             throw std::runtime_error("Buf not in6_addr sized");
190         }
191         memcpy(&ret, buf.data(), sizeof(ret));
192         return ret;
193     }
194 
195     throw std::runtime_error("Unsupported address family");
196 }
197 
198 std::string toString(const struct in_addr& addr)
199 {
200     std::string ip(INET_ADDRSTRLEN, '\0');
201     if (inet_ntop(AF_INET, &addr, ip.data(), ip.size()) == nullptr)
202     {
203         throw std::runtime_error("Failed to convert IP4 to string");
204     }
205 
206     ip.resize(strlen(ip.c_str()));
207     return ip;
208 }
209 
210 std::string toString(const struct in6_addr& addr)
211 {
212     std::string ip(INET6_ADDRSTRLEN, '\0');
213     if (inet_ntop(AF_INET6, &addr, ip.data(), ip.size()) == nullptr)
214     {
215         throw std::runtime_error("Failed to convert IP6 to string");
216     }
217 
218     ip.resize(strlen(ip.c_str()));
219     return ip;
220 }
221 
222 std::string toString(const InAddrAny& addr)
223 {
224     if (std::holds_alternative<struct in_addr>(addr))
225     {
226         const auto& v = std::get<struct in_addr>(addr);
227         return toString(v);
228     }
229     else if (std::holds_alternative<struct in6_addr>(addr))
230     {
231         const auto& v = std::get<struct in6_addr>(addr);
232         return toString(v);
233     }
234 
235     throw std::runtime_error("Invalid addr type");
236 }
237 
238 bool isValidIP(int addressFamily, const std::string& address)
239 {
240     unsigned char buf[sizeof(struct in6_addr)];
241 
242     return inet_pton(addressFamily, address.c_str(), buf) > 0;
243 }
244 
245 bool isValidPrefix(int addressFamily, uint8_t prefixLength)
246 {
247     if (addressFamily == AF_INET)
248     {
249         if (prefixLength < IPV4_MIN_PREFIX_LENGTH ||
250             prefixLength > IPV4_MAX_PREFIX_LENGTH)
251         {
252             return false;
253         }
254     }
255 
256     if (addressFamily == AF_INET6)
257     {
258         if (prefixLength < IPV4_MIN_PREFIX_LENGTH ||
259             prefixLength > IPV6_MAX_PREFIX_LENGTH)
260         {
261             return false;
262         }
263     }
264 
265     return true;
266 }
267 
268 InterfaceList getInterfaces()
269 {
270     InterfaceList interfaces{};
271     struct ifaddrs* ifaddr = nullptr;
272 
273     // attempt to fill struct with ifaddrs
274     if (getifaddrs(&ifaddr) == -1)
275     {
276         auto error = errno;
277         log<level::ERR>("Error occurred during the getifaddrs call",
278                         entry("ERRNO=%d", error));
279         elog<InternalFailure>();
280     }
281 
282     AddrPtr ifaddrPtr(ifaddr);
283     ifaddr = nullptr;
284     const auto& ignoredInterfaces = internal::getIgnoredInterfaces();
285 
286     for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
287     {
288         // walk interfaces
289         // if loopback ignore
290         if (ifa->ifa_flags & IFF_LOOPBACK ||
291             ignoredInterfaces.find(ifa->ifa_name) != ignoredInterfaces.end())
292         {
293             continue;
294         }
295         interfaces.emplace(ifa->ifa_name);
296     }
297     return interfaces;
298 }
299 
300 void deleteInterface(const std::string& intf)
301 {
302     pid_t pid = fork();
303     int status{};
304 
305     if (pid == 0)
306     {
307 
308         execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr);
309         auto error = errno;
310         log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error),
311                         entry("INTF=%s", intf.c_str()));
312         elog<InternalFailure>();
313     }
314     else if (pid < 0)
315     {
316         auto error = errno;
317         log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
318         elog<InternalFailure>();
319     }
320     else if (pid > 0)
321     {
322         while (waitpid(pid, &status, 0) == -1)
323         {
324             if (errno != EINTR)
325             { /* Error other than EINTR */
326                 status = -1;
327                 break;
328             }
329         }
330 
331         if (status < 0)
332         {
333             log<level::ERR>("Unable to delete the interface",
334                             entry("INTF=%s", intf.c_str()),
335                             entry("STATUS=%d", status));
336             elog<InternalFailure>();
337         }
338     }
339 }
340 
341 std::optional<std::string> interfaceToUbootEthAddr(const char* intf)
342 {
343     constexpr char ethPrefix[] = "eth";
344     constexpr size_t ethPrefixLen = sizeof(ethPrefix) - 1;
345     if (strncmp(ethPrefix, intf, ethPrefixLen) != 0)
346     {
347         return std::nullopt;
348     }
349     const auto intfSuffix = intf + ethPrefixLen;
350     if (intfSuffix[0] == '\0')
351     {
352         return std::nullopt;
353     }
354     char* end;
355     unsigned long idx = strtoul(intfSuffix, &end, 10);
356     if (end[0] != '\0')
357     {
358         return std::nullopt;
359     }
360     if (idx == 0)
361     {
362         return "ethaddr";
363     }
364     return "eth" + std::to_string(idx) + "addr";
365 }
366 
367 EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
368                                              const std::string& intf)
369 {
370     EthernetInterfaceIntf::DHCPConf dhcp =
371         EthernetInterfaceIntf::DHCPConf::none;
372     // Get the interface mode value from systemd conf
373     // using namespace std::string_literals;
374     fs::path confPath = confDir;
375     std::string fileName = systemd::config::networkFilePrefix + intf +
376                            systemd::config::networkFileSuffix;
377     confPath /= fileName;
378 
379     config::Parser parser(confPath.string());
380     const auto& values = parser.getValues("Network", "DHCP");
381     if (values.empty())
382     {
383         log<level::NOTICE>("Unable to get the value for Network[DHCP]");
384         return dhcp;
385     }
386     if (values.back() == "true")
387     {
388         dhcp = EthernetInterfaceIntf::DHCPConf::both;
389     }
390     else if (values.back() == "ipv4")
391     {
392         dhcp = EthernetInterfaceIntf::DHCPConf::v4;
393     }
394     else if (values.back() == "ipv6")
395     {
396         dhcp = EthernetInterfaceIntf::DHCPConf::v6;
397     }
398     return dhcp;
399 }
400 
401 namespace mac_address
402 {
403 
404 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
405 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
406 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
407 constexpr auto propIntf = "org.freedesktop.DBus.Properties";
408 constexpr auto methodGet = "Get";
409 constexpr auto configFile = "/usr/share/network/config.json";
410 
411 using DbusObjectPath = std::string;
412 using DbusService = std::string;
413 using DbusInterface = std::string;
414 using ObjectTree =
415     std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
416 
417 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
418 constexpr auto invNetworkIntf =
419     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
420 constexpr auto invRoot = "/xyz/openbmc_project/inventory";
421 
422 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName)
423 {
424 
425     std::string interfaceName = intfName;
426 
427 #ifdef SYNC_MAC_FROM_INVENTORY
428     // load the config JSON from the Read Only Path
429     std::ifstream in(configFile);
430     nlohmann::json configJson;
431     in >> configJson;
432     interfaceName = configJson[intfName];
433 #endif
434 
435     std::vector<DbusInterface> interfaces;
436     interfaces.emplace_back(invNetworkIntf);
437 
438     auto depth = 0;
439 
440     auto mapperCall =
441         bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
442 
443     mapperCall.append(invRoot, depth, interfaces);
444 
445     auto mapperReply = bus.call(mapperCall);
446     if (mapperReply.is_method_error())
447     {
448         log<level::ERR>("Error in mapper call");
449         elog<InternalFailure>();
450     }
451 
452     ObjectTree objectTree;
453     mapperReply.read(objectTree);
454 
455     if (objectTree.empty())
456     {
457         log<level::ERR>("No Object has implemented the interface",
458                         entry("INTERFACE=%s", invNetworkIntf));
459         elog<InternalFailure>();
460     }
461 
462     DbusObjectPath objPath;
463     DbusService service;
464 
465     if (1 == objectTree.size())
466     {
467         objPath = objectTree.begin()->first;
468         service = objectTree.begin()->second.begin()->first;
469     }
470     else
471     {
472         // If there are more than 2 objects, object path must contain the
473         // interface name
474         for (auto const& object : objectTree)
475         {
476             log<level::INFO>("interface",
477                              entry("INT=%s", interfaceName.c_str()));
478             log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
479 
480             if (std::string::npos != object.first.find(interfaceName.c_str()))
481             {
482                 objPath = object.first;
483                 service = object.second.begin()->first;
484                 break;
485             }
486         }
487 
488         if (objPath.empty())
489         {
490             log<level::ERR>("Can't find the object for the interface",
491                             entry("intfName=%s", interfaceName.c_str()));
492             elog<InternalFailure>();
493         }
494     }
495 
496     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
497                                       propIntf, methodGet);
498 
499     method.append(invNetworkIntf, "MACAddress");
500 
501     auto reply = bus.call(method);
502     if (reply.is_method_error())
503     {
504         log<level::ERR>("Failed to get MACAddress",
505                         entry("PATH=%s", objPath.c_str()),
506                         entry("INTERFACE=%s", invNetworkIntf));
507         elog<InternalFailure>();
508     }
509 
510     std::variant<std::string> value;
511     reply.read(value);
512     return fromString(std::get<std::string>(value));
513 }
514 
515 ether_addr fromString(const char* str)
516 {
517     std::string genstr;
518 
519     // MAC address without colons
520     std::string_view strv = str;
521     if (strv.size() == 12 && strv.find(":") == strv.npos)
522     {
523         genstr =
524             fmt::format(FMT_COMPILE("{}:{}:{}:{}:{}:{}"), strv.substr(0, 2),
525                         strv.substr(2, 2), strv.substr(4, 2), strv.substr(6, 2),
526                         strv.substr(8, 2), strv.substr(10, 2));
527         str = genstr.c_str();
528     }
529 
530     ether_addr addr;
531     if (ether_aton_r(str, &addr) == nullptr)
532     {
533         throw std::invalid_argument("Invalid MAC Address");
534     }
535     return addr;
536 }
537 
538 std::string toString(const ether_addr& mac)
539 {
540     char buf[18] = {0};
541     snprintf(buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", mac.ether_addr_octet[0],
542              mac.ether_addr_octet[1], mac.ether_addr_octet[2],
543              mac.ether_addr_octet[3], mac.ether_addr_octet[4],
544              mac.ether_addr_octet[5]);
545     return buf;
546 }
547 
548 bool isEmpty(const ether_addr& mac)
549 {
550     return stdplus::raw::equal(mac, ether_addr{});
551 }
552 
553 bool isMulticast(const ether_addr& mac)
554 {
555     return mac.ether_addr_octet[0] & 0b1;
556 }
557 
558 bool isUnicast(const ether_addr& mac)
559 {
560     return !isEmpty(mac) && !isMulticast(mac);
561 }
562 
563 } // namespace mac_address
564 } // namespace network
565 } // namespace phosphor
566