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