[PATCH] wired: Monitor link state via rtnetlink

Alexei Lazar alazar at nvidia.com
Tue Sep 29 00:02:39 PDT 2026


Subscribe to RTM_NEWLINK events via the existing netlink abstraction to
detect IFF_UP/IFF_LOWER_UP transitions on the wired interface. When the
combined flag state changes, emit EVENT_INTERFACE_ENABLED or
EVENT_INTERFACE_DISABLED so wpa_supplicant can react to cable
plug/unplug events without relying on polling.

After enabling link-change notifications, seed the driver's cached link
state from sysfs at startup. Blindly treating the link as up would cause
the first cable-plug event to be missed when the interface starts
without carrier.

Linux-specific parts are guarded with __linux__ since driver_wired
supports other platforms. CONFIG_DRIVER_WIRED now sets NEED_NETLINK so
wired-only builds link against netlink_init()/netlink_deinit().
netlink_init() does not take ownership of cfg; free it on failure.

Change-Id: I45aabfa815f195d5e121f34e24356a2361667164
Signed-off-by: Alexei Lazar <alazar at nvidia.com>
---
 src/drivers/driver_wired.c | 136 +++++++++++++++++++++++++++++++++++++
 src/drivers/drivers.mak    |   1 +
 src/drivers/drivers.mk     |   1 +
 3 files changed, 138 insertions(+)

diff --git a/src/drivers/driver_wired.c b/src/drivers/driver_wired.c
index 03366b8e4..205e41f55 100644
--- a/src/drivers/driver_wired.c
+++ b/src/drivers/driver_wired.c
@@ -13,6 +13,10 @@
 #include "eloop.h"
 #include "driver.h"
 #include "driver_wired_common.h"
+#ifdef __linux__
+#include "priv_netlink.h"
+#include "netlink.h"
+#endif /* __linux__ */
 
 #include <sys/ioctl.h>
 #undef IFNAMSIZ
@@ -44,11 +48,18 @@ struct ieee8023_hdr {
 #endif /* _MSC_VER */
 
 
+struct netlink_data;
+
 struct wpa_driver_wired_data {
 	struct driver_wired_common_data common;
 
 	int dhcp_sock; /* socket for dhcp packets */
 	int use_pae_group_addr;
+
+	/* Linux rtnetlink link-state monitoring */
+	struct netlink_data *netlink;
+	int ifindex;
+	bool link_up;
 };
 
 
@@ -367,6 +378,128 @@ static void wired_driver_hapd_deinit(void *priv)
 }
 
 
+#ifdef __linux__
+
+static void wpa_driver_wired_event_rtm_newlink(void *ctx, struct ifinfomsg *ifi,
+					       u8 *buf, size_t len)
+{
+	struct wpa_driver_wired_data *drv = ctx;
+	bool up;
+
+	if (ifi->ifi_index != drv->ifindex)
+		return;
+
+	/*
+	 * The link is usable for EAPOL only when the interface is
+	 * administratively up (IFF_UP) and the L1 carrier is present
+	 * (IFF_LOWER_UP). A cable unplug/replug toggles IFF_LOWER_UP while
+	 * IFF_UP stays set, so both have to be checked to detect a link
+	 * toggle.
+	 */
+	up = (ifi->ifi_flags & IFF_UP) && (ifi->ifi_flags & IFF_LOWER_UP);
+
+	wpa_printf(MSG_DEBUG,
+		   "wired: RTM_NEWLINK: ifi_index=%d ifi_flags=0x%x (%s%s%s) link_up %d->%d",
+		   ifi->ifi_index, ifi->ifi_flags,
+		   (ifi->ifi_flags & IFF_UP) ? "[UP]" : "",
+		   (ifi->ifi_flags & IFF_RUNNING) ? "[RUNNING]" : "",
+		   (ifi->ifi_flags & IFF_LOWER_UP) ? "[LOWER_UP]" : "",
+		   drv->link_up, up);
+
+	if (drv->link_up == up)
+		return;
+
+	drv->link_up = up;
+	wpa_supplicant_event(drv->common.ctx, up ? EVENT_INTERFACE_ENABLED :
+			     EVENT_INTERFACE_DISABLED, NULL);
+}
+
+
+static bool wpa_driver_wired_is_initial_link_up(const char *ifname)
+{
+	char path[128], buf[32], *endp;
+	unsigned long flags;
+	FILE *f;
+
+	os_snprintf(path, sizeof(path), "/sys/class/net/%s/flags", ifname);
+	f = fopen(path, "r");
+	if (!f)
+		goto out_err;
+
+	if (!fgets(buf, sizeof(buf), f)) {
+		fclose(f);
+		goto out_err;
+	}
+	fclose(f);
+
+	flags = strtoul(buf, &endp, 0);
+	if (endp == buf)
+		goto out_err;
+
+	return (flags & IFF_UP) && (flags & IFF_LOWER_UP);
+
+out_err:
+	/* Assume up so a real unplug generates EVENT_INTERFACE_DISABLED
+	 * rather than being swallowed by a 0->0 no-op comparison. */
+	wpa_printf(MSG_INFO,
+		   "wired: Failed to query initial link state; assuming up");
+	return true;
+}
+
+
+static void wpa_driver_wired_link_monitor_init(struct wpa_driver_wired_data *drv,
+					       const char *ifname)
+{
+	struct netlink_config *cfg;
+
+	drv->ifindex = if_nametoindex(ifname);
+	if (drv->ifindex == 0) {
+		wpa_printf(MSG_ERROR, "wired: if_nametoindex(%s) failed", ifname);
+		return;
+	}
+
+	cfg = os_zalloc(sizeof(*cfg));
+	if (!cfg)
+		return;
+
+	cfg->ctx = drv;
+	cfg->newlink_cb = wpa_driver_wired_event_rtm_newlink;
+	drv->netlink = netlink_init(cfg);
+	if (drv->netlink == NULL) {
+		os_free(cfg);
+		wpa_printf(MSG_INFO,
+			   "wired: Failed to initialize netlink; link-state monitoring disabled");
+		return;
+	}
+
+	/* Subscribe first, then snapshot state from sysfs. Any transition in
+	 * the gap between subscribe and read stays in the socket buffer and
+	 * is delivered normally by eloop — no events are lost. */
+	drv->link_up = wpa_driver_wired_is_initial_link_up(ifname);
+	wpa_printf(MSG_DEBUG, "wired: Initial link_up=%d", drv->link_up);
+}
+
+
+static void wpa_driver_wired_link_monitor_deinit(struct wpa_driver_wired_data *drv)
+{
+	netlink_deinit(drv->netlink);
+	drv->netlink = NULL;
+}
+
+#else /* __linux__ */
+
+static inline void wpa_driver_wired_link_monitor_init(struct wpa_driver_wired_data *drv,
+						      const char *ifname)
+{
+}
+
+static inline void wpa_driver_wired_link_monitor_deinit(struct wpa_driver_wired_data *drv)
+{
+}
+
+#endif /* __linux__ */
+
+
 static void * wpa_driver_wired_init(void *ctx, const char *ifname)
 {
 	struct wpa_driver_wired_data *drv;
@@ -380,6 +513,8 @@ static void * wpa_driver_wired_init(void *ctx, const char *ifname)
 		return NULL;
 	}
 
+	wpa_driver_wired_link_monitor_init(drv, ifname);
+
 	return drv;
 }
 
@@ -388,6 +523,7 @@ static void wpa_driver_wired_deinit(void *priv)
 {
 	struct wpa_driver_wired_data *drv = priv;
 
+	wpa_driver_wired_link_monitor_deinit(drv);
 	driver_wired_deinit_common(&drv->common);
 	os_free(drv);
 }
diff --git a/src/drivers/drivers.mak b/src/drivers/drivers.mak
index 160b53789..3c5012856 100644
--- a/src/drivers/drivers.mak
+++ b/src/drivers/drivers.mak
@@ -16,6 +16,7 @@ ifdef CONFIG_DRIVER_WIRED
 DRV_CFLAGS += -DCONFIG_DRIVER_WIRED
 DRV_OBJS += ../src/drivers/driver_wired.o
 NEED_DRV_WIRED_COMMON=1
+NEED_NETLINK=y
 endif
 
 ifdef CONFIG_DRIVER_MACSEC_LINUX
diff --git a/src/drivers/drivers.mk b/src/drivers/drivers.mk
index ad0ecd768..f16bac0a3 100644
--- a/src/drivers/drivers.mk
+++ b/src/drivers/drivers.mk
@@ -16,6 +16,7 @@ ifdef CONFIG_DRIVER_WIRED
 DRV_CFLAGS += -DCONFIG_DRIVER_WIRED
 DRV_OBJS += src/drivers/driver_wired.c
 NEED_DRV_WIRED_COMMON=1
+NEED_NETLINK=y
 endif
 
 ifdef CONFIG_DRIVER_MACSEC_LINUX
-- 
2.38.1




More information about the Hostap mailing list