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