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