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