| #include "network_manager.hpp" |
| |
| #include "config_parser.hpp" |
| #include "ipaddress.hpp" |
| #include "system_queries.hpp" |
| #include "types.hpp" |
| #include "util.hpp" |
| |
| #include <linux/if_addr.h> |
| #include <linux/neighbour.h> |
| #include <net/if.h> |
| #include <net/if_arp.h> |
| |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| #include <sdbusplus/message.hpp> |
| #include <stdplus/numeric/str.hpp> |
| #include <stdplus/pinned.hpp> |
| #include <stdplus/print.hpp> |
| #include <stdplus/str/cat.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| #include <filesystem> |
| #include <format> |
| |
| namespace phosphor |
| { |
| namespace network |
| { |
| |
| using namespace phosphor::logging; |
| using namespace sdbusplus::xyz::openbmc_project::Common::Error; |
| using Argument = xyz::openbmc_project::Common::InvalidArgument; |
| using std::literals::string_view_literals::operator""sv; |
| |
| 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<sdbusplus::bus_t> bus, |
| stdplus::PinnedRef<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<std::string, std::variant<std::string>> 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 = stdplus::StrToInt<10, uint16_t>{}(obj.substr(sep + 3)); |
| const auto& state = std::get<std::string>(it->second); |
| man.get().handleAdminState(state, ifidx); |
| } |
| catch (const std::exception& e) |
| { |
| lg2::error("AdministrativeState match parsing failed: {ERROR}", "ERROR", |
| e); |
| } |
| }) |
| { |
| reload.get().setCallback([self = stdplus::PinnedRef(*this)]() { |
| for (auto& hook : self.get().reloadPreHooks) |
| { |
| try |
| { |
| hook(); |
| } |
| catch (const std::exception& ex) |
| { |
| lg2::error("Failed executing reload hook, ignoring: {ERROR}", |
| "ERROR", ex); |
| } |
| } |
| self.get().reloadPreHooks.clear(); |
| try |
| { |
| self.get() |
| .bus.get() |
| .new_method_call("org.freedesktop.network1", |
| "/org/freedesktop/network1", |
| "org.freedesktop.network1.Manager", "Reload") |
| .call(); |
| lg2::info("Reloaded systemd-networkd"); |
| } |
| catch (const sdbusplus::exception_t& ex) |
| { |
| lg2::error("Failed to reload configuration: {ERROR}", "ERROR", ex); |
| self.get().reloadPostHooks.clear(); |
| } |
| for (auto& hook : self.get().reloadPostHooks) |
| { |
| try |
| { |
| hook(); |
| } |
| catch (const std::exception& ex) |
| { |
| lg2::error("Failed executing reload hook, ignoring: {ERROR}", |
| "ERROR", ex); |
| } |
| } |
| self.get().reloadPostHooks.clear(); |
| }); |
| std::vector< |
| std::tuple<int32_t, std::string, sdbusplus::message::object_path>> |
| 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); |
| stdplus::ToStrHandle<stdplus::IntToStr<10, unsigned>> tsh; |
| auto obj = stdplus::strCat("/org/freedesktop/network1/link/_3"sv, |
| tsh(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<std::string> val; |
| rsp.read(val); |
| handleAdminState(std::get<std::string>(val), ifidx); |
| } |
| |
| std::filesystem::create_directories(confDir); |
| systemConf = std::make_unique<phosphor::network::SystemConfiguration>( |
| bus, (this->objPath / "config").str); |
| } |
| |
| 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) |
| { |
| lg2::error("Can't create interface without name: {NET_IDX}", "NET_IDX", |
| info.intf.idx); |
| return; |
| } |
| config::Parser config(config::pathForIntfConf(confDir, *info.intf.name)); |
| auto intf = std::make_unique<EthernetInterface>( |
| 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.type != ARPHRD_ETHER) |
| { |
| ignoredIntf.emplace(info.idx); |
| return; |
| } |
| if (info.name) |
| { |
| const auto& ignored = internal::getIgnoredInterfaces(); |
| if (ignored.find(*info.name) != ignored.end()) |
| { |
| static std::unordered_set<std::string> ignored; |
| if (!ignored.contains(*info.name)) |
| { |
| ignored.emplace(*info.name); |
| lg2::info("Ignoring interface {NET_INTF}", "NET_INTF", |
| *info.name); |
| } |
| 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) |
| { |
| stdplus::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( |
| std::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( |
| std::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, stdplus::InAnyAddr addr) |
| { |
| if (auto it = intfInfo.find(ifidx); it != intfInfo.end()) |
| { |
| std::visit( |
| [&](auto addr) { |
| if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>) |
| { |
| it->second.defgw4.emplace(addr); |
| } |
| else |
| { |
| static_assert(std::is_same_v<stdplus::In6Addr, decltype(addr)>); |
| it->second.defgw6.emplace(addr); |
| } |
| }, |
| addr); |
| if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end()) |
| { |
| std::visit( |
| [&](auto addr) { |
| if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>) |
| { |
| it->second->EthernetInterfaceIntf::defaultGateway( |
| stdplus::toStr(addr)); |
| } |
| else |
| { |
| static_assert( |
| std::is_same_v<stdplus::In6Addr, decltype(addr)>); |
| it->second->EthernetInterfaceIntf::defaultGateway6( |
| stdplus::toStr(addr)); |
| } |
| }, |
| addr); |
| } |
| } |
| else if (!ignoredIntf.contains(ifidx)) |
| { |
| lg2::error("Interface {NET_IDX} not found for gw", "NET_IDX", ifidx); |
| } |
| } |
| |
| void Manager::removeDefGw(unsigned ifidx, stdplus::InAnyAddr addr) |
| { |
| if (auto it = intfInfo.find(ifidx); it != intfInfo.end()) |
| { |
| std::visit( |
| [&](auto addr) { |
| if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>) |
| { |
| if (it->second.defgw4 == addr) |
| { |
| it->second.defgw4.reset(); |
| } |
| } |
| else |
| { |
| static_assert(std::is_same_v<stdplus::In6Addr, decltype(addr)>); |
| if (it->second.defgw6 == addr) |
| { |
| it->second.defgw6.reset(); |
| } |
| } |
| }, |
| addr); |
| if (auto it = interfacesByIdx.find(ifidx); it != interfacesByIdx.end()) |
| { |
| std::visit( |
| [&](auto addr) { |
| if constexpr (std::is_same_v<stdplus::In4Addr, decltype(addr)>) |
| { |
| stdplus::ToStrHandle<stdplus::ToStr<stdplus::In4Addr>> tsh; |
| if (it->second->defaultGateway() == tsh(addr)) |
| { |
| it->second->EthernetInterfaceIntf::defaultGateway(""); |
| } |
| } |
| else |
| { |
| static_assert( |
| std::is_same_v<stdplus::In6Addr, decltype(addr)>); |
| stdplus::ToStrHandle<stdplus::ToStr<stdplus::In6Addr>> tsh; |
| if (it->second->defaultGateway6() == tsh(addr)) |
| { |
| it->second->EthernetInterfaceIntf::defaultGateway6(""); |
| } |
| } |
| }, |
| addr); |
| } |
| } |
| } |
| |
| ObjectPath Manager::vlan(std::string interfaceName, uint32_t id) |
| { |
| if (id == 0 || id >= 4095) |
| { |
| lg2::error("VLAN ID {NET_VLAN} is not valid", "NET_VLAN", id); |
| elog<InvalidArgument>( |
| 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<ResourceNotFound>(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); |
| } |
| lg2::info("Network data purged."); |
| } |
| |
| 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 |