|
@@ -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,
|
|
|
+ ðertype_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);
|
|
|
}
|
|
|
|