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