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