xref: /openbmc/phosphor-networkd/src/util.cpp (revision e7c5aaad)
1 #include "config.h"
2 
3 #include "util.hpp"
4 
5 #include "config_parser.hpp"
6 #include "types.hpp"
7 
8 #include <arpa/inet.h>
9 #include <dirent.h>
10 #include <fmt/compile.h>
11 #include <fmt/format.h>
12 #include <net/if.h>
13 #include <sys/wait.h>
14 
15 #include <algorithm>
16 #include <cctype>
17 #include <charconv>
18 #include <cstdlib>
19 #include <cstring>
20 #include <fstream>
21 #include <list>
22 #ifdef SYNC_MAC_FROM_INVENTORY
23 #include <nlohmann/json.hpp>
24 #endif
25 #include <phosphor-logging/elog-errors.hpp>
26 #include <phosphor-logging/log.hpp>
27 #include <stdexcept>
28 #include <stdplus/raw.hpp>
29 #include <string>
30 #include <string_view>
31 #include <variant>
32 #include <xyz/openbmc_project/Common/error.hpp>
33 
34 namespace phosphor
35 {
36 namespace network
37 {
38 
39 using std::literals::string_view_literals::operator""sv;
40 using namespace phosphor::logging;
41 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
42 
43 namespace internal
44 {
45 
46 void executeCommandinChildProcess(stdplus::const_zstring path, char** args)
47 {
48     using namespace std::string_literals;
49     pid_t pid = fork();
50 
51     if (pid == 0)
52     {
53         execv(path.c_str(), args);
54         exit(255);
55     }
56     else if (pid < 0)
57     {
58         auto error = errno;
59         log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
60         elog<InternalFailure>();
61     }
62     else if (pid > 0)
63     {
64         int status;
65         while (waitpid(pid, &status, 0) == -1)
66         {
67             if (errno != EINTR)
68             {
69                 status = -1;
70                 break;
71             }
72         }
73 
74         if (status < 0)
75         {
76             fmt::memory_buffer buf;
77             fmt::format_to(fmt::appender(buf), "`{}`", path);
78             for (size_t i = 0; args[i] != nullptr; ++i)
79             {
80                 fmt::format_to(fmt::appender(buf), " `{}`", args[i]);
81             }
82             buf.push_back('\0');
83             log<level::ERR>("Unable to execute the command",
84                             entry("CMD=%s", buf.data()),
85                             entry("STATUS=%d", status));
86             elog<InternalFailure>();
87         }
88     }
89 }
90 
91 /** @brief Get ignored interfaces from environment */
92 std::string_view getIgnoredInterfacesEnv()
93 {
94     auto r = std::getenv("IGNORED_INTERFACES");
95     if (r == nullptr)
96     {
97         return "";
98     }
99     return r;
100 }
101 
102 /** @brief Parse the comma separated interface names */
103 std::unordered_set<std::string_view>
104     parseInterfaces(std::string_view interfaces)
105 {
106     std::unordered_set<std::string_view> result;
107     while (true)
108     {
109         auto sep = interfaces.find(',');
110         auto interface = interfaces.substr(0, sep);
111         while (!interface.empty() && std::isspace(interface.front()))
112         {
113             interface.remove_prefix(1);
114         }
115         while (!interface.empty() && std::isspace(interface.back()))
116         {
117             interface.remove_suffix(1);
118         }
119         if (!interface.empty())
120         {
121             result.insert(interface);
122         }
123         if (sep == interfaces.npos)
124         {
125             break;
126         }
127         interfaces = interfaces.substr(sep + 1);
128     }
129     return result;
130 }
131 
132 /** @brief Get the ignored interfaces */
133 const std::unordered_set<std::string_view>& getIgnoredInterfaces()
134 {
135     static auto ignoredInterfaces = parseInterfaces(getIgnoredInterfacesEnv());
136     return ignoredInterfaces;
137 }
138 
139 } // namespace internal
140 
141 constexpr auto familyVisit(auto&& visitor, int family)
142 {
143     if (family == AF_INET)
144     {
145         return visitor.template operator()<AF_INET>();
146     }
147     else if (family == AF_INET6)
148     {
149         return visitor.template operator()<AF_INET6>();
150     }
151     throw std::invalid_argument("Invalid addr family");
152 }
153 
154 template <int family>
155 typename FamilyTraits<family>::addr addrFromBuf(std::string_view buf)
156 {
157     return stdplus::raw::copyFromStrict<typename FamilyTraits<family>::addr>(
158         buf);
159 }
160 
161 InAddrAny addrFromBuf(int family, std::string_view buf)
162 {
163     return familyVisit(
164         [=]<int f>() -> InAddrAny { return addrFromBuf<f>(buf); }, family);
165 }
166 
167 template <typename Addr>
168 std::string toString(const Addr& addr)
169 {
170     static constexpr int family = AddrToFamily<Addr>::value;
171     std::string ret(FamilyTraits<family>::strlen, '\0');
172     if (inet_ntop(family, &addr, ret.data(), ret.size()) == nullptr)
173     {
174         throw std::runtime_error("Failed to convert IP to string");
175     }
176 
177     ret.resize(strlen(ret.c_str()));
178     return ret;
179 }
180 
181 std::string toString(const InAddrAny& addr)
182 {
183     return std::visit([](auto&& a) { return toString(a); }, addr);
184 }
185 
186 bool isValidIP(int family, stdplus::const_zstring address) noexcept
187 {
188     unsigned char buf[sizeof(struct in6_addr)];
189     return inet_pton(family, address.c_str(), buf) > 0;
190 }
191 
192 bool isValidIP(stdplus::const_zstring address) noexcept
193 {
194     return isValidIP(AF_INET, address) || isValidIP(AF_INET6, address);
195 }
196 
197 bool isValidPrefix(int family, uint8_t prefix)
198 {
199     return familyVisit(
200         [=]<int f>() noexcept { return isValidPrefix<f>(prefix); }, family);
201 }
202 
203 string_uset getSystemInterfaces()
204 {
205     string_uset interfaces;
206     struct ifaddrs* ifaddr = nullptr;
207 
208     // attempt to fill struct with ifaddrs
209     if (getifaddrs(&ifaddr) == -1)
210     {
211         auto error = errno;
212         log<level::ERR>("Error occurred during the getifaddrs call",
213                         entry("ERRNO=%d", error));
214         elog<InternalFailure>();
215     }
216 
217     AddrPtr ifaddrPtr(ifaddr);
218     ifaddr = nullptr;
219     const auto& ignoredInterfaces = internal::getIgnoredInterfaces();
220 
221     for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
222     {
223         // walk interfaces
224         // if loopback ignore
225         if (ifa->ifa_flags & IFF_LOOPBACK ||
226             ignoredInterfaces.find(ifa->ifa_name) != ignoredInterfaces.end())
227         {
228             continue;
229         }
230         interfaces.emplace(ifa->ifa_name);
231     }
232     return interfaces;
233 }
234 
235 void deleteInterface(stdplus::const_zstring intf)
236 {
237     pid_t pid = fork();
238     int status{};
239 
240     if (pid == 0)
241     {
242 
243         execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr);
244         auto error = errno;
245         log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error),
246                         entry("INTF=%s", intf.c_str()));
247         elog<InternalFailure>();
248     }
249     else if (pid < 0)
250     {
251         auto error = errno;
252         log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
253         elog<InternalFailure>();
254     }
255     else if (pid > 0)
256     {
257         while (waitpid(pid, &status, 0) == -1)
258         {
259             if (errno != EINTR)
260             { /* Error other than EINTR */
261                 status = -1;
262                 break;
263             }
264         }
265 
266         if (status < 0)
267         {
268             log<level::ERR>("Unable to delete the interface",
269                             entry("INTF=%s", intf.c_str()),
270                             entry("STATUS=%d", status));
271             elog<InternalFailure>();
272         }
273     }
274 }
275 
276 std::optional<std::string> interfaceToUbootEthAddr(std::string_view intf)
277 {
278     constexpr auto pfx = "eth"sv;
279     if (!intf.starts_with(pfx))
280     {
281         return std::nullopt;
282     }
283     intf.remove_prefix(pfx.size());
284     auto last = intf.data() + intf.size();
285     unsigned long idx;
286     auto res = std::from_chars(intf.data(), last, idx);
287     if (res.ec != std::errc() || res.ptr != last)
288     {
289         return std::nullopt;
290     }
291     if (idx == 0)
292     {
293         return "ethaddr";
294     }
295     return fmt::format(FMT_COMPILE("eth{}addr"), idx);
296 }
297 
298 static std::optional<DHCPVal> systemdParseDHCP(std::string_view str)
299 {
300     if (config::icaseeq(str, "ipv4"))
301     {
302         return DHCPVal{.v4 = true, .v6 = false};
303     }
304     if (config::icaseeq(str, "ipv6"))
305     {
306         return DHCPVal{.v4 = false, .v6 = true};
307     }
308     if (auto b = config::parseBool(str); b)
309     {
310         return DHCPVal{.v4 = *b, .v6 = *b};
311     }
312     return std::nullopt;
313 }
314 
315 inline auto systemdParseLast(const config::Parser& config,
316                              std::string_view section, std::string_view key,
317                              auto&& fun)
318 {
319     if (auto str = config.map.getLastValueString(section, key); str == nullptr)
320     {
321         auto err = fmt::format("Unable to get the value of {}[{}] from {}",
322                                section, key, config.getFilename().native());
323         log<level::NOTICE>(err.c_str(),
324                            entry("FILE=%s", config.getFilename().c_str()));
325     }
326     else if (auto val = fun(*str); !val)
327     {
328         auto err = fmt::format("Invalid value of {}[{}] from {}: {}", section,
329                                key, config.getFilename().native(), *str);
330         log<level::NOTICE>(err.c_str(), entry("VALUE=%s", str->c_str()),
331                            entry("FILE=%s", config.getFilename().c_str()));
332     }
333     else
334     {
335         return val;
336     }
337     return decltype(fun(std::string_view{}))(std::nullopt);
338 }
339 
340 bool getIPv6AcceptRA(const config::Parser& config)
341 {
342 #ifdef ENABLE_IPV6_ACCEPT_RA
343     constexpr bool def = true;
344 #else
345     constexpr bool def = false;
346 #endif
347     return systemdParseLast(config, "Network", "IPv6AcceptRA",
348                             config::parseBool)
349         .value_or(def);
350 }
351 
352 DHCPVal getDHCPValue(const config::Parser& config)
353 {
354     return systemdParseLast(config, "Network", "DHCP", systemdParseDHCP)
355         .value_or(DHCPVal{.v4 = true, .v6 = true});
356 }
357 
358 bool getDHCPProp(const config::Parser& config, std::string_view key)
359 {
360     return systemdParseLast(config, "DHCP", key, config::parseBool)
361         .value_or(true);
362 }
363 
364 namespace mac_address
365 {
366 
367 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
368 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
369 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
370 constexpr auto propIntf = "org.freedesktop.DBus.Properties";
371 constexpr auto methodGet = "Get";
372 constexpr auto configFile = "/usr/share/network/config.json";
373 
374 using DbusObjectPath = std::string;
375 using DbusService = std::string;
376 using DbusInterface = std::string;
377 using ObjectTree =
378     std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
379 
380 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
381 constexpr auto invNetworkIntf =
382     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
383 constexpr auto invRoot = "/xyz/openbmc_project/inventory";
384 
385 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName)
386 {
387 
388     std::string interfaceName = intfName;
389 
390 #ifdef SYNC_MAC_FROM_INVENTORY
391     // load the config JSON from the Read Only Path
392     std::ifstream in(configFile);
393     nlohmann::json configJson;
394     in >> configJson;
395     interfaceName = configJson[intfName];
396 #endif
397 
398     std::vector<DbusInterface> interfaces;
399     interfaces.emplace_back(invNetworkIntf);
400 
401     auto depth = 0;
402 
403     auto mapperCall =
404         bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
405 
406     mapperCall.append(invRoot, depth, interfaces);
407 
408     auto mapperReply = bus.call(mapperCall);
409     if (mapperReply.is_method_error())
410     {
411         log<level::ERR>("Error in mapper call");
412         elog<InternalFailure>();
413     }
414 
415     ObjectTree objectTree;
416     mapperReply.read(objectTree);
417 
418     if (objectTree.empty())
419     {
420         log<level::ERR>("No Object has implemented the interface",
421                         entry("INTERFACE=%s", invNetworkIntf));
422         elog<InternalFailure>();
423     }
424 
425     DbusObjectPath objPath;
426     DbusService service;
427 
428     if (1 == objectTree.size())
429     {
430         objPath = objectTree.begin()->first;
431         service = objectTree.begin()->second.begin()->first;
432     }
433     else
434     {
435         // If there are more than 2 objects, object path must contain the
436         // interface name
437         for (auto const& object : objectTree)
438         {
439             log<level::INFO>("interface",
440                              entry("INT=%s", interfaceName.c_str()));
441             log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
442 
443             if (std::string::npos != object.first.find(interfaceName.c_str()))
444             {
445                 objPath = object.first;
446                 service = object.second.begin()->first;
447                 break;
448             }
449         }
450 
451         if (objPath.empty())
452         {
453             log<level::ERR>("Can't find the object for the interface",
454                             entry("intfName=%s", interfaceName.c_str()));
455             elog<InternalFailure>();
456         }
457     }
458 
459     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
460                                       propIntf, methodGet);
461 
462     method.append(invNetworkIntf, "MACAddress");
463 
464     auto reply = bus.call(method);
465     if (reply.is_method_error())
466     {
467         log<level::ERR>("Failed to get MACAddress",
468                         entry("PATH=%s", objPath.c_str()),
469                         entry("INTERFACE=%s", invNetworkIntf));
470         elog<InternalFailure>();
471     }
472 
473     std::variant<std::string> value;
474     reply.read(value);
475     return fromString(std::get<std::string>(value));
476 }
477 
478 static uint8_t decodeHex(std::string_view str)
479 {
480     uint8_t ret;
481     auto res = std::from_chars(str.begin(), str.end(), ret, 16);
482     if (res.ptr != str.end() || res.ec != std::errc())
483     {
484         throw std::invalid_argument("Not hex");
485     }
486     return ret;
487 }
488 
489 ether_addr fromString(std::string_view str)
490 {
491     ether_addr ret;
492     if (str.size() == 12 && str.find(":") == str.npos)
493     {
494         for (size_t i = 0; i < 6; ++i)
495         {
496             ret.ether_addr_octet[i] = decodeHex(str.substr(i * 2, 2));
497         }
498     }
499     else
500     {
501         for (size_t i = 0; i < 5; ++i)
502         {
503             auto loc = str.find(":");
504             ret.ether_addr_octet[i] = decodeHex(str.substr(0, loc));
505             str.remove_prefix(loc == str.npos ? str.size() : loc + 1);
506             if (str.empty())
507             {
508                 throw std::invalid_argument("Missing mac data");
509             }
510         }
511         ret.ether_addr_octet[5] = decodeHex(str);
512     }
513     return ret;
514 }
515 
516 std::string toString(const ether_addr& mac)
517 {
518     return fmt::format(FMT_COMPILE("{:02x}"),
519                        fmt::join(mac.ether_addr_octet, ":"));
520 }
521 
522 bool isEmpty(const ether_addr& mac)
523 {
524     return stdplus::raw::equal(mac, ether_addr{});
525 }
526 
527 bool isMulticast(const ether_addr& mac)
528 {
529     return mac.ether_addr_octet[0] & 0b1;
530 }
531 
532 bool isUnicast(const ether_addr& mac)
533 {
534     return !isEmpty(mac) && !isMulticast(mac);
535 }
536 
537 } // namespace mac_address
538 } // namespace network
539 } // namespace phosphor
540