[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