xref: /openbmc/phosphor-networkd/src/network_manager.cpp (revision 434dd67a00a26db31418ee7022ef0f85f4d06c56)
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         auto val = rsp.unpack<std::variant<std::string>>();
158 
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             if (info.intf.vlan_id)
193             {
194                 interfacesByIdx.insert_or_assign(info.intf.idx,
195                                                  it->second.get());
196             }
197             it->second->updateInfo(info.intf);
198             return;
199         }
200     }
201     if (!info.intf.name)
202     {
203         lg2::error("Can't create interface without name: {NET_IDX}", "NET_IDX",
204                    info.intf.idx);
205         return;
206     }
207     config::Parser config(config::pathForIntfConf(confDir, *info.intf.name));
208     auto intf = std::make_unique<EthernetInterface>(
209         bus, *this, info, objPath.str, config, enabled);
210     intf->loadNameServers(config);
211     intf->loadNTPServers(config);
212     auto ptr = intf.get();
213     interfaces.insert_or_assign(*info.intf.name, std::move(intf));
214     interfacesByIdx.insert_or_assign(info.intf.idx, ptr);
215 }
216 
addInterface(const InterfaceInfo & info)217 void Manager::addInterface(const InterfaceInfo& info)
218 {
219     if (info.type != ARPHRD_ETHER)
220     {
221         ignoredIntf.emplace(info.idx);
222         return;
223     }
224     if (info.name)
225     {
226         const auto& ignored = internal::getIgnoredInterfaces();
227         if (ignored.find(*info.name) != ignored.end())
228         {
229             static std::unordered_set<std::string> ignored;
230             if (!ignored.contains(*info.name))
231             {
232                 ignored.emplace(*info.name);
233                 lg2::info("Ignoring interface {NET_INTF}", "NET_INTF",
234                           *info.name);
235             }
236             ignoredIntf.emplace(info.idx);
237             return;
238         }
239     }
240 
241     auto infoIt = intfInfo.find(info.idx);
242     if (infoIt != intfInfo.end())
243     {
244         infoIt->second.intf = info;
245     }
246     else
247     {
248         infoIt = std::get<0>(intfInfo.emplace(info.idx, AllIntfInfo{info}));
249     }
250 
251     if (auto it = systemdNetworkdEnabled.find(info.idx);
252         it != systemdNetworkdEnabled.end())
253     {
254         createInterface(infoIt->second, it->second);
255     }
256 }
257 
removeInterface(const InterfaceInfo & info)258 void Manager::removeInterface(const InterfaceInfo& info)
259 {
260     auto iit = interfacesByIdx.find(info.idx);
261     auto nit = interfaces.end();
262     if (info.name)
263     {
264         nit = interfaces.find(*info.name);
265         if (nit != interfaces.end() && iit != interfacesByIdx.end() &&
266             nit->second.get() != iit->second)
267         {
268             stdplus::print(stderr, "Removed interface desync detected\n");
269             fflush(stderr);
270             std::abort();
271         }
272     }
273     else if (iit != interfacesByIdx.end())
274     {
275         for (nit = interfaces.begin(); nit != interfaces.end(); ++nit)
276         {
277             if (nit->second.get() == iit->second)
278             {
279                 break;
280             }
281         }
282     }
283 
284     if (iit != interfacesByIdx.end())
285     {
286         interfacesByIdx.erase(iit);
287     }
288     else
289     {
290         ignoredIntf.erase(info.idx);
291     }
292     if (nit != interfaces.end())
293     {
294         interfaces.erase(nit);
295     }
296     intfInfo.erase(info.idx);
297 }
298 
addAddress(const AddressInfo & info)299 void Manager::addAddress(const AddressInfo& info)
300 {
301     if (info.flags & IFA_F_DEPRECATED)
302     {
303         return;
304     }
305     if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
306     {
307         it->second.addrs.insert_or_assign(info.ifaddr, info);
308         if (auto it = interfacesByIdx.find(info.ifidx);
309             it != interfacesByIdx.end())
310         {
311             it->second->addAddr(info);
312         }
313     }
314     else if (!ignoredIntf.contains(info.ifidx))
315     {
316         throw std::runtime_error(
317             std::format("Interface `{}` not found for addr", info.ifidx));
318     }
319 }
320 
removeAddress(const AddressInfo & info)321 void Manager::removeAddress(const AddressInfo& info)
322 {
323     if (auto it = interfacesByIdx.find(info.ifidx); it != interfacesByIdx.end())
324     {
325         it->second->addrs.erase(info.ifaddr);
326         if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
327         {
328             it->second.addrs.erase(info.ifaddr);
329         }
330     }
331 }
332 
addNeighbor(const NeighborInfo & info)333 void Manager::addNeighbor(const NeighborInfo& info)
334 {
335     if (!(info.state & NUD_PERMANENT) || !info.addr)
336     {
337         return;
338     }
339     if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
340     {
341         it->second.staticNeighs.insert_or_assign(*info.addr, info);
342         if (auto it = interfacesByIdx.find(info.ifidx);
343             it != interfacesByIdx.end())
344         {
345             it->second->addStaticNeigh(info);
346         }
347     }
348     else if (!ignoredIntf.contains(info.ifidx))
349     {
350         throw std::runtime_error(
351             std::format("Interface `{}` not found for neigh", info.ifidx));
352     }
353 }
354 
removeNeighbor(const NeighborInfo & info)355 void Manager::removeNeighbor(const NeighborInfo& info)
356 {
357     if (!info.addr)
358     {
359         return;
360     }
361     if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end())
362     {
363         it->second.staticNeighs.erase(*info.addr);
364         if (auto it = interfacesByIdx.find(info.ifidx);
365             it != interfacesByIdx.end())
366         {
367             it->second->staticNeighbors.erase(*info.addr);
368         }
369     }
370 }
371 
addDefGw(unsigned ifidx,stdplus::InAnyAddr addr)372 void Manager::addDefGw(unsigned ifidx, stdplus::InAnyAddr addr)
373 {
374     if (auto it = intfInfo.find(ifidx); it != intfInfo.end())
375     {
376         std::visit(
377             [&](auto addr) {
378                 if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>)
379                 {
380                     it->second.defgw4.emplace(addr);
381                 }
382                 else
383                 {
384                     static_assert(
385                         std::is_same_v<stdplus::In6Addr, decltype(addr)>);
386                     it->second.defgw6.emplace(addr);
387                 }
388             },
389             addr);
390         if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end())
391         {
392             std::visit(
393                 [&](auto addr) {
394                     if constexpr (std::is_same_v<stdplus::In4Addr,
395                                                  decltype(addr)>)
396                     {
397                         it->second->EthernetInterfaceIntf::defaultGateway(
398                             stdplus::toStr(addr));
399                     }
400                     else
401                     {
402                         static_assert(
403                             std::is_same_v<stdplus::In6Addr, decltype(addr)>);
404                         it->second->EthernetInterfaceIntf::defaultGateway6(
405                             stdplus::toStr(addr));
406                     }
407                 },
408                 addr);
409         }
410     }
411     else if (!ignoredIntf.contains(ifidx))
412     {
413         lg2::error("Interface {NET_IDX} not found for gw", "NET_IDX", ifidx);
414     }
415 }
416 
removeDefGw(unsigned ifidx,stdplus::InAnyAddr addr)417 void Manager::removeDefGw(unsigned ifidx, stdplus::InAnyAddr addr)
418 {
419     if (auto it = intfInfo.find(ifidx); it != intfInfo.end())
420     {
421         std::visit(
422             [&](auto addr) {
423                 if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>)
424                 {
425                     if (it->second.defgw4 == addr)
426                     {
427                         it->second.defgw4.reset();
428                     }
429                 }
430                 else
431                 {
432                     static_assert(
433                         std::is_same_v<stdplus::In6Addr, decltype(addr)>);
434                     if (it->second.defgw6 == addr)
435                     {
436                         it->second.defgw6.reset();
437                     }
438                 }
439             },
440             addr);
441         if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end())
442         {
443             std::visit(
444                 [&](auto addr) {
445                     if constexpr (std::is_same_v<stdplus::In4Addr,
446                                                  decltype(addr)>)
447                     {
448                         stdplus::ToStrHandle<stdplus::ToStr<stdplus::In4Addr>>
449                             tsh;
450                         if (it->second->defaultGateway() == tsh(addr))
451                         {
452                             it->second->EthernetInterfaceIntf::defaultGateway(
453                                 "");
454                         }
455                     }
456                     else
457                     {
458                         static_assert(
459                             std::is_same_v<stdplus::In6Addr, decltype(addr)>);
460                         stdplus::ToStrHandle<stdplus::ToStr<stdplus::In6Addr>>
461                             tsh;
462                         if (it->second->defaultGateway6() == tsh(addr))
463                         {
464                             it->second->EthernetInterfaceIntf::defaultGateway6(
465                                 "");
466                         }
467                     }
468                 },
469                 addr);
470         }
471     }
472 }
473 
vlan(std::string interfaceName,uint32_t id)474 ObjectPath Manager::vlan(std::string interfaceName, uint32_t id)
475 {
476     if (id == 0 || id >= 4095)
477     {
478         lg2::error("VLAN ID {NET_VLAN} is not valid", "NET_VLAN", id);
479         elog<InvalidArgument>(
480             Argument::ARGUMENT_NAME("VLANId"),
481             Argument::ARGUMENT_VALUE(std::to_string(id).c_str()));
482     }
483 
484     auto it = interfaces.find(interfaceName);
485     if (it == interfaces.end())
486     {
487         using ResourceErr =
488             phosphor::logging::xyz::openbmc_project::Common::ResourceNotFound;
489         elog<ResourceNotFound>(ResourceErr::RESOURCE(interfaceName.c_str()));
490     }
491     return it->second->createVLAN(id);
492 }
493 
reset()494 void Manager::reset()
495 {
496     for (const auto& dirent : std::filesystem::directory_iterator(confDir))
497     {
498         std::error_code ec;
499         std::filesystem::remove(dirent.path(), ec);
500     }
501     lg2::info("Network data purged.");
502 }
503 
writeToConfigurationFile()504 void Manager::writeToConfigurationFile()
505 {
506     // write all the static ip address in the systemd-network conf file
507     for (const auto& intf : interfaces)
508     {
509         intf.second->writeConfigurationFile();
510     }
511 }
512 
handleAdminState(std::string_view state,unsigned ifidx)513 void Manager::handleAdminState(std::string_view state, unsigned ifidx)
514 {
515     if (state == "initialized" || state == "linger")
516     {
517         systemdNetworkdEnabled.erase(ifidx);
518     }
519     else
520     {
521         bool managed = state != "unmanaged";
522         systemdNetworkdEnabled.insert_or_assign(ifidx, managed);
523         if (auto it = intfInfo.find(ifidx); it != intfInfo.end())
524         {
525             createInterface(it->second, managed);
526         }
527     }
528 }
529 
writeLLDPDConfigurationFile()530 void Manager::writeLLDPDConfigurationFile()
531 {
532     std::ofstream lldpdConfig(lldpFilePath);
533 
534     lldpdConfig << "configure system description BMC" << std::endl;
535     lldpdConfig << "configure system ip management pattern eth*" << std::endl;
536     for (const auto& intf : interfaces)
537     {
538         bool emitlldp = intf.second->emitLLDP();
539         if (emitlldp)
540         {
541             lldpdConfig << "configure ports " << intf.second->interfaceName()
542                         << " lldp status tx-only" << std::endl;
543         }
544         else
545         {
546             lldpdConfig << "configure ports " << intf.second->interfaceName()
547                         << " lldp status disabled" << std::endl;
548         }
549     }
550 
551     lldpdConfig.close();
552 }
553 
reloadLLDPService()554 void Manager::reloadLLDPService()
555 {
556     try
557     {
558         auto method = bus.get().new_method_call(
559             systemdBusname, systemdObjPath, systemdInterface, "RestartUnit");
560         method.append(lldpService, "replace");
561         bus.get().call_noreply(method);
562     }
563     catch (const sdbusplus::exception_t& ex)
564     {
565         lg2::error("Failed to restart service {SERVICE}: {ERR}", "SERVICE",
566                    lldpService, "ERR", ex);
567     }
568 }
569 
570 } // namespace network
571 } // namespace phosphor
572