xref: /openbmc/phosphor-networkd/src/network_manager.cpp (revision b800612229a53bae2ef9ae1cf51c0e86b6236bd6)
1 #include "config.h"
2 
3 #include "network_manager.hpp"
4 
5 #include "config_parser.hpp"
6 #include "ipaddress.hpp"
7 #include "system_queries.hpp"
8 #include "types.hpp"
9 #include "util.hpp"
10 
11 #include <net/if.h>
12 
13 #include <filesystem>
14 #include <fstream>
15 #include <phosphor-logging/elog-errors.hpp>
16 #include <phosphor-logging/log.hpp>
17 #include <sdbusplus/message.hpp>
18 #include <xyz/openbmc_project/Common/error.hpp>
19 
20 constexpr char SYSTEMD_BUSNAME[] = "org.freedesktop.systemd1";
21 constexpr char SYSTEMD_PATH[] = "/org/freedesktop/systemd1";
22 constexpr char SYSTEMD_INTERFACE[] = "org.freedesktop.systemd1.Manager";
23 constexpr auto FirstBootFile = "/var/lib/network/firstBoot_";
24 
25 constexpr char NETWORKD_BUSNAME[] = "org.freedesktop.network1";
26 constexpr char NETWORKD_PATH[] = "/org/freedesktop/network1";
27 constexpr char NETWORKD_INTERFACE[] = "org.freedesktop.network1.Manager";
28 
29 namespace phosphor
30 {
31 namespace network
32 {
33 
34 extern std::unique_ptr<Timer> refreshObjectTimer;
35 extern std::unique_ptr<Timer> reloadTimer;
36 using namespace phosphor::logging;
37 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
38 using Argument = xyz::openbmc_project::Common::InvalidArgument;
39 
40 static constexpr const char enabledMatch[] =
41     "type='signal',sender='org.freedesktop.network1',path_namespace='/org/"
42     "freedesktop/network1/"
43     "link',interface='org.freedesktop.DBus.Properties',member='"
44     "PropertiesChanged',arg0='org.freedesktop.network1.Link',";
45 
46 Manager::Manager(sdbusplus::bus_t& bus, const char* objPath,
47                  const fs::path& confDir) :
48     details::VLANCreateIface(bus, objPath,
49                              details::VLANCreateIface::action::defer_emit),
50     bus(bus), objectPath(objPath),
51     systemdNetworkdEnabledMatch(
52         bus, enabledMatch, [&](sdbusplus::message_t& m) {
53             std::string intf;
54             std::unordered_map<std::string, std::variant<std::string>> values;
55             try
56             {
57                 m.read(intf, values);
58                 auto it = values.find("AdministrativeState");
59                 if (it == values.end())
60                 {
61                     return;
62                 }
63                 const std::string_view obj = m.get_path();
64                 auto sep = obj.rfind('/');
65                 if (sep == obj.npos || sep + 3 > obj.size())
66                 {
67                     throw std::invalid_argument("Invalid obj path");
68                 }
69                 auto ifidx = DecodeInt<unsigned, 10>{}(obj.substr(sep + 3));
70                 const auto& state = std::get<std::string>(it->second);
71                 handleAdminState(state, ifidx);
72             }
73             catch (const std::exception& e)
74             {
75                 log<level::ERR>(
76                     fmt::format("AdministrativeState match parsing failed: {}",
77                                 e.what())
78                         .c_str(),
79                     entry("ERROR=%s", e.what()));
80             }
81         })
82 {
83     setConfDir(confDir);
84     std::vector<
85         std::tuple<int32_t, std::string, sdbusplus::message::object_path>>
86         links;
87     try
88     {
89         auto rsp =
90             bus.new_method_call("org.freedesktop.network1",
91                                 "/org/freedesktop/network1",
92                                 "org.freedesktop.network1.Manager", "ListLinks")
93                 .call();
94         rsp.read(links);
95     }
96     catch (const sdbusplus::exception::SdBusError& e)
97     {
98         // Any failures are systemd-network not being ready
99     }
100     for (const auto& link : links)
101     {
102         unsigned ifidx = std::get<0>(link);
103         auto obj = fmt::format("/org/freedesktop/network1/link/_3{}", ifidx);
104         auto req =
105             bus.new_method_call("org.freedesktop.network1", obj.c_str(),
106                                 "org.freedesktop.DBus.Properties", "Get");
107         req.append("org.freedesktop.network1.Link", "AdministrativeState");
108         auto rsp = req.call();
109         std::variant<std::string> val;
110         rsp.read(val);
111         handleAdminState(std::get<std::string>(val), ifidx);
112     }
113 }
114 
115 void Manager::setConfDir(const fs::path& dir)
116 {
117     confDir = dir;
118 
119     if (!fs::exists(confDir))
120     {
121         if (!fs::create_directories(confDir))
122         {
123             log<level::ERR>("Unable to create the network conf dir",
124                             entry("DIR=%s", confDir.c_str()));
125             elog<InternalFailure>();
126         }
127     }
128 }
129 
130 void Manager::createInterface(const UndiscoveredInfo& info, bool enabled)
131 {
132     removeInterface(info.intf);
133     config::Parser config(config::pathForIntfConf(confDir, *info.intf.name));
134     auto intf = std::make_unique<EthernetInterface>(
135         bus, *this, info.intf, objectPath, config, true, enabled);
136     intf->createIPAddressObjects();
137     intf->createStaticNeighborObjects();
138     intf->loadNameServers(config);
139     intf->loadNTPServers(config);
140     auto ptr = intf.get();
141     interfaces.insert_or_assign(*info.intf.name, std::move(intf));
142     interfacesByIdx.insert_or_assign(info.intf.idx, ptr);
143 }
144 
145 void Manager::addInterface(const InterfaceInfo& info)
146 {
147     if (info.flags & IFF_LOOPBACK)
148     {
149         return;
150     }
151     if (!info.name)
152     {
153         throw std::invalid_argument("Interface missing name");
154     }
155     const auto& ignored = internal::getIgnoredInterfaces();
156     if (ignored.find(*info.name) != ignored.end())
157     {
158         auto msg = fmt::format("Ignoring interface {}\n", *info.name);
159         log<level::INFO>(msg.c_str());
160         return;
161     }
162 
163     auto it = systemdNetworkdEnabled.find(info.idx);
164     if (it != systemdNetworkdEnabled.end())
165     {
166         createInterface({info}, it->second);
167     }
168     else
169     {
170         undiscoveredIntfInfo.insert_or_assign(
171             info.idx, UndiscoveredInfo{std::move(info)});
172     }
173 }
174 
175 void Manager::removeInterface(const InterfaceInfo& info)
176 {
177     auto iit = interfacesByIdx.find(info.idx);
178     auto nit = interfaces.end();
179     if (info.name)
180     {
181         nit = interfaces.find(*info.name);
182         if (nit != interfaces.end() && iit != interfacesByIdx.end() &&
183             nit->second.get() != iit->second)
184         {
185             fmt::print(stderr, "Removed interface desync detected\n");
186             fflush(stderr);
187             std::abort();
188         }
189     }
190     else if (iit != interfacesByIdx.end())
191     {
192         for (nit = interfaces.begin(); nit != interfaces.end(); ++nit)
193         {
194             if (nit->second.get() == iit->second)
195             {
196                 break;
197             }
198         }
199     }
200 
201     if (iit != interfacesByIdx.end())
202     {
203         interfacesByIdx.erase(iit);
204     }
205     else
206     {
207         undiscoveredIntfInfo.erase(info.idx);
208     }
209     if (nit != interfaces.end())
210     {
211         interfaces.erase(nit);
212     }
213 }
214 
215 inline void getIntfOrLog(const decltype(Manager::interfacesByIdx)& intfs,
216                          unsigned idx, auto&& cb)
217 {
218     auto it = intfs.find(idx);
219     if (it == intfs.end())
220     {
221         auto msg = fmt::format("Interface `{}` not found", idx);
222         log<level::ERR>(msg.c_str(), entry("IFIDX=%u", idx));
223         return;
224     }
225     cb(*it->second);
226 }
227 
228 void Manager::addAddress(const AddressInfo& info)
229 {
230     getIntfOrLog(interfacesByIdx, info.ifidx,
231                  [&](auto& intf) { intf.addAddr(info); });
232 }
233 
234 void Manager::removeAddress(const AddressInfo& info)
235 {
236     getIntfOrLog(interfacesByIdx, info.ifidx,
237                  [&](auto& intf) { intf.addrs.erase(info.ifaddr); });
238 }
239 
240 void Manager::addNeighbor(const NeighborInfo& info)
241 {
242     getIntfOrLog(interfacesByIdx, info.ifidx,
243                  [&](auto& intf) { intf.addStaticNeigh(info); });
244 }
245 
246 void Manager::removeNeighbor(const NeighborInfo& info)
247 {
248     if (info.addr)
249     {
250         getIntfOrLog(interfacesByIdx, info.ifidx, [&](auto& intf) {
251             intf.staticNeighbors.erase(*info.addr);
252         });
253     }
254 }
255 
256 void Manager::addDefGw(unsigned ifidx, InAddrAny addr)
257 {
258     getIntfOrLog(interfacesByIdx, ifidx, [&](auto& intf) {
259         std::visit(
260             [&](auto addr) {
261                 if constexpr (std::is_same_v<in_addr, decltype(addr)>)
262                 {
263                     intf.EthernetInterfaceIntf::defaultGateway(
264                         std::to_string(addr));
265                 }
266                 else if constexpr (std::is_same_v<in6_addr, decltype(addr)>)
267                 {
268                     intf.EthernetInterfaceIntf::defaultGateway6(
269                         std::to_string(addr));
270                 }
271                 else
272                 {
273                     static_assert(!std::is_same_v<void, decltype(addr)>);
274                 }
275             },
276             addr);
277     });
278 }
279 
280 void Manager::removeDefGw(unsigned ifidx, InAddrAny addr)
281 {
282     getIntfOrLog(interfacesByIdx, ifidx, [&](auto& intf) {
283         std::visit(
284             [&](auto addr) {
285                 if constexpr (std::is_same_v<in_addr, decltype(addr)>)
286                 {
287                     if (intf.defaultGateway() == std::to_string(addr))
288                     {
289                         intf.EthernetInterfaceIntf::defaultGateway("");
290                     }
291                 }
292                 else if constexpr (std::is_same_v<in6_addr, decltype(addr)>)
293                 {
294                     if (intf.defaultGateway6() == std::to_string(addr))
295                     {
296                         intf.EthernetInterfaceIntf::defaultGateway6("");
297                     }
298                 }
299                 else
300                 {
301                     static_assert(!std::is_same_v<void, decltype(addr)>);
302                 }
303             },
304             addr);
305     });
306 }
307 
308 void Manager::createInterfaces()
309 {
310     // clear all the interfaces first
311     interfaces.clear();
312     interfacesByIdx.clear();
313     for (auto& info : system::getInterfaces())
314     {
315         addInterface(info);
316     }
317 }
318 
319 void Manager::createChildObjects()
320 {
321     routeTable.refresh();
322 
323     // creates the ethernet interface dbus object.
324     createInterfaces();
325 
326     systemConf.reset(nullptr);
327     dhcpConf.reset(nullptr);
328 
329     fs::path objPath = objectPath;
330     objPath /= "config";
331 
332     // create the system conf object.
333     systemConf = std::make_unique<phosphor::network::SystemConfiguration>(
334         bus, objPath.string());
335     // create the dhcp conf object.
336     objPath /= "dhcp";
337     dhcpConf = std::make_unique<phosphor::network::dhcp::Configuration>(
338         bus, objPath.string(), *this);
339 }
340 
341 ObjectPath Manager::vlan(std::string interfaceName, uint32_t id)
342 {
343     if (id == 0 || id >= 4095)
344     {
345         log<level::ERR>("VLAN ID is not valid", entry("VLANID=%u", id));
346         elog<InvalidArgument>(
347             Argument::ARGUMENT_NAME("VLANId"),
348             Argument::ARGUMENT_VALUE(std::to_string(id).c_str()));
349     }
350 
351     auto it = interfaces.find(interfaceName);
352     if (it == interfaces.end())
353     {
354         using ResourceErr =
355             phosphor::logging::xyz::openbmc_project::Common::ResourceNotFound;
356         elog<ResourceNotFound>(ResourceErr::RESOURCE(interfaceName.c_str()));
357     }
358     return it->second->createVLAN(id);
359 }
360 
361 void Manager::reset()
362 {
363     if (fs::is_directory(confDir))
364     {
365         for (const auto& file : fs::directory_iterator(confDir))
366         {
367             fs::remove(file.path());
368         }
369     }
370     log<level::INFO>("Network Factory Reset queued.");
371 }
372 
373 // Need to merge the below function with the code which writes the
374 // config file during factory reset.
375 // TODO openbmc/openbmc#1751
376 void Manager::writeToConfigurationFile()
377 {
378     // write all the static ip address in the systemd-network conf file
379     for (const auto& intf : interfaces)
380     {
381         intf.second->writeConfigurationFile();
382     }
383 }
384 
385 #ifdef SYNC_MAC_FROM_INVENTORY
386 void Manager::setFistBootMACOnInterface(
387     const std::pair<std::string, std::string>& inventoryEthPair)
388 {
389     for (const auto& interface : interfaces)
390     {
391         if (interface.first == inventoryEthPair.first)
392         {
393             auto returnMAC =
394                 interface.second->macAddress(inventoryEthPair.second);
395             if (returnMAC == inventoryEthPair.second)
396             {
397                 log<level::INFO>("Set the MAC on "),
398                     entry("interface : ", interface.first.c_str()),
399                     entry("MAC : ", inventoryEthPair.second.c_str());
400                 std::error_code ec;
401                 if (std::filesystem::is_directory("/var/lib/network", ec))
402                 {
403                     std::ofstream persistentFile(FirstBootFile +
404                                                  interface.first);
405                 }
406                 break;
407             }
408             else
409             {
410                 log<level::INFO>("MAC is Not Set on ethernet Interface");
411             }
412         }
413     }
414 }
415 
416 #endif
417 
418 void Manager::reloadConfigsNoRefresh()
419 {
420     reloadTimer->restartOnce(reloadTimeout);
421 }
422 
423 void Manager::reloadConfigs()
424 {
425     reloadConfigsNoRefresh();
426     // Ensure that the next refresh happens after reconfiguration
427     refreshObjectTimer->setRemaining(reloadTimeout + refreshTimeout);
428 }
429 
430 void Manager::doReloadConfigs()
431 {
432     for (auto& hook : reloadPreHooks)
433     {
434         try
435         {
436             hook();
437         }
438         catch (const std::exception& ex)
439         {
440             log<level::ERR>("Failed executing reload hook, ignoring",
441                             entry("ERR=%s", ex.what()));
442         }
443     }
444     reloadPreHooks.clear();
445     try
446     {
447         auto method = bus.new_method_call(NETWORKD_BUSNAME, NETWORKD_PATH,
448                                           NETWORKD_INTERFACE, "Reload");
449         bus.call_noreply(method);
450     }
451     catch (const sdbusplus::exception_t& ex)
452     {
453         log<level::ERR>("Failed to reload configuration",
454                         entry("ERR=%s", ex.what()));
455         elog<InternalFailure>();
456     }
457     // Ensure reconfiguration has enough time
458     if (refreshObjectTimer->isEnabled())
459     {
460         refreshObjectTimer->setRemaining(refreshTimeout);
461     }
462 }
463 
464 void Manager::handleAdminState(std::string_view state, unsigned ifidx)
465 {
466     if (state == "initialized" || state == "linger")
467     {
468         systemdNetworkdEnabled.erase(ifidx);
469     }
470     else
471     {
472         bool managed = state != "unmanaged";
473         systemdNetworkdEnabled.insert_or_assign(ifidx, managed);
474         if (auto it = undiscoveredIntfInfo.find(ifidx);
475             it != undiscoveredIntfInfo.end())
476         {
477             auto info = std::move(it->second);
478             undiscoveredIntfInfo.erase(it);
479             createInterface(info, managed);
480         }
481         else if (auto it = interfacesByIdx.find(ifidx);
482                  it != interfacesByIdx.end())
483         {
484             it->second->EthernetInterfaceIntf::nicEnabled(managed);
485         }
486     }
487 }
488 
489 } // namespace network
490 } // namespace phosphor
491