#include "network_manager.hpp" #include "config_parser.hpp" #include "ipaddress.hpp" #include "system_queries.hpp" #include "types.hpp" #include "util.hpp" #include #include #include #include #include #include #include #include #include constexpr char SYSTEMD_BUSNAME[] = "org.freedesktop.systemd1"; constexpr char SYSTEMD_PATH[] = "/org/freedesktop/systemd1"; constexpr char SYSTEMD_INTERFACE[] = "org.freedesktop.systemd1.Manager"; constexpr char NETWORKD_BUSNAME[] = "org.freedesktop.network1"; constexpr char NETWORKD_PATH[] = "/org/freedesktop/network1"; constexpr char NETWORKD_INTERFACE[] = "org.freedesktop.network1.Manager"; namespace phosphor { namespace network { using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::Error; using Argument = xyz::openbmc_project::Common::InvalidArgument; static constexpr const char enabledMatch[] = "type='signal',sender='org.freedesktop.network1',path_namespace='/org/" "freedesktop/network1/" "link',interface='org.freedesktop.DBus.Properties',member='" "PropertiesChanged',arg0='org.freedesktop.network1.Link',"; Manager::Manager(stdplus::PinnedRef bus, DelayedExecutor& reload, stdplus::zstring_view objPath, const std::filesystem::path& confDir) : ManagerIface(bus, objPath.c_str(), ManagerIface::action::defer_emit), reload(reload), bus(bus), objPath(std::string(objPath)), confDir(confDir), systemdNetworkdEnabledMatch( bus, enabledMatch, [man = stdplus::PinnedRef(*this)](sdbusplus::message_t& m) { std::string intf; std::unordered_map> values; try { m.read(intf, values); auto it = values.find("AdministrativeState"); if (it == values.end()) { return; } const std::string_view obj = m.get_path(); auto sep = obj.rfind('/'); if (sep == obj.npos || sep + 3 > obj.size()) { throw std::invalid_argument("Invalid obj path"); } auto ifidx = DecodeInt{}(obj.substr(sep + 3)); const auto& state = std::get(it->second); man.get().handleAdminState(state, ifidx); } catch (const std::exception& e) { log( fmt::format("AdministrativeState match parsing failed: {}", e.what()) .c_str(), entry("ERROR=%s", e.what())); } }) { reload.setCallback([&]() { for (auto& hook : reloadPreHooks) { try { hook(); } catch (const std::exception& ex) { log("Failed executing reload hook, ignoring", entry("ERR=%s", ex.what())); } } reloadPreHooks.clear(); try { bus.get() .new_method_call(NETWORKD_BUSNAME, NETWORKD_PATH, NETWORKD_INTERFACE, "Reload") .call(); log("Reloaded systemd-networkd"); } catch (const sdbusplus::exception_t& ex) { log("Failed to reload configuration", entry("ERR=%s", ex.what())); reloadPostHooks.clear(); } for (auto& hook : reloadPostHooks) { try { hook(); } catch (const std::exception& ex) { log("Failed executing reload hook, ignoring", entry("ERR=%s", ex.what())); } } reloadPostHooks.clear(); }); std::vector< std::tuple> links; try { auto rsp = bus.get() .new_method_call("org.freedesktop.network1", "/org/freedesktop/network1", "org.freedesktop.network1.Manager", "ListLinks") .call(); rsp.read(links); } catch (const sdbusplus::exception::SdBusError& e) { // Any failures are systemd-network not being ready } for (const auto& link : links) { unsigned ifidx = std::get<0>(link); auto obj = fmt::format("/org/freedesktop/network1/link/_3{}", ifidx); auto req = bus.get().new_method_call("org.freedesktop.network1", obj.c_str(), "org.freedesktop.DBus.Properties", "Get"); req.append("org.freedesktop.network1.Link", "AdministrativeState"); auto rsp = req.call(); std::variant val; rsp.read(val); handleAdminState(std::get(val), ifidx); } std::filesystem::create_directories(confDir); systemConf = std::make_unique( bus, (this->objPath / "config").str); dhcpConf = std::make_unique( bus, (this->objPath / "dhcp").str, *this); } void Manager::createInterface(const AllIntfInfo& info, bool enabled) { if (ignoredIntf.find(info.intf.idx) != ignoredIntf.end()) { return; } if (auto it = interfacesByIdx.find(info.intf.idx); it != interfacesByIdx.end()) { if (info.intf.name && *info.intf.name != it->second->interfaceName()) { interfaces.erase(it->second->interfaceName()); interfacesByIdx.erase(it); } else { it->second->updateInfo(info.intf); return; } } else if (info.intf.name) { auto it = interfaces.find(*info.intf.name); if (it != interfaces.end()) { it->second->updateInfo(info.intf); return; } } if (!info.intf.name) { auto msg = fmt::format("Can't create interface without name: {}", info.intf.idx); log(msg.c_str(), entry("IFIDX=%u", info.intf.idx)); return; } config::Parser config(config::pathForIntfConf(confDir, *info.intf.name)); auto intf = std::make_unique( bus, *this, info, objPath.str, config, enabled); intf->loadNameServers(config); intf->loadNTPServers(config); auto ptr = intf.get(); interfaces.insert_or_assign(*info.intf.name, std::move(intf)); interfacesByIdx.insert_or_assign(info.intf.idx, ptr); } void Manager::addInterface(const InterfaceInfo& info) { if (info.flags & IFF_LOOPBACK) { ignoredIntf.emplace(info.idx); return; } if (info.name) { const auto& ignored = internal::getIgnoredInterfaces(); if (ignored.find(*info.name) != ignored.end()) { static std::unordered_set ignored; if (!ignored.contains(*info.name)) { ignored.emplace(*info.name); auto msg = fmt::format("Ignoring interface {}\n", *info.name); log(msg.c_str()); } ignoredIntf.emplace(info.idx); return; } } auto infoIt = intfInfo.find(info.idx); if (infoIt != intfInfo.end()) { infoIt->second.intf = info; } else { infoIt = std::get<0>(intfInfo.emplace(info.idx, AllIntfInfo{info})); } if (auto it = systemdNetworkdEnabled.find(info.idx); it != systemdNetworkdEnabled.end()) { createInterface(infoIt->second, it->second); } } void Manager::removeInterface(const InterfaceInfo& info) { auto iit = interfacesByIdx.find(info.idx); auto nit = interfaces.end(); if (info.name) { nit = interfaces.find(*info.name); if (nit != interfaces.end() && iit != interfacesByIdx.end() && nit->second.get() != iit->second) { fmt::print(stderr, "Removed interface desync detected\n"); fflush(stderr); std::abort(); } } else if (iit != interfacesByIdx.end()) { for (nit = interfaces.begin(); nit != interfaces.end(); ++nit) { if (nit->second.get() == iit->second) { break; } } } if (iit != interfacesByIdx.end()) { interfacesByIdx.erase(iit); } else { ignoredIntf.erase(info.idx); } if (nit != interfaces.end()) { interfaces.erase(nit); } intfInfo.erase(info.idx); } void Manager::addAddress(const AddressInfo& info) { if (info.flags & IFA_F_DEPRECATED) { return; } if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end()) { it->second.addrs.insert_or_assign(info.ifaddr, info); if (auto it = interfacesByIdx.find(info.ifidx); it != interfacesByIdx.end()) { it->second->addAddr(info); } } else if (!ignoredIntf.contains(info.ifidx)) { throw std::runtime_error( fmt::format("Interface `{}` not found for addr", info.ifidx)); } } void Manager::removeAddress(const AddressInfo& info) { if (auto it = interfacesByIdx.find(info.ifidx); it != interfacesByIdx.end()) { it->second->addrs.erase(info.ifaddr); if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end()) { it->second.addrs.erase(info.ifaddr); } } } void Manager::addNeighbor(const NeighborInfo& info) { if (!(info.state & NUD_PERMANENT) || !info.addr) { return; } if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end()) { it->second.staticNeighs.insert_or_assign(*info.addr, info); if (auto it = interfacesByIdx.find(info.ifidx); it != interfacesByIdx.end()) { it->second->addStaticNeigh(info); } } else if (!ignoredIntf.contains(info.ifidx)) { throw std::runtime_error( fmt::format("Interface `{}` not found for neigh", info.ifidx)); } } void Manager::removeNeighbor(const NeighborInfo& info) { if (!info.addr) { return; } if (auto it = intfInfo.find(info.ifidx); it != intfInfo.end()) { it->second.staticNeighs.erase(*info.addr); if (auto it = interfacesByIdx.find(info.ifidx); it != interfacesByIdx.end()) { it->second->staticNeighbors.erase(*info.addr); } } } void Manager::addDefGw(unsigned ifidx, InAddrAny addr) { if (auto it = intfInfo.find(ifidx); it != intfInfo.end()) { std::visit( [&](auto addr) { if constexpr (std::is_same_v) { it->second.defgw4.emplace(addr); } else if constexpr (std::is_same_v) { it->second.defgw6.emplace(addr); } else { static_assert(!std::is_same_v); } }, addr); if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end()) { std::visit( [&](auto addr) { if constexpr (std::is_same_v) { it->second->EthernetInterfaceIntf::defaultGateway( std::to_string(addr)); } else if constexpr (std::is_same_v) { it->second->EthernetInterfaceIntf::defaultGateway6( std::to_string(addr)); } else { static_assert(!std::is_same_v); } }, addr); } } else if (!ignoredIntf.contains(ifidx)) { auto msg = fmt::format("Interface `{}` not found for gw", ifidx); log(msg.c_str(), entry("IFIDX=%u", ifidx)); } } void Manager::removeDefGw(unsigned ifidx, InAddrAny addr) { if (auto it = intfInfo.find(ifidx); it != intfInfo.end()) { std::visit( [&](auto addr) { if constexpr (std::is_same_v) { if (it->second.defgw4 == addr) { it->second.defgw4.reset(); } } else if constexpr (std::is_same_v) { if (it->second.defgw6 == addr) { it->second.defgw6.reset(); } } else { static_assert(!std::is_same_v); } }, addr); if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end()) { std::visit( [&](auto addr) { if constexpr (std::is_same_v) { if (it->second->defaultGateway() == std::to_string(addr)) { it->second->EthernetInterfaceIntf::defaultGateway( ""); } } else if constexpr (std::is_same_v) { if (it->second->defaultGateway6() == std::to_string(addr)) { it->second->EthernetInterfaceIntf::defaultGateway6( ""); } } else { static_assert(!std::is_same_v); } }, addr); } } } ObjectPath Manager::vlan(std::string interfaceName, uint32_t id) { if (id == 0 || id >= 4095) { log("VLAN ID is not valid", entry("VLANID=%u", id)); elog( Argument::ARGUMENT_NAME("VLANId"), Argument::ARGUMENT_VALUE(std::to_string(id).c_str())); } auto it = interfaces.find(interfaceName); if (it == interfaces.end()) { using ResourceErr = phosphor::logging::xyz::openbmc_project::Common::ResourceNotFound; elog(ResourceErr::RESOURCE(interfaceName.c_str())); } return it->second->createVLAN(id); } void Manager::reset() { for (const auto& dirent : std::filesystem::directory_iterator(confDir)) { std::error_code ec; std::filesystem::remove(dirent.path(), ec); } log("Network data purged."); } // Need to merge the below function with the code which writes the // config file during factory reset. // TODO openbmc/openbmc#1751 void Manager::writeToConfigurationFile() { // write all the static ip address in the systemd-network conf file for (const auto& intf : interfaces) { intf.second->writeConfigurationFile(); } } void Manager::handleAdminState(std::string_view state, unsigned ifidx) { if (state == "initialized" || state == "linger") { systemdNetworkdEnabled.erase(ifidx); } else { bool managed = state != "unmanaged"; systemdNetworkdEnabled.insert_or_assign(ifidx, managed); if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end()) { it->second->EthernetInterfaceIntf::nicEnabled(managed); } else if (auto it = intfInfo.find(ifidx); it != intfInfo.end()) { createInterface(it->second, managed); } } } } // namespace network } // namespace phosphor