]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Work around Linux packet socket regression
authorJouni Malinen <j@w1.fi>
Sat, 31 Jan 2015 15:21:58 +0000 (17:21 +0200)
committerJouni Malinen <j@w1.fi>
Sat, 31 Jan 2015 15:21:58 +0000 (17:21 +0200)
Linux kernel commit 576eb62598f10c8c7fd75703fe89010cdcfff596 ('bridge:
respect RFC2863 operational state') from 2012 introduced a regression
for using wpa_supplicant with EAPOL frames and a station interface in a
bridge. Since it does not look like this regression is going to get
fixed any time soon (it is already two years from that commit and over
1.5 from a discussion pointing out the regression), add a workaround in
wpa_supplicant to avoid this issue.

The wpa_supplicant workaround uses a secondary packet socket to capture
all frames (ETH_P_ALL) from the netdev that is in a bridge. This is
needed to avoid the kernel regression. However, this comes at the price
of more CPU load. Some of this is avoided with use of Linux socket
filter, but still, this is less efficient than a packet socket bound to
the specific EAPOL ethertype. The workaround gets disabled
automatically, if the main packet socket interface on the bridge
interface turns out to be working for RX (e.g., due to an old kernel
version being used or a new kernel version having a fix for the
regression). In addition, this workaround is only taken into use for the
special case of running wpa_supplicant with an interface in a bridge.

Signed-off-by: Jouni Malinen <j@w1.fi>
src/l2_packet/l2_packet.h
src/l2_packet/l2_packet_freebsd.c
src/l2_packet/l2_packet_linux.c
src/l2_packet/l2_packet_ndis.c
src/l2_packet/l2_packet_none.c
src/l2_packet/l2_packet_privsep.c
src/l2_packet/l2_packet_winpcap.c
wpa_supplicant/wpa_supplicant.c

index 7537f93eebbe5b36524ee0988b889db75a030704..2a452458214be7d124c2e8481829893b553733d1 100644 (file)
@@ -67,6 +67,19 @@ struct l2_packet_data * l2_packet_init(
                            const u8 *buf, size_t len),
        void *rx_callback_ctx, int l2_hdr);
 
+/**
+ * l2_packet_init_bridge - Like l2_packet_init() but with bridge workaround
+ *
+ * This version of l2_packet_init() can be used to enable a workaround for Linux
+ * packet socket in case of a station interface in a bridge.
+ */
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr);
+
 /**
  * l2_packet_deinit - Deinitialize l2_packet interface
  * @l2: Pointer to internal l2_packet data from l2_packet_init()
index d87c32b2cc40d4e64a2d35227fb0d4d285c62b17..aa836482767bd6fe2ab713247bc930c59f448b14 100644 (file)
@@ -256,6 +256,18 @@ struct l2_packet_data * l2_packet_init(
 }
 
 
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr)
+{
+       return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
+                             rx_callback_ctx, l2_hdr);
+}
+
+
 void l2_packet_deinit(struct l2_packet_data *l2)
 {
        if (l2 != NULL) {
index 89ff7db5c80ddb00eca533d91218b37c18f021f3..68b20089b1025e3c49fe2cc06ce8b442dc82d416 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * WPA Supplicant - Layer2 packet handling with Linux packet sockets
- * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -27,6 +27,9 @@ struct l2_packet_data {
        void *rx_callback_ctx;
        int l2_hdr; /* whether to include layer 2 (Ethernet) header data
                     * buffers */
+
+       /* For working around Linux packet socket behavior and regression. */
+       int fd_br_rx;
 };
 
 /* Generated by 'sudo tcpdump -s 3000 -dd greater 278 and ip and udp and
@@ -130,6 +133,36 @@ static void l2_packet_receive(int sock, void *eloop_ctx, void *sock_ctx)
        }
 
        l2->rx_callback(l2->rx_callback_ctx, ll.sll_addr, buf, res);
+
+       if (l2->fd_br_rx >= 0) {
+               wpa_printf(MSG_DEBUG, "l2_packet_receive: Main packet socket for %s seems to have working RX - close workaround bridge socket",
+                          l2->ifname);
+               eloop_unregister_read_sock(l2->fd_br_rx);
+               close(l2->fd_br_rx);
+               l2->fd_br_rx = -1;
+       }
+}
+
+
+static void l2_packet_receive_br(int sock, void *eloop_ctx, void *sock_ctx)
+{
+       struct l2_packet_data *l2 = eloop_ctx;
+       u8 buf[2300];
+       int res;
+       struct sockaddr_ll ll;
+       socklen_t fromlen;
+
+       os_memset(&ll, 0, sizeof(ll));
+       fromlen = sizeof(ll);
+       res = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *) &ll,
+                      &fromlen);
+       if (res < 0) {
+               wpa_printf(MSG_DEBUG, "l2_packet_receive_br - recvfrom: %s",
+                          strerror(errno));
+               return;
+       }
+
+       l2->rx_callback(l2->rx_callback_ctx, ll.sll_addr, buf, res);
 }
 
 
@@ -150,6 +183,7 @@ struct l2_packet_data * l2_packet_init(
        l2->rx_callback = rx_callback;
        l2->rx_callback_ctx = rx_callback_ctx;
        l2->l2_hdr = l2_hdr;
+       l2->fd_br_rx = -1;
 
        l2->fd = socket(PF_PACKET, l2_hdr ? SOCK_RAW : SOCK_DGRAM,
                        htons(protocol));
@@ -197,6 +231,87 @@ struct l2_packet_data * l2_packet_init(
 }
 
 
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr)
+{
+       struct l2_packet_data *l2;
+       struct sock_filter ethertype_sock_filter_insns[] = {
+               /* Load ethertype */
+               BPF_STMT(BPF_LD | BPF_H | BPF_ABS, 2 * ETH_ALEN),
+               /* Jump over next statement if ethertype does not match */
+               BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, protocol, 0, 1),
+               /* Ethertype match - return all */
+               BPF_STMT(BPF_RET | BPF_K, ~0),
+               /* No match - drop */
+               BPF_STMT(BPF_RET | BPF_K, 0)
+       };
+       const struct sock_fprog ethertype_sock_filter = {
+               .len = ARRAY_SIZE(ethertype_sock_filter_insns),
+               .filter = ethertype_sock_filter_insns,
+       };
+       struct sockaddr_ll ll;
+
+       l2 = l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
+                           rx_callback_ctx, l2_hdr);
+       if (!l2)
+               return NULL;
+
+       /*
+        * The Linux packet socket behavior has changed over the years and there
+        * is an inconvenient regression in it that breaks RX for a specific
+        * protocol from interfaces in a bridge when that interface is not in
+        * fully operation state (i.e., when in station mode and not completed
+        * authorization). To work around this, register ETH_P_ALL version of
+        * the packet socket bound to the real netdev and use socket filter to
+        * match the ethertype value. This version is less efficient, but
+        * required for functionality with many kernel version. If the main
+        * packet socket is found to be working, this less efficient version
+        * gets closed automatically.
+        */
+
+       l2->fd_br_rx = socket(PF_PACKET, l2_hdr ? SOCK_RAW : SOCK_DGRAM,
+                             htons(ETH_P_ALL));
+       if (l2->fd_br_rx < 0) {
+               wpa_printf(MSG_DEBUG, "%s: socket(PF_PACKET-fd_br_rx): %s",
+                          __func__, strerror(errno));
+               /* try to continue without the workaround RX socket */
+               return l2;
+       }
+
+       os_memset(&ll, 0, sizeof(ll));
+       ll.sll_family = PF_PACKET;
+       ll.sll_ifindex = if_nametoindex(ifname);
+       ll.sll_protocol = htons(ETH_P_ALL);
+       if (bind(l2->fd_br_rx, (struct sockaddr *) &ll, sizeof(ll)) < 0) {
+               wpa_printf(MSG_DEBUG, "%s: bind[PF_PACKET-fd_br_rx]: %s",
+                          __func__, strerror(errno));
+               /* try to continue without the workaround RX socket */
+               close(l2->fd_br_rx);
+               l2->fd_br_rx = -1;
+               return l2;
+       }
+
+       if (setsockopt(l2->fd_br_rx, SOL_SOCKET, SO_ATTACH_FILTER,
+                      &ethertype_sock_filter, sizeof(struct sock_fprog))) {
+               wpa_printf(MSG_DEBUG,
+                          "l2_packet_linux: setsockopt(SO_ATTACH_FILTER) failed: %s",
+                          strerror(errno));
+               /* try to continue without the workaround RX socket */
+               close(l2->fd_br_rx);
+               l2->fd_br_rx = -1;
+               return l2;
+       }
+
+       eloop_register_read_sock(l2->fd_br_rx, l2_packet_receive_br, l2, NULL);
+
+       return l2;
+}
+
+
 void l2_packet_deinit(struct l2_packet_data *l2)
 {
        if (l2 == NULL)
@@ -206,7 +321,12 @@ void l2_packet_deinit(struct l2_packet_data *l2)
                eloop_unregister_read_sock(l2->fd);
                close(l2->fd);
        }
-               
+
+       if (l2->fd_br_rx >= 0) {
+               eloop_unregister_read_sock(l2->fd_br_rx);
+               close(l2->fd_br_rx);
+       }
+
        os_free(l2);
 }
 
index 39a62a0abe7fb9e9239b15125cf42364f1715ffd..716778164af4adc1672ea1d40bb3577875605853 100644 (file)
@@ -450,6 +450,18 @@ struct l2_packet_data * l2_packet_init(
 }
 
 
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr)
+{
+       return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
+                             rx_callback_ctx, l2_hdr);
+}
+
+
 void l2_packet_deinit(struct l2_packet_data *l2)
 {
        if (l2 == NULL)
index 0501925c6c0763e4b0a59786545d1eff6ab3460a..307fc6daa4039668689cbe56f8f6ea4326d21465 100644 (file)
@@ -91,6 +91,18 @@ struct l2_packet_data * l2_packet_init(
 }
 
 
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr)
+{
+       return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
+                             rx_callback_ctx, l2_hdr);
+}
+
+
 void l2_packet_deinit(struct l2_packet_data *l2)
 {
        if (l2 == NULL)
index 76dcccc708129d5700d6b7af7eb2c66413801f44..e26ca20a8625d13a0f5bc758db4a042ab545a995 100644 (file)
@@ -231,6 +231,18 @@ fail:
 }
 
 
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr)
+{
+       return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
+                             rx_callback_ctx, l2_hdr);
+}
+
+
 void l2_packet_deinit(struct l2_packet_data *l2)
 {
        if (l2 == NULL)
index b6e5088938ec0d1b4c7f52ad2c3b9c122a0e6978..74085a3169af8309d9d82a48ea641fd3eda904e3 100644 (file)
@@ -248,6 +248,18 @@ struct l2_packet_data * l2_packet_init(
 }
 
 
+struct l2_packet_data * l2_packet_init_bridge(
+       const char *br_ifname, const char *ifname, const u8 *own_addr,
+       unsigned short protocol,
+       void (*rx_callback)(void *ctx, const u8 *src_addr,
+                           const u8 *buf, size_t len),
+       void *rx_callback_ctx, int l2_hdr)
+{
+       return l2_packet_init(br_ifname, own_addr, protocol, rx_callback,
+                             rx_callback_ctx, l2_hdr);
+}
+
+
 static void l2_packet_deinit_timeout(void *eloop_ctx, void *timeout_ctx)
 {
        struct l2_packet_data *l2 = eloop_ctx;
index cd96b63bf4b4c0e5ef1938925332292910e61622..6ad09a87517490f1d8d0ca2d97f89e76d0dbceb4 100644 (file)
@@ -3086,11 +3086,9 @@ int wpa_supplicant_driver_init(struct wpa_supplicant *wpa_s)
        if (wpa_s->bridge_ifname[0]) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Receiving packets from bridge "
                        "interface '%s'", wpa_s->bridge_ifname);
-               wpa_s->l2_br = l2_packet_init(wpa_s->bridge_ifname,
-                                             wpa_s->own_addr,
-                                             ETH_P_EAPOL,
-                                             wpa_supplicant_rx_eapol_bridge,
-                                             wpa_s, 1);
+               wpa_s->l2_br = l2_packet_init_bridge(
+                       wpa_s->bridge_ifname, wpa_s->ifname, wpa_s->own_addr,
+                       ETH_P_EAPOL, wpa_supplicant_rx_eapol_bridge, wpa_s, 1);
                if (wpa_s->l2_br == NULL) {
                        wpa_msg(wpa_s, MSG_ERROR, "Failed to open l2_packet "
                                "connection for the bridge interface '%s'",