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