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