xref: /openbmc/phosphor-networkd/src/util.cpp (revision 6d2175129bc2dad3f961ffdf81344b381387d898)
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 std::optional<std::string> interfaceToUbootEthAddr(std::string_view intf)
133 {
134     constexpr auto pfx = "eth"sv;
135     if (!intf.starts_with(pfx))
136     {
137         return std::nullopt;
138     }
139     intf.remove_prefix(pfx.size());
140     auto last = intf.data() + intf.size();
141     unsigned long idx;
142     auto res = std::from_chars(intf.data(), last, idx);
143     if (res.ec != std::errc() || res.ptr != last)
144     {
145         return std::nullopt;
146     }
147     if (idx == 0)
148     {
149         return "ethaddr";
150     }
151     return fmt::format(FMT_COMPILE("eth{}addr"), idx);
152 }
153 
154 static std::optional<DHCPVal> systemdParseDHCP(std::string_view str)
155 {
156     if (config::icaseeq(str, "ipv4"))
157     {
158         return DHCPVal{.v4 = true, .v6 = false};
159     }
160     if (config::icaseeq(str, "ipv6"))
161     {
162         return DHCPVal{.v4 = false, .v6 = true};
163     }
164     if (auto b = config::parseBool(str); b)
165     {
166         return DHCPVal{.v4 = *b, .v6 = *b};
167     }
168     return std::nullopt;
169 }
170 
171 inline auto systemdParseLast(const config::Parser& config,
172                              std::string_view section, std::string_view key,
173                              auto&& fun)
174 {
175     if (!config.getFileExists())
176     {
177     }
178     else if (auto str = config.map.getLastValueString(section, key);
179              str == nullptr)
180     {
181         auto err = fmt::format("Unable to get the value of {}[{}] from {}",
182                                section, key, config.getFilename().native());
183         log<level::NOTICE>(err.c_str(),
184                            entry("FILE=%s", config.getFilename().c_str()));
185     }
186     else if (auto val = fun(*str); !val)
187     {
188         auto err = fmt::format("Invalid value of {}[{}] from {}: {}", section,
189                                key, config.getFilename().native(), *str);
190         log<level::NOTICE>(err.c_str(), entry("VALUE=%s", str->c_str()),
191                            entry("FILE=%s", config.getFilename().c_str()));
192     }
193     else
194     {
195         return val;
196     }
197     return decltype(fun(std::string_view{}))(std::nullopt);
198 }
199 
200 bool getIPv6AcceptRA(const config::Parser& config)
201 {
202 #ifdef ENABLE_IPV6_ACCEPT_RA
203     constexpr bool def = true;
204 #else
205     constexpr bool def = false;
206 #endif
207     return systemdParseLast(config, "Network", "IPv6AcceptRA",
208                             config::parseBool)
209         .value_or(def);
210 }
211 
212 DHCPVal getDHCPValue(const config::Parser& config)
213 {
214     return systemdParseLast(config, "Network", "DHCP", systemdParseDHCP)
215         .value_or(DHCPVal{.v4 = true, .v6 = true});
216 }
217 
218 bool getDHCPProp(const config::Parser& config, std::string_view key)
219 {
220     return systemdParseLast(config, "DHCP", key, config::parseBool)
221         .value_or(true);
222 }
223 
224 namespace mac_address
225 {
226 
227 constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
228 constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
229 constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
230 constexpr auto propIntf = "org.freedesktop.DBus.Properties";
231 constexpr auto methodGet = "Get";
232 constexpr auto configFile = "/usr/share/network/config.json";
233 
234 using DbusObjectPath = std::string;
235 using DbusService = std::string;
236 using DbusInterface = std::string;
237 using ObjectTree =
238     std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
239 
240 constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
241 constexpr auto invNetworkIntf =
242     "xyz.openbmc_project.Inventory.Item.NetworkInterface";
243 constexpr auto invRoot = "/xyz/openbmc_project/inventory";
244 
245 ether_addr getfromInventory(sdbusplus::bus_t& bus, const std::string& intfName)
246 {
247 
248     std::string interfaceName = intfName;
249 
250 #ifdef SYNC_MAC_FROM_INVENTORY
251     // load the config JSON from the Read Only Path
252     std::ifstream in(configFile);
253     nlohmann::json configJson;
254     in >> configJson;
255     interfaceName = configJson[intfName];
256 #endif
257 
258     std::vector<DbusInterface> interfaces;
259     interfaces.emplace_back(invNetworkIntf);
260 
261     auto depth = 0;
262 
263     auto mapperCall =
264         bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
265 
266     mapperCall.append(invRoot, depth, interfaces);
267 
268     auto mapperReply = bus.call(mapperCall);
269     if (mapperReply.is_method_error())
270     {
271         log<level::ERR>("Error in mapper call");
272         elog<InternalFailure>();
273     }
274 
275     ObjectTree objectTree;
276     mapperReply.read(objectTree);
277 
278     if (objectTree.empty())
279     {
280         log<level::ERR>("No Object has implemented the interface",
281                         entry("INTERFACE=%s", invNetworkIntf));
282         elog<InternalFailure>();
283     }
284 
285     DbusObjectPath objPath;
286     DbusService service;
287 
288     if (1 == objectTree.size())
289     {
290         objPath = objectTree.begin()->first;
291         service = objectTree.begin()->second.begin()->first;
292     }
293     else
294     {
295         // If there are more than 2 objects, object path must contain the
296         // interface name
297         for (auto const& object : objectTree)
298         {
299             log<level::INFO>("interface",
300                              entry("INT=%s", interfaceName.c_str()));
301             log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
302 
303             if (std::string::npos != object.first.find(interfaceName.c_str()))
304             {
305                 objPath = object.first;
306                 service = object.second.begin()->first;
307                 break;
308             }
309         }
310 
311         if (objPath.empty())
312         {
313             log<level::ERR>("Can't find the object for the interface",
314                             entry("intfName=%s", interfaceName.c_str()));
315             elog<InternalFailure>();
316         }
317     }
318 
319     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
320                                       propIntf, methodGet);
321 
322     method.append(invNetworkIntf, "MACAddress");
323 
324     auto reply = bus.call(method);
325     if (reply.is_method_error())
326     {
327         log<level::ERR>("Failed to get MACAddress",
328                         entry("PATH=%s", objPath.c_str()),
329                         entry("INTERFACE=%s", invNetworkIntf));
330         elog<InternalFailure>();
331     }
332 
333     std::variant<std::string> value;
334     reply.read(value);
335     return ToAddr<ether_addr>{}(std::get<std::string>(value));
336 }
337 
338 bool isEmpty(const ether_addr& mac)
339 {
340     return mac == ether_addr{};
341 }
342 
343 bool isMulticast(const ether_addr& mac)
344 {
345     return mac.ether_addr_octet[0] & 0b1;
346 }
347 
348 bool isUnicast(const ether_addr& mac)
349 {
350     return !isEmpty(mac) && !isMulticast(mac);
351 }
352 
353 } // namespace mac_address
354 } // namespace network
355 } // namespace phosphor
356