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