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