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