[PATCHv3,0/4] Improvements for roaming

Submitted by Jouni Malinen on Nov. 29, 2016, 9:55 p.m.

Details

Message ID 20161129215559.GA28813@w1.fi
State New
Headers show

Commit Message

Jouni Malinen Nov. 29, 2016, 9:55 p.m.
On Fri, Oct 21, 2016 at 01:11:48PM +0200, Michael Braun wrote:
> here comes the update patchset for a new FT inter-AP communication
> protocol already including broadcasting (but without caching).
> 
> Most important changes:
>  - use new oui ethertype
>    This has been left as separate commit for easier review, but can
>    be squashed easily.

Thanks! The first patch looks pretty good. I rebased this on top of the
current tree and did some cleanup while reviewing this (see attached
version). It is actually good to keep this as a separate commit as long
as 2/4 gets applied at the same time to avoid multiple different times
of breaking protocol backwards compatibility.

>  - use AES+HMAC-SHA256 with Encrypt-Then-Mac scheme for AED protection

So this uses AES key wrapping as the encryption mechanism and
HMAC-SHA256 as a separate authentication step on top of that. This
sounds like a bit strange combination to me.. Since there are multiple
AEAD options and number of those already implemented in hostap.git, it
would seem to make more sense to go with that instead to make the
implementation simpler..

AES-CCM, AES-GCM, and AES-SIV are available in hostap.git. AES-SIV would
likely be the simplest option here due to not having to come up with a
mechanism for defining separate nonces/IVs. That said, we should
consider potential needs for replay protection here as well.. In the
current implementation, the only available protection is timestamp
comparison which has a 60 second window to allow not exactly
synchronized system times on the devices.

Patch hide | download patch | download mbox

From 152f78a56af565a2f08c1b90ae6d5b169fdf1c6f Mon Sep 17 00:00:00 2001
From: Michael Braun <michael-dev@fami-braun.de>
Date: Fri, 21 Oct 2016 13:11:49 +0200
Subject: [PATCH 1/4] FT: Replace inter-AP protocol with use of OUI Extended
 Ethertype

Replace the previously used extension of IEEE 802.11 managed Ethertype
89-0d (originally added for Remote Request/Response in IEEE 802.11r)
with Ethertype 88-b7 (OUI Extended EtherType) for FT inter-AP
communication. The new design uses a more properly assigned identifier
for the messages.

This breaks backward compatiblity.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
---
 hostapd/Makefile       |   6 ++
 hostapd/main.c         |   3 +
 src/ap/eth_p_oui.c     | 189 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/ap/eth_p_oui.h     |  61 ++++++++++++++++
 src/ap/hostapd.h       |  10 +++
 src/ap/wpa_auth.h      |  30 +++-----
 src/ap/wpa_auth_ft.c   |  74 ++++++++++++-------
 src/ap/wpa_auth_glue.c | 152 ++++++++++++++++++++++++++++++++++++++-
 src/utils/common.h     |   3 +
 9 files changed, 481 insertions(+), 47 deletions(-)
 create mode 100644 src/ap/eth_p_oui.c
 create mode 100644 src/ap/eth_p_oui.h

diff --git a/hostapd/Makefile b/hostapd/Makefile
index 52c72dc..051c7d4 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -295,6 +295,12 @@  OBJS += ../src/ap/wpa_auth_ft.o
 NEED_SHA256=y
 NEED_AES_OMAC1=y
 NEED_AES_UNWRAP=y
+NEED_ETH_P_OUI=y
+endif
+
+ifdef NEED_ETH_P_OUI
+CFLAGS += -DCONFIG_ETH_P_OUI
+OBJS += ../src/ap/eth_p_oui.o
 endif
 
 ifdef CONFIG_SAE
diff --git a/hostapd/main.c b/hostapd/main.c
index bcc47a4..b57dd78 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -659,6 +659,9 @@  int main(int argc, char *argv[])
 	interfaces.global_iface_name = NULL;
 	interfaces.global_ctrl_sock = -1;
 	dl_list_init(&interfaces.global_ctrl_dst);
+#ifdef CONFIG_ETH_P_OUI
+	dl_list_init(&interfaces.eth_p_oui);
+#endif /* CONFIG_ETH_P_OUI */
 
 	for (;;) {
 		c = getopt(argc, argv, "b:Bde:f:hi:KP:STtu:vg:G:");
diff --git a/src/ap/eth_p_oui.c b/src/ap/eth_p_oui.c
new file mode 100644
index 0000000..bdd15db
--- /dev/null
+++ b/src/ap/eth_p_oui.c
@@ -0,0 +1,189 @@ 
+/*
+ * hostapd / IEEE 802 OUI Extended EtherType 88-B7
+ * Copyright (c) 2016, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "l2_packet/l2_packet.h"
+#include "hostapd.h"
+#include "eth_p_oui.h"
+
+/*
+ * See IEEE Std 802-8014, Clause 9.2.4 for the definition of the OUI Extended
+ * EtherType 88-B7. This file implements this with OUI 00:13:74 and
+ * vendor-specific subtype 0x0001.
+ */
+const u8 global_oui[] = { 0x00, 0x13, 0x74, 0x00, 0x01 };
+
+struct eth_p_oui_iface {
+	struct dl_list list;
+	char ifname[IFNAMSIZ + 1];
+	struct l2_packet_data *l2;
+	struct dl_list receiver;
+};
+
+struct eth_p_oui_ctx {
+	struct dl_list list;
+	struct eth_p_oui_iface *iface;
+	/* all data needed to deliver and unregister */
+	u8 oui_suffix; /* last byte of OUI */
+	void (*rx_callback)(void *ctx, const u8 *src_addr,
+			    const u8 *dst_addr, u8 oui_suffix,
+			    const u8 *buf, size_t len);
+	void *rx_callback_ctx;
+};
+
+
+void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		       const u8 *dst_addr, const u8 *buf, size_t len)
+{
+	ctx->rx_callback(ctx->rx_callback_ctx, src_addr, dst_addr,
+			 ctx->oui_suffix, buf, len);
+}
+
+
+static void eth_p_rx(void *ctx, const u8 *src_addr, const u8 *buf, size_t len)
+{
+	struct eth_p_oui_iface *iface = ctx;
+	struct eth_p_oui_ctx *receiver;
+	const struct l2_ethhdr *ethhdr;
+
+	if (len < sizeof(*ethhdr) + sizeof(global_oui) + 1) {
+		/* too short packet */
+		return;
+	}
+
+	ethhdr = (struct l2_ethhdr *) buf;
+	/* trim eth_hdr from buf and len */
+	buf += sizeof(*ethhdr);
+	len -= sizeof(*ethhdr);
+
+	dl_list_for_each(receiver, &iface->receiver,
+			 struct eth_p_oui_ctx, list) {
+		if (os_memcmp(buf, global_oui, sizeof(global_oui)) != 0 ||
+		    buf[sizeof(global_oui)] != receiver->oui_suffix)
+			continue;
+
+		/* do not pass OUI to rx_callback */
+		eth_p_oui_deliver(receiver, ethhdr->h_source, ethhdr->h_dest,
+				  buf + sizeof(global_oui) + 1,
+				  len - sizeof(global_oui) - 1);
+	}
+}
+
+
+struct eth_p_oui_ctx *
+eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix,
+		   void (*rx_callback)(void *ctx, const u8 *src_addr,
+				       const u8 *dst_addr, u8 oui_suffix,
+				       const u8 *buf, size_t len),
+		   void *rx_callback_ctx)
+{
+	struct eth_p_oui_iface *iface;
+	struct eth_p_oui_ctx *receiver;
+	int found = 0;
+	struct hapd_interfaces *interfaces;
+
+	receiver = os_zalloc(sizeof(*receiver));
+	if (!receiver)
+		goto err;
+
+	receiver->oui_suffix = oui_suffix;
+	receiver->rx_callback = rx_callback;
+	receiver->rx_callback_ctx = rx_callback_ctx;
+
+	interfaces = hapd->iface->interfaces;
+
+	dl_list_for_each(iface, &interfaces->eth_p_oui, struct eth_p_oui_iface,
+			 list) {
+		if (os_strncmp(iface->ifname, ifname, sizeof(iface->ifname)) !=
+		    0)
+			continue;
+		found = 1;
+		break;
+	}
+
+	if (!found) {
+		iface = os_zalloc(sizeof(*iface));
+		if (!iface)
+			goto err;
+
+		os_strlcpy(iface->ifname, ifname, sizeof(iface->ifname));
+		iface->l2 = l2_packet_init(ifname, NULL, ETH_P_OUI, eth_p_rx,
+					   iface, 1);
+		if (!iface->l2) {
+			os_free(iface);
+			goto err;
+		}
+		dl_list_init(&iface->receiver);
+
+		dl_list_add_tail(&interfaces->eth_p_oui, &iface->list);
+	}
+
+	dl_list_add_tail(&iface->receiver, &receiver->list);
+	receiver->iface = iface;
+
+	return receiver;
+err:
+	os_free(receiver);
+	return NULL;
+}
+
+
+void eth_p_oui_unregister(struct eth_p_oui_ctx *ctx)
+{
+	struct eth_p_oui_iface *iface;
+
+	if (!ctx)
+		return;
+
+	iface = ctx->iface;
+
+	dl_list_del(&ctx->list);
+	os_free(ctx);
+
+	if (dl_list_empty(&iface->receiver)) {
+		dl_list_del(&iface->list);
+		l2_packet_deinit(iface->l2);
+		os_free(iface);
+	}
+}
+
+
+int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		   const u8 *dst_addr, const u8 *buf, size_t len)
+{
+	struct eth_p_oui_iface *iface = ctx->iface;
+	u8 *packet, *p;
+	size_t packet_len;
+	int ret;
+	struct l2_ethhdr *ethhdr;
+
+	packet_len = sizeof(*ethhdr) + sizeof(global_oui) + 1 + len;
+	packet = os_zalloc(packet_len);
+	if (!packet)
+		return -1;
+	p = packet;
+
+	ethhdr = (struct l2_ethhdr *) packet;
+	os_memcpy(ethhdr->h_source, src_addr, ETH_ALEN);
+	os_memcpy(ethhdr->h_dest, dst_addr, ETH_ALEN);
+	ethhdr->h_proto = host_to_be16(ETH_P_OUI);
+	p += sizeof(*ethhdr);
+
+	os_memcpy(p, global_oui, sizeof(global_oui));
+	p[sizeof(global_oui)] = ctx->oui_suffix;
+	p += sizeof(global_oui) + 1;
+
+	os_memcpy(p, buf, len);
+
+	ret = l2_packet_send(iface->l2, NULL, 0, packet, packet_len);
+	os_free(packet);
+	return ret;
+}
diff --git a/src/ap/eth_p_oui.h b/src/ap/eth_p_oui.h
new file mode 100644
index 0000000..c4cbd3b
--- /dev/null
+++ b/src/ap/eth_p_oui.h
@@ -0,0 +1,61 @@ 
+/*
+ * hostapd / IEEE 802 OUI Extended Ethertype
+ * Copyright (c) 2016, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef ETH_P_OUI_H
+#define ETH_P_OUI_H
+
+struct eth_p_oui_ctx;
+struct hostapd_data;
+
+#ifdef CONFIG_ETH_P_OUI
+
+/* rx_callback only gets payload after OUI passed as buf */
+struct eth_p_oui_ctx *
+eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix,
+		   void (*rx_callback)(void *ctx, const u8 *src_addr,
+				       const u8 *dst_addr, u8 oui_suffix,
+				       const u8 *buf, size_t len),
+		   void *rx_callback_ctx);
+void eth_p_oui_unregister(struct eth_p_oui_ctx *eth_p_oui);
+int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		   const u8 *dst_addr, const u8 *buf, size_t len);
+void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		       const u8 *dst_addr, const u8 *buf, size_t len);
+
+#else /* CONFIG_ETH_P_OUI */
+
+static inline struct eth_p_oui_ctx *
+eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix,
+		   void (*rx_callback)(void *ctx, const u8 *src_addr,
+				       const u8 *dst_addr, u8 oui_suffix,
+				       const u8 *buf, size_t len),
+		   void *rx_callback_ctx)
+{
+	return NULL;
+}
+
+static inline void eth_p_oui_unregister(struct eth_p_oui_ctx *ctx)
+{
+}
+
+static inline int
+eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+	       const u8 *dst_addr, const u8 *buf, size_t len)
+{
+	return -1;
+}
+
+static inline void
+eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr,
+		  const u8 *dst_addr, const u8 *buf, size_t len)
+{
+}
+
+#endif /* CONFIG_ETH_P_OUI */
+
+#endif /* ETH_P_OUI_H */
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index fd5aaed..9a870f2 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -53,6 +53,9 @@  struct hapd_interfaces {
 #ifndef CONFIG_NO_VLAN
 	struct dynamic_iface *vlan_priv;
 #endif /* CONFIG_NO_VLAN */
+#ifdef CONFIG_ETH_P_OUI
+	struct dl_list eth_p_oui; /* OUI Extended EtherType handlers */
+#endif /* CONFIG_ETH_P_OUI */
 	int eloop_initialized;
 };
 
@@ -185,6 +188,13 @@  struct hostapd_data {
 #endif /* CONFIG_FULL_DYNAMIC_VLAN */
 
 	struct l2_packet_data *l2;
+
+#ifdef CONFIG_IEEE80211R
+	struct eth_p_oui_ctx *oui_ft_pull;
+	struct eth_p_oui_ctx *oui_ft_push;
+	struct eth_p_oui_ctx *oui_ft_resp;
+#endif /* CONFIG_IEEE80211R */
+
 	struct wps_context *wps;
 
 	int beacon_set_done;
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 743f2e6..0a3e87f 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -37,10 +37,12 @@  struct ft_rrb_frame {
 
 #define FT_PACKET_REQUEST 0
 #define FT_PACKET_RESPONSE 1
-/* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r */
-#define FT_PACKET_R0KH_R1KH_PULL 200
-#define FT_PACKET_R0KH_R1KH_RESP 201
-#define FT_PACKET_R0KH_R1KH_PUSH 202
+
+/* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r. These
+ * use OUI Extended EtherType as the encapsulating format. */
+#define FT_PACKET_R0KH_R1KH_PULL 0x01
+#define FT_PACKET_R0KH_R1KH_RESP 0x02
+#define FT_PACKET_R0KH_R1KH_PUSH 0x03
 
 #define FT_R0KH_R1KH_PULL_NONCE_LEN 16
 #define FT_R0KH_R1KH_PULL_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
@@ -49,11 +51,6 @@  struct ft_rrb_frame {
 #define FT_R0KH_R1KH_PULL_PAD_LEN ((8 - FT_R0KH_R1KH_PULL_DATA_LEN % 8) % 8)
 
 struct ft_r0kh_r1kh_pull_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PULL */
-	le16 data_length; /* little endian length of data (44) */
-	u8 ap_address[ETH_ALEN];
-
 	u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN];
 	u8 pmk_r0_name[WPA_PMK_NAME_LEN];
 	u8 r1kh_id[FT_R1KH_ID_LEN];
@@ -67,11 +64,6 @@  struct ft_r0kh_r1kh_pull_frame {
 				    WPA_PMK_NAME_LEN + 2)
 #define FT_R0KH_R1KH_RESP_PAD_LEN ((8 - FT_R0KH_R1KH_RESP_DATA_LEN % 8) % 8)
 struct ft_r0kh_r1kh_resp_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_RESP */
-	le16 data_length; /* little endian length of data (78) */
-	u8 ap_address[ETH_ALEN];
-
 	u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; /* copied from pull */
 	u8 r1kh_id[FT_R1KH_ID_LEN]; /* copied from pull */
 	u8 s1kh_id[ETH_ALEN]; /* copied from pull */
@@ -87,11 +79,6 @@  struct ft_r0kh_r1kh_resp_frame {
 				    WPA_PMK_NAME_LEN + 2)
 #define FT_R0KH_R1KH_PUSH_PAD_LEN ((8 - FT_R0KH_R1KH_PUSH_DATA_LEN % 8) % 8)
 struct ft_r0kh_r1kh_push_frame {
-	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
-	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PUSH */
-	le16 data_length; /* little endian length of data (82) */
-	u8 ap_address[ETH_ALEN];
-
 	/* Encrypted with AES key-wrap */
 	u8 timestamp[4]; /* current time in seconds since unix epoch, little
 			  * endian */
@@ -221,6 +208,8 @@  struct wpa_auth_callbacks {
 						  void *ctx), void *cb_ctx);
 	int (*send_ether)(void *ctx, const u8 *dst, u16 proto, const u8 *data,
 			  size_t data_len);
+	int (*send_oui)(void *ctx, const u8 *dst, u8 oui_suffix, const u8 *data,
+			size_t data_len);
 #ifdef CONFIG_IEEE80211R_AP
 	struct wpa_state_machine * (*add_sta)(void *ctx, const u8 *sta_addr);
 	int (*send_ft_action)(void *ctx, const u8 *dst,
@@ -328,6 +317,9 @@  u16 wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies,
 int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len);
 int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 		  const u8 *data, size_t data_len);
+void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
+		       const u8 *dst_addr, u8 oui_suffix, const u8 *data,
+		       size_t data_len);
 void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr);
 #endif /* CONFIG_IEEE80211R_AP */
 
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 637d6d6..0d03f4b 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -41,6 +41,19 @@  static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst,
 }
 
 
+static int wpa_ft_rrb_oui_send(struct wpa_authenticator *wpa_auth,
+			       const u8 *dst,  u8 oui_suffix,
+			       const u8 *data, size_t data_len)
+{
+	if (!wpa_auth->cb.send_oui)
+		return -1;
+	wpa_printf(MSG_DEBUG, "FT: RRB-OUI type %u send to " MACSTR,
+		   oui_suffix, MAC2STR(dst));
+	return wpa_auth->cb.send_oui(wpa_auth->cb.ctx, dst, oui_suffix, data,
+				     data_len);
+}
+
+
 static int wpa_ft_action_send(struct wpa_authenticator *wpa_auth,
 			      const u8 *dst, const u8 *data, size_t data_len)
 {
@@ -337,11 +350,6 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 		   "address " MACSTR, MAC2STR(r0kh->addr));
 
 	os_memset(&frame, 0, sizeof(frame));
-	frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
-	frame.packet_type = FT_PACKET_R0KH_R1KH_PULL;
-	frame.data_length = host_to_le16(FT_R0KH_R1KH_PULL_DATA_LEN);
-	os_memcpy(frame.ap_address, sm->wpa_auth->addr, ETH_ALEN);
-
 	/* aes_wrap() does not support inplace encryption, so use a temporary
 	 * buffer for the data. */
 	if (random_get_bytes(f.nonce, FT_R0KH_R1KH_PULL_NONCE_LEN)) {
@@ -366,7 +374,8 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 	if (sm->ft_pending_req_ies == NULL)
 		return -1;
 
-	wpa_ft_rrb_send(sm->wpa_auth, r0kh->addr, (u8 *) &frame, sizeof(frame));
+	wpa_ft_rrb_oui_send(sm->wpa_auth, r0kh->addr, FT_PACKET_R0KH_R1KH_PULL,
+			    (u8 *) &frame, sizeof(frame));
 
 	return 0;
 }
@@ -1461,11 +1470,6 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 		   MACSTR, MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id));
 
 	os_memset(&resp, 0, sizeof(resp));
-	resp.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
-	resp.packet_type = FT_PACKET_R0KH_R1KH_RESP;
-	resp.data_length = host_to_le16(FT_R0KH_R1KH_RESP_DATA_LEN);
-	os_memcpy(resp.ap_address, wpa_auth->addr, ETH_ALEN);
-
 	/* aes_wrap() does not support inplace encryption, so use a temporary
 	 * buffer for the data. */
 	os_memcpy(r.nonce, f.nonce, sizeof(f.nonce));
@@ -1495,7 +1499,8 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 
 	os_memset(pmk_r0, 0, PMK_LEN);
 
-	wpa_ft_rrb_send(wpa_auth, src_addr, (u8 *) &resp, sizeof(resp));
+	wpa_ft_rrb_oui_send(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_RESP,
+			    (u8 *) &resp, sizeof(resp));
 
 	return 0;
 }
@@ -1736,13 +1741,6 @@  int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 		return -1;
 	}
 
-	if (frame->packet_type == FT_PACKET_R0KH_R1KH_PULL)
-		return wpa_ft_rrb_rx_pull(wpa_auth, src_addr, data, data_len);
-	if (frame->packet_type == FT_PACKET_R0KH_R1KH_RESP)
-		return wpa_ft_rrb_rx_resp(wpa_auth, src_addr, data, data_len);
-	if (frame->packet_type == FT_PACKET_R0KH_R1KH_PUSH)
-		return wpa_ft_rrb_rx_push(wpa_auth, src_addr, data, data_len);
-
 	wpa_hexdump(MSG_MSGDUMP, "FT: RRB - FT Action frame", pos, alen);
 
 	if (alen < 1 + 1 + 2 * ETH_ALEN) {
@@ -1820,6 +1818,36 @@  int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 }
 
 
+void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
+		       const u8 *dst_addr, u8 oui_suffix, const u8 *data,
+		       size_t data_len)
+{
+	wpa_printf(MSG_DEBUG, "FT: RRB received frame from remote AP " MACSTR,
+		   MAC2STR(src_addr));
+	wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame - oui_suffix=%d", oui_suffix);
+
+	if (is_multicast_ether_addr(dst_addr)) {
+		wpa_printf(MSG_DEBUG,
+			   "FT: RRB-OUI received frame from remote AP " MACSTR
+			   " to multicast address " MACSTR,
+			   MAC2STR(src_addr), MAC2STR(dst_addr));
+		return;
+	}
+
+	switch (oui_suffix) {
+	case FT_PACKET_R0KH_R1KH_PULL:
+		wpa_ft_rrb_rx_pull(wpa_auth, src_addr, data, data_len);
+		break;
+	case FT_PACKET_R0KH_R1KH_RESP:
+		wpa_ft_rrb_rx_resp(wpa_auth, src_addr, data, data_len);
+		break;
+	case FT_PACKET_R0KH_R1KH_PUSH:
+		wpa_ft_rrb_rx_push(wpa_auth, src_addr, data, data_len);
+		break;
+	}
+}
+
+
 static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 				   struct wpa_ft_pmk_r0_sa *pmk_r0,
 				   struct ft_remote_r1kh *r1kh,
@@ -1831,11 +1859,6 @@  static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 	u8 *crypt;
 
 	os_memset(&frame, 0, sizeof(frame));
-	frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB;
-	frame.packet_type = FT_PACKET_R0KH_R1KH_PUSH;
-	frame.data_length = host_to_le16(FT_R0KH_R1KH_PUSH_DATA_LEN);
-	os_memcpy(frame.ap_address, wpa_auth->addr, ETH_ALEN);
-
 	/* aes_wrap() does not support inplace encryption, so use a temporary
 	 * buffer for the data. */
 	os_memcpy(f.r1kh_id, r1kh->id, FT_R1KH_ID_LEN);
@@ -1860,7 +1883,8 @@  static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 		     plain, crypt) < 0)
 		return;
 
-	wpa_ft_rrb_send(wpa_auth, r1kh->addr, (u8 *) &frame, sizeof(frame));
+	wpa_ft_rrb_oui_send(wpa_auth, r1kh->addr, FT_PACKET_R0KH_R1KH_PUSH,
+			    (u8 *) &frame, sizeof(frame));
 }
 
 
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index aabac36..0ce4af2 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -17,6 +17,7 @@ 
 #include "eapol_auth/eapol_auth_sm_i.h"
 #include "eap_server/eap.h"
 #include "l2_packet/l2_packet.h"
+#include "eth_p_oui.h"
 #include "hostapd.h"
 #include "ieee802_1x.h"
 #include "preauth_auth.h"
@@ -506,6 +507,107 @@  static int hostapd_wpa_auth_send_ether(void *ctx, const u8 *dst, u16 proto,
 
 #ifdef CONFIG_IEEE80211R_AP
 
+struct wpa_auth_oui_iface_iter_data {
+	struct hostapd_data *src_hapd;
+	const u8 *dst;
+	const u8 *data;
+	size_t data_len;
+	u8 oui_suffix;
+};
+
+
+static int hostapd_wpa_auth_oui_iter(struct hostapd_iface *iface, void *ctx)
+{
+	struct wpa_auth_oui_iface_iter_data *idata = ctx;
+	struct hostapd_data *hapd;
+	struct eth_p_oui_ctx *oui_ctx;
+	size_t j;
+
+	for (j = 0; j < iface->num_bss; j++) {
+		hapd = iface->bss[j];
+		if (hapd == idata->src_hapd)
+			continue;
+		if (!is_multicast_ether_addr(idata->dst) &&
+		    os_memcmp(hapd->own_addr, idata->dst, ETH_ALEN) != 0)
+			continue;
+
+		switch (idata->oui_suffix) {
+		case FT_PACKET_R0KH_R1KH_PULL:
+			oui_ctx = hapd->oui_ft_pull;
+			break;
+		case FT_PACKET_R0KH_R1KH_PUSH:
+			oui_ctx = hapd->oui_ft_push;
+			break;
+		case FT_PACKET_R0KH_R1KH_RESP:
+			oui_ctx = hapd->oui_ft_resp;
+			break;
+		default:
+			oui_ctx = NULL;
+			break;
+		}
+
+		if (!oui_ctx)
+			continue;
+
+		eth_p_oui_deliver(oui_ctx, idata->src_hapd->own_addr,
+				  idata->dst, idata->data, idata->data_len);
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
+static int hostapd_wpa_auth_send_oui(void *ctx, const u8 *dst, u8 oui_suffix,
+				     const u8 *data, size_t data_len)
+{
+	struct hostapd_data *hapd = ctx;
+	struct eth_p_oui_ctx *oui_ctx;
+
+#ifdef CONFIG_IEEE80211R_AP
+	if (hapd->iface->interfaces &&
+	    hapd->iface->interfaces->for_each_interface) {
+		struct wpa_auth_oui_iface_iter_data idata;
+		int res;
+
+		idata.src_hapd = hapd;
+		idata.dst = dst;
+		idata.data = data;
+		idata.data_len = data_len;
+		idata.oui_suffix = oui_suffix;
+		res = hapd->iface->interfaces->for_each_interface(
+			hapd->iface->interfaces, hostapd_wpa_auth_oui_iter,
+			&idata);
+		if (res == 1)
+			return data_len;
+	}
+#endif /* CONFIG_IEEE80211R_AP */
+
+	switch (oui_suffix) {
+	case FT_PACKET_R0KH_R1KH_PULL:
+		oui_ctx = hapd->oui_ft_pull;
+		break;
+	case FT_PACKET_R0KH_R1KH_PUSH:
+		oui_ctx = hapd->oui_ft_push;
+		break;
+	case FT_PACKET_R0KH_R1KH_RESP:
+		oui_ctx = hapd->oui_ft_resp;
+		break;
+	default:
+		oui_ctx = NULL;
+		break;
+	}
+
+	if (!oui_ctx)
+		return -1;
+
+	return eth_p_oui_send(oui_ctx, hapd->own_addr, dst, data, data_len);
+}
+
+
+#ifdef CONFIG_IEEE80211R_AP
+
 static int hostapd_wpa_auth_send_ft_action(void *ctx, const u8 *dst,
 					   const u8 *data, size_t data_len)
 {
@@ -582,6 +684,22 @@  static void hostapd_rrb_receive(void *ctx, const u8 *src_addr, const u8 *buf,
 }
 
 
+static void hostapd_rrb_oui_receive(void *ctx, const u8 *src_addr,
+				    const u8 *dst_addr, u8 oui_suffix,
+				    const u8 *buf, size_t len)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> "
+		   MACSTR, MAC2STR(src_addr), MAC2STR(dst_addr));
+	if (!is_multicast_ether_addr(dst_addr) &&
+	    os_memcmp(hapd->own_addr, dst_addr, ETH_ALEN) != 0)
+		return;
+	wpa_ft_rrb_oui_rx(hapd->wpa_auth, src_addr, dst_addr, oui_suffix, buf,
+			  len);
+}
+
+
 static int hostapd_wpa_auth_add_tspec(void *ctx, const u8 *sta_addr,
 				      u8 *tspec_ie, size_t tspec_ielen)
 {
@@ -620,6 +738,7 @@  int hostapd_setup_wpa(struct hostapd_data *hapd)
 	cb.for_each_sta = hostapd_wpa_auth_for_each_sta;
 	cb.for_each_auth = hostapd_wpa_auth_for_each_auth;
 	cb.send_ether = hostapd_wpa_auth_send_ether;
+	cb.send_oui = hostapd_wpa_auth_send_oui;
 #ifdef CONFIG_IEEE80211R_AP
 	cb.send_ft_action = hostapd_wpa_auth_send_ft_action;
 	cb.add_sta = hostapd_wpa_auth_add_sta;
@@ -653,9 +772,11 @@  int hostapd_setup_wpa(struct hostapd_data *hapd)
 #ifdef CONFIG_IEEE80211R_AP
 	if (!hostapd_drv_none(hapd) &&
 	    wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt)) {
-		hapd->l2 = l2_packet_init(hapd->conf->bridge[0] ?
-					  hapd->conf->bridge :
-					  hapd->conf->iface, NULL, ETH_P_RRB,
+		const char *ft_iface;
+
+		ft_iface = hapd->conf->bridge[0] ? hapd->conf->bridge :
+			   hapd->conf->iface;
+		hapd->l2 = l2_packet_init(ft_iface, NULL, ETH_P_RRB,
 					  hostapd_rrb_receive, hapd, 1);
 		if (hapd->l2 == NULL &&
 		    (hapd->driver == NULL ||
@@ -664,6 +785,25 @@  int hostapd_setup_wpa(struct hostapd_data *hapd)
 				   "interface");
 			return -1;
 		}
+
+		hapd->oui_ft_pull = eth_p_oui_register(hapd, ft_iface,
+						       FT_PACKET_R0KH_R1KH_PULL,
+						       hostapd_rrb_oui_receive,
+						       hapd);
+		hapd->oui_ft_push = eth_p_oui_register(hapd, ft_iface,
+						       FT_PACKET_R0KH_R1KH_PUSH,
+						       hostapd_rrb_oui_receive,
+						       hapd);
+		hapd->oui_ft_resp = eth_p_oui_register(hapd, ft_iface,
+						       FT_PACKET_R0KH_R1KH_RESP,
+						       hostapd_rrb_oui_receive,
+						       hapd);
+		if (!hapd->oui_ft_pull || !hapd->oui_ft_push ||
+		    !hapd->oui_ft_resp) {
+			wpa_printf(MSG_ERROR,
+				   "Failed to open ETH_P_OUI interface");
+			return -1;
+		}
 	}
 #endif /* CONFIG_IEEE80211R_AP */
 
@@ -706,5 +846,11 @@  void hostapd_deinit_wpa(struct hostapd_data *hapd)
 #ifdef CONFIG_IEEE80211R_AP
 	l2_packet_deinit(hapd->l2);
 	hapd->l2 = NULL;
+	eth_p_oui_unregister(hapd->oui_ft_pull);
+	hapd->oui_ft_pull = NULL;
+	eth_p_oui_unregister(hapd->oui_ft_resp);
+	hapd->oui_ft_resp = NULL;
+	eth_p_oui_unregister(hapd->oui_ft_push);
+	hapd->oui_ft_push = NULL;
 #endif /* CONFIG_IEEE80211R_AP */
 }
diff --git a/src/utils/common.h b/src/utils/common.h
index 7785677..b85c0fa 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -331,6 +331,9 @@  static inline void WPA_PUT_LE64(u8 *a, u64 val)
 #ifndef ETH_P_RRB
 #define ETH_P_RRB 0x890D
 #endif /* ETH_P_RRB */
+#ifndef ETH_P_OUI
+#define ETH_P_OUI 0x88B7
+#endif /* ETH_P_OUI */
 
 
 #ifdef __GNUC__
-- 
1.9.1