network_manager: Async query all interface states
This only queries the state during startup and relies on matches to keep
up with changes from the systemd-networkd daemon.
Change-Id: I30776d443c39a52a91826ad1b515da60c7ecf641
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index 4f71a46..0408219 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -18,7 +18,6 @@
#include <filesystem>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
-#include <sdbusplus/bus/match.hpp>
#include <stdplus/raw.hpp>
#include <stdplus/zstring.hpp>
#include <string>
@@ -76,8 +75,7 @@
const InterfaceInfo& info,
std::string_view objRoot,
const config::Parser& config,
- bool emitSignal,
- std::optional<bool> enabled) :
+ bool emitSignal, bool enabled) :
EthernetInterface(bus, manager, info, makeObjPath(objRoot, *info.name),
config, emitSignal, enabled)
{
@@ -87,8 +85,7 @@
const InterfaceInfo& info,
std::string&& objPath,
const config::Parser& config,
- bool emitSignal,
- std::optional<bool> enabled) :
+ bool emitSignal, bool enabled) :
Ifaces(bus, objPath.c_str(),
emitSignal ? Ifaces::action::defer_emit
: Ifaces::action::emit_no_signals),
@@ -99,7 +96,7 @@
EthernetInterfaceIntf::dhcp4(dhcpVal.v4);
EthernetInterfaceIntf::dhcp6(dhcpVal.v6);
EthernetInterfaceIntf::ipv6AcceptRA(getIPv6AcceptRA(config));
- EthernetInterfaceIntf::nicEnabled(enabled ? *enabled : queryNicEnabled());
+ EthernetInterfaceIntf::nicEnabled(enabled);
{
const auto& gws = manager.getRouteTable().getDefaultGateway();
auto it = gws.find(ifIdx);
@@ -435,91 +432,6 @@
}));
}
-bool EthernetInterface::queryNicEnabled() const
-{
- constexpr auto svc = "org.freedesktop.network1";
- constexpr auto intf = "org.freedesktop.network1.Link";
- constexpr auto prop = "AdministrativeState";
- char* rpath;
- sd_bus_path_encode("/org/freedesktop/network1/link",
- std::to_string(ifIdx).c_str(), &rpath);
- std::string path(rpath);
- free(rpath);
-
- // Store / Parser for the AdministrativeState return value
- std::optional<bool> ret;
- auto cb = [&](std::string_view state) {
- if (state != "initialized")
- {
- ret = state != "unmanaged";
- }
- };
-
- // Build a matcher before making the property call to ensure we
- // can eventually get the value.
- sdbusplus::bus::match_t match(
- bus,
- fmt::format("type='signal',sender='{}',path='{}',interface='{}',member="
- "'PropertiesChanged',arg0='{}',",
- svc, path, PROPERTY_INTERFACE, intf)
- .c_str(),
- [&](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(prop);
- // Ignore properties that aren't AdministrativeState
- if (it != values.end())
- {
- cb(std::get<std::string>(it->second));
- }
- }
- catch (const std::exception& e)
- {
- log<level::ERR>(
- fmt::format(
- "AdministrativeState match parsing failed on {}: {}",
- interfaceName(), e.what())
- .c_str(),
- entry("INTERFACE=%s", interfaceName().c_str()),
- entry("ERROR=%s", e.what()));
- }
- });
-
- // Actively call for the value in case the interface is already configured
- auto method =
- bus.new_method_call(svc, path.c_str(), PROPERTY_INTERFACE, METHOD_GET);
- method.append(intf, prop);
- try
- {
- auto reply = bus.call(method);
- std::variant<std::string> state;
- reply.read(state);
- cb(std::get<std::string>(state));
- }
- catch (const std::exception& e)
- {
- log<level::ERR>(
- fmt::format("Failed to get AdministrativeState on {}: {}",
- interfaceName(), e.what())
- .c_str(),
- entry("INTERFACE=%s", interfaceName().c_str()),
- entry("ERROR=%s", e.what()));
- }
-
- // The interface is not yet configured by systemd-networkd, wait until it
- // signals us a valid state.
- while (!ret)
- {
- bus.wait();
- bus.process_discard();
- }
-
- return *ret;
-}
-
bool EthernetInterface::nicEnabled(bool value)
{
if (value == EthernetInterfaceIntf::nicEnabled())
diff --git a/src/ethernet_interface.hpp b/src/ethernet_interface.hpp
index ee83fe2..a2fb409 100644
--- a/src/ethernet_interface.hpp
+++ b/src/ethernet_interface.hpp
@@ -79,12 +79,12 @@
* @param[in] vlan - The id of the vlan if configured
* @param[in] emitSignal - true if the object added signal needs to be
* send.
- * @param[in] enabled - Override the lookup of nicEnabled
+ * @param[in] enabled - Determine if systemd-networkd is managing this link
*/
EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
const InterfaceInfo& info, std::string_view objRoot,
- const config::Parser& config, bool emitSignal = true,
- std::optional<bool> enabled = std::nullopt);
+ const config::Parser& config, bool emitSignal,
+ bool enabled);
/** @brief Network Manager object. */
Manager& manager;
@@ -265,18 +265,13 @@
EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
const InterfaceInfo& info, std::string&& objPath,
const config::Parser& config, bool emitSignal,
- std::optional<bool> enabled);
+ bool enabled);
/** @brief Determines if the address is manually assigned
* @param[in] origin - The origin entry of the IP::Address
* @returns true/false value if the address is static
*/
bool originIsManuallyAssigned(IP::AddressOrigin origin);
-
- /** @brief Determines if the NIC is enabled in systemd
- * @returns true/false value if the NIC is enabled
- */
- bool queryNicEnabled() const;
};
} // namespace network
diff --git a/src/network_manager.cpp b/src/network_manager.cpp
index beb677a..f862e28 100644
--- a/src/network_manager.cpp
+++ b/src/network_manager.cpp
@@ -11,6 +11,7 @@
#include <fstream>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
+#include <sdbusplus/message.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
constexpr char SYSTEMD_BUSNAME[] = "org.freedesktop.systemd1";
@@ -33,13 +34,79 @@
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(sdbusplus::bus_t& bus, const char* objPath,
const fs::path& confDir) :
details::VLANCreateIface(bus, objPath,
details::VLANCreateIface::action::defer_emit),
- bus(bus), objectPath(objPath)
+ bus(bus), objectPath(objPath),
+ systemdNetworkdEnabledMatch(
+ bus, enabledMatch, [&](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 = DecodeInt<unsigned, 10>{}(obj.substr(sep + 3));
+ const auto& state = std::get<std::string>(it->second);
+ handleAdminState(state, ifidx);
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>(
+ fmt::format("AdministrativeState match parsing failed: {}",
+ e.what())
+ .c_str(),
+ entry("ERROR=%s", e.what()));
+ }
+ })
{
setConfDir(confDir);
+ std::vector<
+ std::tuple<int32_t, std::string, sdbusplus::message::object_path>>
+ links;
+ try
+ {
+ auto rsp =
+ bus.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.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);
+ }
}
void Manager::setConfDir(const fs::path& dir)
@@ -57,24 +124,36 @@
}
}
+void Manager::addInterface(InterfaceInfo& info, bool enabled)
+{
+ config::Parser config(config::pathForIntfConf(confDir, *info.name));
+ auto intf = std::make_unique<EthernetInterface>(
+ bus, *this, info, objectPath, config, true, enabled);
+ intf->createIPAddressObjects();
+ intf->createStaticNeighborObjects();
+ intf->loadNameServers(config);
+ intf->loadNTPServers(config);
+ auto ptr = intf.get();
+ interfaces.emplace(std::move(*info.name), std::move(intf));
+ interfacesByIdx.emplace(info.idx, ptr);
+}
+
void Manager::createInterfaces()
{
// clear all the interfaces first
interfaces.clear();
interfacesByIdx.clear();
- for (auto& interface : system::getInterfaces())
+ for (auto& info : system::getInterfaces())
{
- config::Parser config(
- config::pathForIntfConf(confDir, *interface.name));
- auto intf = std::make_unique<EthernetInterface>(bus, *this, interface,
- objectPath, config);
- intf->createIPAddressObjects();
- intf->createStaticNeighborObjects();
- intf->loadNameServers(config);
- intf->loadNTPServers(config);
- auto ptr = intf.get();
- interfaces.emplace(std::move(*interface.name), std::move(intf));
- interfacesByIdx.emplace(interface.idx, ptr);
+ auto it = systemdNetworkdEnabled.find(info.idx);
+ if (it != systemdNetworkdEnabled.end())
+ {
+ addInterface(info, it->second);
+ }
+ else
+ {
+ undiscoveredIntfInfo.insert_or_assign(info.idx, std::move(info));
+ }
}
}
@@ -223,5 +302,30 @@
}
}
+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 = undiscoveredIntfInfo.find(ifidx);
+ it != undiscoveredIntfInfo.end())
+ {
+ auto info = std::move(it->second);
+ undiscoveredIntfInfo.erase(it);
+ addInterface(info, managed);
+ }
+ else if (auto it = interfacesByIdx.find(ifidx);
+ it != interfacesByIdx.end())
+ {
+ it->second->EthernetInterfaceIntf::nicEnabled(managed);
+ }
+ }
+}
+
} // namespace network
} // namespace phosphor
diff --git a/src/network_manager.hpp b/src/network_manager.hpp
index f265439..f7eb6bd 100644
--- a/src/network_manager.hpp
+++ b/src/network_manager.hpp
@@ -10,6 +10,7 @@
#include <function2/function2.hpp>
#include <memory>
#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
#include <string>
#include <string_view>
#include <vector>
@@ -42,7 +43,6 @@
class Manager : public details::VLANCreateIface
{
public:
- Manager() = delete;
Manager(const Manager&) = delete;
Manager& operator=(const Manager&) = delete;
Manager(Manager&&) = delete;
@@ -63,6 +63,9 @@
*/
void writeToConfigurationFile();
+ /** @brief Adds a single interface to the interface map */
+ void addInterface(InterfaceInfo& info, bool enabled);
+
/** @brief Fetch the interface and the ipaddress details
* from the system and create the ethernet interraces
* dbus object.
@@ -170,8 +173,18 @@
/** @brief The routing table */
route::Table routeTable;
+ /** @brief Map of interface info for undiscovered interfaces */
+ std::unordered_map<unsigned, InterfaceInfo> undiscoveredIntfInfo;
+
+ /** @brief Map of enabled interfaces */
+ std::unordered_map<unsigned, bool> systemdNetworkdEnabled;
+ sdbusplus::bus::match_t systemdNetworkdEnabledMatch;
+
/** @brief List of hooks to execute during the next reload */
std::vector<fu2::unique_function<void()>> reloadPreHooks;
+
+ /** @brief Handles the recipt of an adminstrative state string */
+ void handleAdminState(std::string_view state, unsigned ifidx);
};
} // namespace network