xref: /openbmc/phosphor-networkd/src/network_manager.cpp (revision f7116de80882b7edad729e7d8747639eff600b08)
1 #include "network_manager.hpp"
2 
3 #include "config_parser.hpp"
4 #include "ipaddress.hpp"
5 #include "system_queries.hpp"
6 #include "types.hpp"
7 #include "util.hpp"
8 
9 #include <linux/if_addr.h>
10 #include <linux/neighbour.h>
11 #include <net/if.h>
12 #include <net/if_arp.h>
13 
14 #include <phosphor-logging/elog-errors.hpp>
15 #include <phosphor-logging/lg2.hpp>
16 #include <sdbusplus/message.hpp>
17 #include <stdplus/numeric/str.hpp>
18 #include <stdplus/pinned.hpp>
19 #include <stdplus/print.hpp>
20 #include <stdplus/str/cat.hpp>
21 #include <xyz/openbmc_project/Common/error.hpp>
22 
23 #include <filesystem>
24 #include <format>
25 #include <fstream>
26 
27 namespace phosphor
28 {
29 namespace network
30 {
31 
32 using namespace phosphor::logging;
33 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
34 using Argument = xyz::openbmc_project::Common::InvalidArgument;
35 using std::literals::string_view_literals::operator""sv;
36 
37 constexpr auto systemdBusname = "org.freedesktop.systemd1";
38 constexpr auto systemdObjPath = "/org/freedesktop/systemd1";
39 constexpr auto systemdInterface = "org.freedesktop.systemd1.Manager";
40 constexpr auto lldpFilePath = "/etc/lldpd.conf";
41 constexpr auto lldpService = "lldpd.service";
42 
43 static constexpr const char enabledMatch[] =
44     "type='signal',sender='org.freedesktop.network1',path_namespace='/org/"
45     "freedesktop/network1/"
46     "link',interface='org.freedesktop.DBus.Properties',member='"
47     "PropertiesChanged',arg0='org.freedesktop.network1.Link',";
48 
Manager(stdplus::PinnedRef<sdbusplus::bus_t> bus,stdplus::PinnedRef<DelayedExecutor> reload,stdplus::zstring_view objPath,const std::filesystem::path & confDir)49 Manager::Manager(stdplus::PinnedRef<sdbusplus::bus_t> bus,
50                  stdplus::PinnedRef<DelayedExecutor> reload,
51                  stdplus::zstring_view objPath,
52                  const std::filesystem::path& confDir) :
53     ManagerIface(bus, objPath.c_str(), ManagerIface::action::defer_emit),
54     reload(reload), bus(bus), objPath(std::string(objPath)), confDir(confDir),
55     systemdNetworkdEnabledMatch(
56         bus, enabledMatch,
57         [man = stdplus::PinnedRef(*this)](sdbusplus::message_t& m) {
58             std::string intf;
59             std::unordered_map<std::string, std::variant<std::string>> values;
60             try
61             {
62                 m.read(intf, values);
63                 auto it = values.find("AdministrativeState");
64                 if (it == values.end())
65                 {
66                     return;
67                 }
68                 const std::string_view obj = m.get_path();
69                 auto sep = obj.rfind('/');
70                 if (sep == obj.npos || sep + 3 > obj.size())
71                 {
72                     throw std::invalid_argument("Invalid obj path");
73                 }
74                 auto ifidx =
75                     stdplus::StrToInt<10, uint16_t>{}(obj.substr(sep + 3));
76                 const auto& state = std::get<std::string>(it->second);
77                 man.get().handleAdminState(state, ifidx);
78             }
79             catch (const std::exception& e)
80             {
81                 lg2::error("AdministrativeState match parsing failed: {ERROR}",
82                            "ERROR", e);
83             }
84         })
85 {
__anond1a9d47b0202() 86     reload.get().setCallback([self = stdplus::PinnedRef(*this)]() {
87         for (auto& hook : self.get().reloadPreHooks)
88         {
89             try
90             {
91                 hook();
92             }
93             catch (const std::exception& ex)
94             {
95                 lg2::error("Failed executing reload hook, ignoring: {ERROR}",
96                            "ERROR", ex);
97             }
98         }
99         self.get().reloadPreHooks.clear();
100         try
101         {
102             self.get()
103                 .bus.get()
104                 .new_method_call("org.freedesktop.network1",
105                                  "/org/freedesktop/network1",
106                                  "org.freedesktop.network1.Manager", "Reload")
107                 .call();
108             lg2::info("Reloaded systemd-networkd");
109         }
110         catch (const sdbusplus::exception_t& ex)
111         {
112             lg2::error("Failed to reload configuration: {ERROR}", "ERROR", ex);
113             self.get().reloadPostHooks.clear();
114         }
115         for (auto& hook : self.get().reloadPostHooks)
116         {
117             try
118             {
119                 hook();
120             }
121             catch (const std::exception& ex)
122             {
123                 lg2::error("Failed executing reload hook, ignoring: {ERROR}",
124                            "ERROR", ex);
125             }
126         }
127         self.get().reloadPostHooks.clear();
128     });
129     std::vector<
130         std::tuple<int32_t, std::string, sdbusplus::message::object_path>>
131         links;
132     try
133     {
134         auto rsp = bus.get()
135                        .new_method_call("org.freedesktop.network1",
136                                         "/org/freedesktop/network1",
137                                         "org.freedesktop.network1.Manager",
138                                         "ListLinks")
139                        .call();
140         rsp.read(links);
141     }
142     catch (const sdbusplus::exception::SdBusError& e)
143     {
144         // Any failures are systemd-network not being ready
145     }
146     for (const auto& link : links)
147     {
148         unsigned ifidx = std::get<0>(link);
149         stdplus::ToStrHandle<stdplus::IntToStr<10, unsigned>> tsh;
150         auto obj =
151             stdplus::strCat("/org/freedesktop/network1/link/_3"sv, tsh(ifidx));
152         auto req =
153             bus.get().new_method_call("org.freedesktop.network1", obj.c_str(),
154                                       "org.freedesktop.DBus.Properties", "Get");
155         req.append("org.freedesktop.network1.Link", "AdministrativeState");
156         auto rsp = req.call();
157         std::variant<std::string> val;
158         rsp.read(val);
159         handleAdminState(std::get<std::string>(val), ifidx);
160     }
161 
162     std::filesystem::create_directories(confDir);
163     systemConf = std::make_unique<phosphor::network::SystemConfiguration>(
164         bus, (this->objPath / "config").str);
165 }
166 
createInterface(const AllIntfInfo & info,bool enabled)167 void Manager::createInterface(const AllIntfInfo& info, bool enabled)
168 {
169     if (ignoredIntf.find(info.intf.idx) != ignoredIntf.end())
170     {
171         return;
172     }
173     if (auto it = interfacesByIdx.find(info.intf.idx);
174         it != interfacesByIdx.end())
175     {
176         if (info.intf.name && *info.intf.name != it->second->interfaceName())
177         {
178             interfaces.erase(it->second->interfaceName());
179             interfacesByIdx.erase(it);
180         }
181         else
182         {
183             it->second->updateInfo(info.intf);
184             return;
185         }
186     }
187     else if (info.intf.name)
188     {
189         auto it = interfaces.find(*info.intf.name);
190         if (it != interfaces.end())
191         {
192             it->second->updateInfo(info.intf);
193             return;
194         }
195     }
196     if (!info.intf.name)
197     {
198         lg2::error("Can't create interface without name: {NET_IDX}", "NET_IDX",
199                    info.intf.idx);
200         return;
201     }
202     config::Parser config(config::pathForIntfConf(confDir, *info.intf.name));
203     auto intf = std::make_unique<EthernetInterface>(
204         bus, *this, info, objPath.str, config, enabled);
205     intf->loadNameServers(config);
206     intf->loadNTPServers(config);
207     auto ptr = intf.get();
208     interfaces.insert_or_assign(*info.intf.name, std::move(intf));
209     interfacesByIdx.insert_or_assign(info.intf.idx, ptr);
210 }
211 
addInterface(const InterfaceInfo & info)212 void Manager::addInterface(const InterfaceInfo& info)
213 {
214     if (info.type != ARPHRD_ETHER)
215     {
216         ignoredIntf.emplace(info.idx);
217         return;
218     }
219     if (info.name)
220     {
221         const auto& ignored = internal::getIgnoredInterfaces();
222         if (ignored.find(*info.name) != ignored.end())
223         {
224             static std::unordered_set<std::string> ignored;
225             if (!ignored.contains(*info.name))
226             {
227                 ignored.emplace(*info.name);
228                 lg2::info("Ignoring interface {NET_INTF}", "NET_INTF",
229                           *info.name);
230             }
231             ignoredIntf.emplace(info.idx);
232             return;
233         }
234     }
235 
236     auto infoIt = intfInfo.find(info.idx);
237     if (infoIt != intfInfo.end())
238     {
239         infoIt->second.intf = info;
240     }
241     else
242     {
243         infoIt = std::get<0>(intfInfo.emplace(info.idx, AllIntfInfo{info}));
244     }
245 
246     if (auto it = systemdNetworkdEnabled.find(info.idx);
247         it != systemdNetworkdEnabled.end())
248     {
249         createInterface(infoIt->second, it->second);
250     }
251 }
252 
removeInterface(const InterfaceInfo & info)253 void Manager::removeInterface(const InterfaceInfo& info)
254 {
255     auto iit = interfacesByIdx.find(info.idx);
256     auto nit = interfaces.end();
257     if (info.name)
258     {
259         nit = interfaces.find(*info.name);
260         if (nit != interfaces.end() && iit != interfacesByIdx.end() &&
261             nit->second.get() != iit->second)
262         {
263             stdplus::print(stderr, "Removed interface desync detected\n");
264             fflush(stderr);
265             std::abort();
266         }
267     }
268     else if (iit != interfacesByIdx.end())
269     {
270         for (nit = interfaces.begin(); nit != interfaces.end(); ++nit)
271         {
272             if (nit->second.get() == iit->second)
273             {
274                 break;
275             }
276         }
277     }
278 
279     if (iit != interfacesByIdx.end())
280     {
281         interfacesByIdx.erase(iit);
282     }
283     else
284     {
285         ignoredIntf.erase(info.idx);
286     }
287     if (nit != interfaces.end())
288     {
289         interfaces.erase(nit);
290     }
291     intfInfo.erase(info.idx);
292 }
293 
addAddress(const AddressInfo & info)294 void Manager::addAddress(const AddressInfo& info)
295 {
296     if (info.flags & IFA_F_DEPRECATED)
297     {
298         return;
299     }
300     if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
301     {
302         it->second.addrs.insert_or_assign(info.ifaddr, info);
303         if (auto it = interfacesByIdx.find(info.ifidx);
304             it != interfacesByIdx.end())
305         {
306             it->second->addAddr(info);
307         }
308     }
309     else if (!ignoredIntf.contains(info.ifidx))
310     {
311         throw std::runtime_error(
312             std::format("Interface `{}` not found for addr", info.ifidx));
313     }
314 }
315 
removeAddress(const AddressInfo & info)316 void Manager::removeAddress(const AddressInfo& info)
317 {
318     if (auto it = interfacesByIdx.find(info.ifidx); it != interfacesByIdx.end())
319     {
320         it->second->addrs.erase(info.ifaddr);
321         if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
322         {
323             it->second.addrs.erase(info.ifaddr);
324         }
325     }
326 }
327 
addNeighbor(const NeighborInfo & info)328 void Manager::addNeighbor(const NeighborInfo& info)
329 {
330     if (!(info.state & NUD_PERMANENT) || !info.addr)
331     {
332         return;
333     }
334     if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
335     {
336         it->second.staticNeighs.insert_or_assign(*info.addr, info);
337         if (auto it = interfacesByIdx.find(info.ifidx);
338             it != interfacesByIdx.end())
339         {
340             it->second->addStaticNeigh(info);
341         }
342     }
343     else if (!ignoredIntf.contains(info.ifidx))
344     {
345         throw std::runtime_error(
346             std::format("Interface `{}` not found for neigh", info.ifidx));
347     }
348 }
349 
removeNeighbor(const NeighborInfo & info)350 void Manager::removeNeighbor(const NeighborInfo& info)
351 {
352     if (!info.addr)
353     {
354         return;
355     }
356     if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
357     {
358         it->second.staticNeighs.erase(*info.addr);
359         if (auto it = interfacesByIdx.find(info.ifidx);
360             it != interfacesByIdx.end())
361         {
362             it->second->staticNeighbors.erase(*info.addr);
363         }
364     }
365 }
366 
addDefGw(unsigned ifidx,stdplus::InAnyAddr addr)367 void Manager::addDefGw(unsigned ifidx, stdplus::InAnyAddr addr)
368 {
369     if (auto it = intfInfo.find(ifidx); it != intfInfo.end())
370     {
371         std::visit(
372             [&](auto addr) {
373                 if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>)
374                 {
375                     it->second.defgw4.emplace(addr);
376                 }
377                 else
378                 {
379                     static_assert(
380                         std::is_same_v<stdplus::In6Addr, decltype(addr)>);
381                     it->second.defgw6.emplace(addr);
382                 }
383             },
384             addr);
385         if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end())
386         {
387             std::visit(
388                 [&](auto addr) {
389                     if constexpr (std::is_same_v<stdplus::In4Addr,
390                                                  decltype(addr)>)
391                     {
392                         it->second->EthernetInterfaceIntf::defaultGateway(
393                             stdplus::toStr(addr));
394                     }
395                     else
396                     {
397                         static_assert(
398                             std::is_same_v<stdplus::In6Addr, decltype(addr)>);
399                         it->second->EthernetInterfaceIntf::defaultGateway6(
400                             stdplus::toStr(addr));
401                     }
402                 },
403                 addr);
404         }
405     }
406     else if (!ignoredIntf.contains(ifidx))
407     {
408         lg2::error("Interface {NET_IDX} not found for gw", "NET_IDX", ifidx);
409     }
410 }
411 
removeDefGw(unsigned ifidx,stdplus::InAnyAddr addr)412 void Manager::removeDefGw(unsigned ifidx, stdplus::InAnyAddr addr)
413 {
414     if (auto it = intfInfo.find(ifidx); it != intfInfo.end())
415     {
416         std::visit(
417             [&](auto addr) {
418                 if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>)
419                 {
420                     if (it->second.defgw4 == addr)
421                     {
422                         it->second.defgw4.reset();
423                     }
424                 }
425                 else
426                 {
427                     static_assert(
428                         std::is_same_v<stdplus::In6Addr, decltype(addr)>);
429                     if (it->second.defgw6 == addr)
430                     {
431                         it->second.defgw6.reset();
432                     }
433                 }
434             },
435             addr);
436         if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end())
437         {
438             std::visit(
439                 [&](auto addr) {
440                     if constexpr (std::is_same_v<stdplus::In4Addr,
441                                                  decltype(addr)>)
442                     {
443                         stdplus::ToStrHandle<stdplus::ToStr<stdplus::In4Addr>>
444                             tsh;
445                         if (it->second->defaultGateway() == tsh(addr))
446                         {
447                             it->second->EthernetInterfaceIntf::defaultGateway(
448                                 "");
449                         }
450                     }
451                     else
452                     {
453                         static_assert(
454                             std::is_same_v<stdplus::In6Addr, decltype(addr)>);
455                         stdplus::ToStrHandle<stdplus::ToStr<stdplus::In6Addr>>
456                             tsh;
457                         if (it->second->defaultGateway6() == tsh(addr))
458                         {
459                             it->second->EthernetInterfaceIntf::defaultGateway6(
460                                 "");
461                         }
462                     }
463                 },
464                 addr);
465         }
466     }
467 }
468 
vlan(std::string interfaceName,uint32_t id)469 ObjectPath Manager::vlan(std::string interfaceName, uint32_t id)
470 {
471     if (id == 0 || id >= 4095)
472     {
473         lg2::error("VLAN ID {NET_VLAN} is not valid", "NET_VLAN", id);
474         elog<InvalidArgument>(
475             Argument::ARGUMENT_NAME("VLANId"),
476             Argument::ARGUMENT_VALUE(std::to_string(id).c_str()));
477     }
478 
479     auto it = interfaces.find(interfaceName);
480     if (it == interfaces.end())
481     {
482         using ResourceErr =
483             phosphor::logging::xyz::openbmc_project::Common::ResourceNotFound;
484         elog<ResourceNotFound>(ResourceErr::RESOURCE(interfaceName.c_str()));
485     }
486     return it->second->createVLAN(id);
487 }
488 
reset()489 void Manager::reset()
490 {
491     for (const auto& dirent : std::filesystem::directory_iterator(confDir))
492     {
493         std::error_code ec;
494         std::filesystem::remove(dirent.path(), ec);
495     }
496     lg2::info("Network data purged.");
497 }
498 
writeToConfigurationFile()499 void Manager::writeToConfigurationFile()
500 {
501     // write all the static ip address in the systemd-network conf file
502     for (const auto& intf : interfaces)
503     {
504         intf.second->writeConfigurationFile();
505     }
506 }
507 
handleAdminState(std::string_view state,unsigned ifidx)508 void Manager::handleAdminState(std::string_view state, unsigned ifidx)
509 {
510     if (state == "initialized" || state == "linger")
511     {
512         systemdNetworkdEnabled.erase(ifidx);
513     }
514     else
515     {
516         bool managed = state != "unmanaged";
517         systemdNetworkdEnabled.insert_or_assign(ifidx, managed);
518         if (auto it = intfInfo.find(ifidx); it != intfInfo.end())
519         {
520             createInterface(it->second, managed);
521         }
522     }
523 }
524 
writeLLDPDConfigurationFile()525 void Manager::writeLLDPDConfigurationFile()
526 {
527     std::ofstream lldpdConfig(lldpFilePath);
528 
529     lldpdConfig << "configure system description BMC" << std::endl;
530     lldpdConfig << "configure system ip management pattern eth*" << std::endl;
531     for (const auto& intf : interfaces)
532     {
533         bool emitlldp = intf.second->emitLLDP();
534         if (emitlldp)
535         {
536             lldpdConfig << "configure ports " << intf.second->interfaceName()
537                         << " lldp status tx-only" << std::endl;
538         }
539         else
540         {
541             lldpdConfig << "configure ports " << intf.second->interfaceName()
542                         << " lldp status disabled" << std::endl;
543         }
544     }
545 
546     lldpdConfig.close();
547 }
548 
reloadLLDPService()549 void Manager::reloadLLDPService()
550 {
551     try
552     {
553         auto method = bus.get().new_method_call(
554             systemdBusname, systemdObjPath, systemdInterface, "RestartUnit");
555         method.append(lldpService, "replace");
556         bus.get().call_noreply(method);
557     }
558     catch (const sdbusplus::exception_t& ex)
559     {
560         lg2::error("Failed to restart service {SERVICE}: {ERR}", "SERVICE",
561                    lldpService, "ERR", ex);
562     }
563 }
564 
565 } // namespace network
566 } // namespace phosphor
567