Bypass QDISC for control frames on Linux
diff mbox series

Message ID 20190912192002.6105-1-alexander@wetzel-home.de
State New
Headers show
Series
  • Bypass QDISC for control frames on Linux
Related show

Commit Message

Alexander Wetzel Sept. 12, 2019, 7:20 p.m. UTC
Let wpa_supplicant/hostapd generated control frames bypass the QDISC
when the linux kernel supports the feature, to make sure the frames
are reaching the driver and are not delayed or dropped by QDISC.

This fixes a potential race when rekeying a connection under load:
Without that an EAPOL#4 frame send out by wpa_supplicant may not have
reached the driver when switching over to the new key, causing the AP to
disconnect the STA.

Signed-off-by: Alexander Wetzel <alexander@wetzel-home.de>
---

The intend of this patch is to make sure the eapol#4 frame has at least
reached the driver when we replace the key.

Now this is is just the quick & easy fix to make sure EAPOL#4 is at
least in the driver queues when we replace the key, as discussed here:
http://lists.infradead.org/pipermail/hostap/2019-September/040514.html

I've cleaned up the tested hack a bit and added the same bypass for
hostapd. I don't like the two independent eapol paths and think it would
make sense to let nl80211 handle eapol frames also for wpa_supplicant
and not just for hostapd. But the changes to get that implemented are
quite far reaching and not directly linked to the problem at hand.

I've also started looking how to unify the two paths, but this will be
quite invasive and nothing I see in the near future.

The patch here is therefore just enabling PACKET_QDISC_BYPASS on the
existing sockets we use to transmit eapol frames: When we can't bypass
the QDISC - probably the kernel is < 3.14 - we simply continue anyhow,
thus keeping the existing behavior. (A warning here seems a bit like
overkill, but that's just my gut feeling...)

Technically it would be sufficient to only apply the second part of the
patch (l2_packet_linux.c): Really critical is only the eapol#4 frame. 
But having some qdisc drop any 802.11 management frames - and then ONLY
eapol frames - did not look like a good idea.

 src/drivers/driver_nl80211.c    | 8 ++++++--
 src/l2_packet/l2_packet_linux.c | 9 +++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)

Patch
diff mbox series

diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index f699fb53f..a8372cfc0 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -1992,6 +1992,7 @@  static void * wpa_driver_nl80211_drv_init(void *ctx, const char *ifname,
 {
 	struct wpa_driver_nl80211_data *drv;
 	struct i802_bss *bss;
+	int enabled = 1;
 
 	if (global_priv == NULL)
 		return NULL;
@@ -2038,9 +2039,12 @@  static void * wpa_driver_nl80211_drv_init(void *ctx, const char *ifname,
 	if (drv->eapol_tx_sock < 0)
 		goto failed;
 
-	if (drv->data_tx_status) {
-		int enabled = 1;
+	/* The qdisc could delay/drop frames. Bypass it when supported. */
+	setsockopt(drv->eapol_tx_sock, SOL_PACKET, PACKET_QDISC_BYPASS,
+		   &enabled, sizeof(enabled));
 
+	if (drv->data_tx_status) {
+		enabled = 1;
 		if (setsockopt(drv->eapol_tx_sock, SOL_SOCKET, SO_WIFI_STATUS,
 			       &enabled, sizeof(enabled)) < 0) {
 			wpa_printf(MSG_DEBUG,
diff --git a/src/l2_packet/l2_packet_linux.c b/src/l2_packet/l2_packet_linux.c
index 291c9dd26..1bd7d15d1 100644
--- a/src/l2_packet/l2_packet_linux.c
+++ b/src/l2_packet/l2_packet_linux.c
@@ -274,6 +274,7 @@  struct l2_packet_data * l2_packet_init(
 	struct l2_packet_data *l2;
 	struct ifreq ifr;
 	struct sockaddr_ll ll;
+	int enabled = 1;
 
 	l2 = os_zalloc(sizeof(struct l2_packet_data));
 	if (l2 == NULL)
@@ -294,6 +295,14 @@  struct l2_packet_data * l2_packet_init(
 		os_free(l2);
 		return NULL;
 	}
+
+	/* The qdisc could delay/drop the eapol #3 frame, causing the frame to
+	 * be encrypted with the new key or not to be send out at all when
+	 * rekeying a busy link. Bypass it when supported.
+	 */
+	setsockopt(l2->fd, SOL_PACKET, PACKET_QDISC_BYPASS,
+                   &enabled, sizeof(enabled));
+
 	os_memset(&ifr, 0, sizeof(ifr));
 	os_strlcpy(ifr.ifr_name, l2->ifname, sizeof(ifr.ifr_name));
 	if (ioctl(l2->fd, SIOCGIFINDEX, &ifr) < 0) {