xref: /openbmc/phosphor-networkd/src/util.cpp (revision 5b17938c)
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 (!config.getFileExists())
217     {
218     }
219     else if (auto str = config.map.getLastValueString(section, key);
220              str == nullptr)
221     {
222         auto err = fmt::format("Unable to get the value of {}[{}] from {}",
223                                section, key, config.getFilename().native());
224         log<level::NOTICE>(err.c_str(),
225                            entry("FILE=%s", config.getFilename().c_str()));
226     }
227     else if (auto val = fun(*str); !val)
228     {
229         auto err = fmt::format("Invalid value of {}[{}] from {}: {}", section,
230                                key, config.getFilename().native(), *str);
231         log<level::NOTICE>(err.c_str(), entry("VALUE=%s", str->c_str()),
232                            entry("FILE=%s", config.getFilename().c_str()));
233     }
234     else
235     {
236         return val;
237     }
238     return decltype(fun(std::string_view{}))(std::nullopt);
239 }
240 
241 bool getIPv6AcceptRA(const config::Parser& config)
242 {
243 #ifdef ENABLE_IPV6_ACCEPT_RA
244     constexpr bool def = true;
245 #else
246     constexpr bool def = false;
247 #endif
248     return systemdParseLast(config, "Network", "IPv6AcceptRA",
249                             config::parseBool)
250         .value_or(def);
251 }
252 
253 DHCPVal getDHCPValue(const config::Parser& config)
254 {
255     return systemdParseLast(config, "Network", "DHCP", systemdParseDHCP)
256         .value_or(DHCPVal{.v4 = true, .v6 = true});
257 }
258 
259 bool getDHCPProp(const config::Parser& config, std::string_view key)
260 {
261     return systemdParseLast(config, "DHCP", key, config::parseBool)
262         .value_or(true);
263 }
264 
265 namespace mac_address
266 {
267 
268 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
269 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
270 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
271 constexpr auto propIntf = "org.freedesktop.DBus.Properties";
272 constexpr auto methodGet = "Get";
273 constexpr auto configFile = "/usr/share/network/config.json";
274 
275 using DbusObjectPath = std::string;
276 using DbusService = std::string;
277 using DbusInterface = std::string;
278 using ObjectTree =
279     std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
280 
281 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
282 constexpr auto invNetworkIntf =
283     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
284 constexpr auto invRoot = "/xyz/openbmc_project/inventory";
285 
286 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName)
287 {
288 
289     std::string interfaceName = intfName;
290 
291 #ifdef SYNC_MAC_FROM_INVENTORY
292     // load the config JSON from the Read Only Path
293     std::ifstream in(configFile);
294     nlohmann::json configJson;
295     in >> configJson;
296     interfaceName = configJson[intfName];
297 #endif
298 
299     std::vector<DbusInterface> interfaces;
300     interfaces.emplace_back(invNetworkIntf);
301 
302     auto depth = 0;
303 
304     auto mapperCall =
305         bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
306 
307     mapperCall.append(invRoot, depth, interfaces);
308 
309     auto mapperReply = bus.call(mapperCall);
310     if (mapperReply.is_method_error())
311     {
312         log<level::ERR>("Error in mapper call");
313         elog<InternalFailure>();
314     }
315 
316     ObjectTree objectTree;
317     mapperReply.read(objectTree);
318 
319     if (objectTree.empty())
320     {
321         log<level::ERR>("No Object has implemented the interface",
322                         entry("INTERFACE=%s", invNetworkIntf));
323         elog<InternalFailure>();
324     }
325 
326     DbusObjectPath objPath;
327     DbusService service;
328 
329     if (1 == objectTree.size())
330     {
331         objPath = objectTree.begin()->first;
332         service = objectTree.begin()->second.begin()->first;
333     }
334     else
335     {
336         // If there are more than 2 objects, object path must contain the
337         // interface name
338         for (auto const& object : objectTree)
339         {
340             log<level::INFO>("interface",
341                              entry("INT=%s", interfaceName.c_str()));
342             log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
343 
344             if (std::string::npos != object.first.find(interfaceName.c_str()))
345             {
346                 objPath = object.first;
347                 service = object.second.begin()->first;
348                 break;
349             }
350         }
351 
352         if (objPath.empty())
353         {
354             log<level::ERR>("Can't find the object for the interface",
355                             entry("intfName=%s", interfaceName.c_str()));
356             elog<InternalFailure>();
357         }
358     }
359 
360     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
361                                       propIntf, methodGet);
362 
363     method.append(invNetworkIntf, "MACAddress");
364 
365     auto reply = bus.call(method);
366     if (reply.is_method_error())
367     {
368         log<level::ERR>("Failed to get MACAddress",
369                         entry("PATH=%s", objPath.c_str()),
370                         entry("INTERFACE=%s", invNetworkIntf));
371         elog<InternalFailure>();
372     }
373 
374     std::variant<std::string> value;
375     reply.read(value);
376     return ToAddr<ether_addr>{}(std::get<std::string>(value));
377 }
378 
379 bool isEmpty(const ether_addr& mac)
380 {
381     return mac == ether_addr{};
382 }
383 
384 bool isMulticast(const ether_addr& mac)
385 {
386     return mac.ether_addr_octet[0] & 0b1;
387 }
388 
389 bool isUnicast(const ether_addr& mac)
390 {
391     return !isEmpty(mac) && !isMulticast(mac);
392 }
393 
394 } // namespace mac_address
395 } // namespace network
396 } // namespace phosphor
397