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