[PATCHv6,3/5] FT RRB: add msg replay and msg delay protection

Submitted by michael-dev@fami-braun.de on April 2, 2017, 12:52 p.m.

Details

Message ID 1491137573-643-4-git-send-email-michael-dev@fami-braun.de
State Superseded
Headers show

Commit Message

michael-dev@fami-braun.de April 2, 2017, 12:52 p.m.
This adds a counter and adds sequence numbering to FT RRB packets. The
sequence number is checked against r0kh/r1kh sequence number cache.

Special attention is needed in case the remote AP reboots and thus loses
its state. I prefer it to recover automatically even without synchronized
clocks.  Therefore an identifier called dom is generated randomly along the
initial sequence number. If the dom transmitted does not match or the
sequence number is not in the range currently expected, the sender is asked
for a fresh confirmation of its currently used sequence numbers. The packet
that triggered this is cached and processed again later.

Additionally, in order to ensure freshness, the remote ap includes an
timestamp with its messages. It is then verified that the received
messages are indeed fresh by comparing it to the older timestamps received
and the time elapsed since then.
Therefore FT_RRB_TIMESTAMP is no longer needed.

This breaks backward compatibility.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
--
2017-04-02:
 - rkh_seq is guaranteed to be set in rx_seq_resp
---
 src/ap/ap_config.c        |   2 +
 src/ap/wpa_auth.c         |   1 +
 src/ap/wpa_auth.h         |  28 +-
 src/ap/wpa_auth_ft.c      | 756 ++++++++++++++++++++++++++++++++++++++++++----
 src/ap/wpa_auth_glue.c    |  20 ++
 src/ap/wpa_auth_i.h       |  30 ++
 tests/hwsim/test_ap_ft.py |   4 +-
 7 files changed, 775 insertions(+), 66 deletions(-)

Comments

Jouni Malinen April 17, 2017, 10:12 a.m.
On Sun, Apr 02, 2017 at 02:52:51PM +0200, Michael Braun wrote:
> This adds a counter and adds sequence numbering to FT RRB packets. The
> sequence number is checked against r0kh/r1kh sequence number cache.
> 
> Special attention is needed in case the remote AP reboots and thus loses
> its state. I prefer it to recover automatically even without synchronized
> clocks.  Therefore an identifier called dom is generated randomly along the
> initial sequence number. If the dom transmitted does not match or the
> sequence number is not in the range currently expected, the sender is asked
> for a fresh confirmation of its currently used sequence numbers. The packet
> that triggered this is cached and processed again later.

This seems to be breaking a number of hwsim test cases. For example,
ap_ft_sae fails every time when run on its own. When run after some
other FT test cases, it can pass, but that is not really good behavior,
i.e., every single case should work.

Something seems to be going wrong with sequence number updating:

FT: Received push
FT: sequence number - hexdump(len=12): 11 22 b1 de 61 22 3f bc 03 00 00 00
FT: Possibly invalid sequence number in push from 02:00:00:00:03:00
FT: RRB-OUI type 4 send to 02:00:00:00:03:00

FT: Received sequence number request
FT: seq request - nonce - hexdump(len=16): 38 87 10 d5 a9 38 e2 1b 6a a1 ee bf fb 78 49 06
FT: RRB-OUI type 5 send to 02:00:00:00:04:00

FT: Received sequence number response
FT: sequence number - hexdump(len=12): 11 22 b1 de 62 22 3f bc 03 00 00 00
FT: seq response - reset seq number
FT: Received push
FT: sequence number - hexdump(len=12): 11 22 b1 de 61 22 3f bc 03 00 00 00
FT: Invalid sequence number in push from 02:00:00:00:03:00


It looks like the sequence number does indeed get reset, but the first
message that needed this gets dropped completely and there is no
automatic recovery from that. This makes the first FT protocol exchange
fail. Was this by design? Or was there supposed to be some kind of
mechanism to allow this first frame be accepted after sequence number
reset?


I was going through the remaining patches in this series and did some
cleanup while reviewing them. The current snapshot is here:
https://w1.fi/p/ft-rrb/

However, I could not proceed any further due to these hwsim test case
failures from this patch 3/5. And I cannot apply the first two patches
either on their own since I want to get all the
backwards-compatibility-breaking patches in at the same time.
michael-dev@fami-braun.de April 17, 2017, 2:41 p.m.
Am 17.04.2017 12:12, schrieb Jouni Malinen:
> This seems to be breaking a number of hwsim test cases. For example,
> ap_ft_sae fails every time when run on its own. When run after some
> other FT test cases, it can pass, but that is not really good behavior,
> i.e., every single case should work.

I've tested my series on top of 2971da2 and it passes ap_ft_sae for me 
also when that test is run alone.
Same goes for the series you send (https://w1.fi/p/ft-rrb/).
Do you use a config file different from example-hostapd.conf?

> Something seems to be going wrong with sequence number updating:

> FT: Received push

the original message

> FT: Received sequence number request
> FT: Received sequence number response

The remote AP send
  dom = 11 22 b1 de
  seq = 62 22 3f bc
  ts  = 03 00 00 00
when resetting the sequence number.

So seq in range [ seq - 16 - 1; seq ) + (seq, ...) should be accepted 
then, see wpa_ft_rrb_rx_seq_resp.

> FT: Received push

The original message got restarted automatically.
Its sequence number contains
  dom = 11 22 b1 de
  seq = 61 22 3f bc
  ts = 03 00 00 00

So dom matches and ts cannot fail as well.
The sequence number is in the range configured to be accepted, so this 
is fine as well.

When running ap_ft_sae alone on top of the series, I get:

FT: Received push
FT: sequence number - hexdump(len=12): f3 e2 0b 0e c3 e5 aa 47 ac cc 27 
00
FT: Possibly invalid sequence number in push from 02:00:00:00:03:00
FT: RRB-OUI type 4 send to 02:00:00:00:03:00

FT: RRB received packet 02:00:00:00:04:00 -> 02:00:00:00:03:00
FT: Received sequence number request
FT: RRB-OUI type 5 send to 02:00:00:00:04:00

FT: Received sequence number response
FT: seq response - nonce - hexdump(len=16): 9a 66 fb 33 2f 38 2f 6b 88 
e1 cf ef 66 67 52 5e
FT: sequence number - hexdump(len=12): f3 e2 0b 0e c4 e5 aa 47 ac cc 27 
00
FT: Invalid sequence number in seq response from 02:00:00:00:03:00
FT: seq response - reset seq number

FT: Received push
FT: R0KH-ID - hexdump(len=10): 6e 61 73 31 2e 77 31 2e 66 69
FT: R1KH-ID=00:01:02:03:04:06
FT: sequence number - hexdump(len=12): f3 e2 0b 0e c3 e5 aa 47 ac cc 27 
00
FT: S1KH-ID=02:00:00:00:00:00

So I'm puzzled.

Regards,
M. Braun

Patch hide | download patch | download mbox

diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 6b3d4e8..10e1c4b 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -523,6 +523,7 @@  void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 		while (r0kh) {
 			r0kh_prev = r0kh;
 			r0kh = r0kh->next;
+			os_free(r0kh_prev->seq);
 			os_free(r0kh_prev);
 		}
 
@@ -531,6 +532,7 @@  void hostapd_config_free_bss(struct hostapd_bss_config *conf)
 		while (r1kh) {
 			r1kh_prev = r1kh;
 			r1kh = r1kh->next;
+			os_free(r1kh_prev->seq);
 			os_free(r1kh_prev);
 		}
 	}
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index c91affc..338c6c7 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -518,6 +518,7 @@  void wpa_deinit(struct wpa_authenticator *wpa_auth)
 #ifdef CONFIG_IEEE80211R_AP
 	wpa_ft_pmk_cache_deinit(wpa_auth->ft_pmk_cache);
 	wpa_auth->ft_pmk_cache = NULL;
+	wpa_ft_deinit(wpa_auth);
 #endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_P2P
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 9bf5884..49c0480 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -13,6 +13,7 @@ 
 #include "common/eapol_common.h"
 #include "common/wpa_common.h"
 #include "common/ieee802_11_defs.h"
+#include "utils/list.h"
 
 #define MAX_OWN_IE_OVERRIDE 256
 
@@ -43,6 +44,8 @@  struct ft_rrb_frame {
 #define FT_PACKET_R0KH_R1KH_PULL 0x01
 #define FT_PACKET_R0KH_R1KH_RESP 0x02
 #define FT_PACKET_R0KH_R1KH_PUSH 0x03
+#define FT_PACKET_R0KH_R1KH_SEQ_REQ 0x04
+#define FT_PACKET_R0KH_R1KH_SEQ_RESP 0x05
 
 /* packet layout
  *  IEEE 802 extended OUI ethertype frame header
@@ -57,6 +60,7 @@  struct ft_rrb_frame {
 
 #define FT_RRB_LAST_EMPTY     0 /* placeholder or padding */
 
+#define FT_RRB_SEQ            1 /* struct ft_rrb_seq */
 #define FT_RRB_NONCE          2 /* size FT_RRB_NONCE_LEN */
 #define FT_RRB_TIMESTAMP      3 /* le32 unix seconds */
 
@@ -77,26 +81,40 @@  struct ft_rrb_tlv {
 	/* followed by data of length len */
 } STRUCT_PACKED;
 
+struct ft_rrb_seq {
+	le32 dom;
+	le32 seq;
+	le32 ts;
+} STRUCT_PACKED;
+
 /* session TLVs:
  *   required: PMK_R1, PMK_R1_NAME, PAIRWISE
  *
  * pull frame TLVs:
  *   auth:
- *     required: NONCE, R0KH_ID, R1KH_ID
+ *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
  *   encrypted:
  *     required: PMK_R0_NAME, S1KH_ID
  *
  * response frame TLVs:
  *   auth:
- *     required: NONCE, R0KH_ID, R1KH_ID
+ *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
  *   encrypted:
  *     required: S1KH_ID, session TLVs
  *
  * push frame TLVs:
  *   auth:
- *     required: TIMESTAMP, R0KH_ID, R1KH_ID
+ *     required: SEQ, R0KH_ID, R1KH_ID
  *   encrypted:
  *     required: S1KH_ID, PMK_R0_NAME, session TLVs
+ *
+ * sequence number request frame TLVs:
+ *   auth:
+ *     required: R0KH_ID, R1KH_ID, NONCE
+ *
+ * sequence number response frame TLVs:
+ *   auth:
+ *     required: SEQ, NONCE, R0KH_ID, R1KH_ID
  */
 
 #ifdef _MSC_VER
@@ -110,6 +128,7 @@  struct wpa_authenticator;
 struct wpa_state_machine;
 struct rsn_pmksa_cache_entry;
 struct eapol_state_machine;
+struct ft_remote_seq;
 
 
 struct ft_remote_r0kh {
@@ -118,6 +137,7 @@  struct ft_remote_r0kh {
 	u8 id[FT_R0KH_ID_MAX_LEN];
 	size_t id_len;
 	u8 key[32];
+	struct ft_remote_seq *seq;
 };
 
 
@@ -126,6 +146,7 @@  struct ft_remote_r1kh {
 	u8 addr[ETH_ALEN];
 	u8 id[FT_R1KH_ID_LEN];
 	u8 key[32];
+	struct ft_remote_seq *seq;
 };
 
 
@@ -345,6 +366,7 @@  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);
+void wpa_ft_deinit(struct wpa_authenticator *wpa_auth);
 #endif /* CONFIG_IEEE80211R_AP */
 
 void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm);
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index f5a8d83..ba380ab 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -27,11 +27,14 @@ 
 #ifdef CONFIG_IEEE80211R_AP
 
 static const char *ft_rrb_ad = "hostapd FT RRB";
+const unsigned int ftRRBseqTimeout = 10;
+const unsigned int ftRRBmaxQueueLen = 100;
 
 static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm,
 				     const u8 *current_ap, const u8 *sta_addr,
 				     u16 status, const u8 *resp_ies,
 				     size_t resp_ies_len);
+static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx);
 
 struct tlv_list {
 	int type;
@@ -536,6 +539,293 @@  int wpa_write_ftie(struct wpa_auth_config *conf, const u8 *r0kh_id,
 }
 
 
+/* a packet to be handled after seq response */
+struct ft_remote_item {
+	struct dl_list list;
+
+	u8 nonce[FT_RRB_NONCE_LEN];
+	struct os_reltime nonce_ts;
+
+	u8 src_addr[ETH_ALEN];
+	u8 *enc;
+	size_t enc_len;
+	u8 *auth;
+	size_t auth_len;
+	int (*cb)(struct wpa_authenticator *wpa_auth,
+		  const u8 *src_addr,
+		  const u8 *enc, size_t enc_len,
+		  const u8 *auth, size_t auth_len,
+		  int noDefer);
+};
+
+static void wpa_ft_rrb_seq_free(struct ft_remote_item *item)
+{
+	eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, ELOOP_ALL_CTX, item);
+	dl_list_del(&item->list);
+	os_free(item->enc);
+	os_free(item->auth);
+	os_free(item);
+}
+
+static void wpa_ft_rrb_seq_flush(struct wpa_authenticator *wpa_auth,
+				 struct ft_remote_seq *rkh_seq, int cb) {
+	struct ft_remote_item *item, *n;
+
+	dl_list_for_each_safe(item, n, &rkh_seq->rx.queue,
+			      struct ft_remote_item, list) {
+		if (cb && item->cb)
+			item->cb(wpa_auth, item->src_addr, item->enc,
+				 item->enc_len, item->auth, item->auth_len, 1);
+		wpa_ft_rrb_seq_free(item);
+	}
+}
+
+
+static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct ft_remote_item *item = timeout_ctx;
+
+	wpa_ft_rrb_seq_free(item);
+}
+
+
+static int
+wpa_ft_rrb_seq_req(struct wpa_authenticator *wpa_auth,
+		   struct ft_remote_seq *rkh_seq, const u8 *src_addr,
+		   const u8 *f_r0kh_id, size_t f_r0kh_id_len,
+		   const u8 *f_r1kh_id, const u8 *key, size_t key_len,
+		   const u8 *enc, size_t enc_len,
+		   const u8 *auth, size_t auth_len,
+		   int (*cb)(struct wpa_authenticator *wpa_auth,
+			     const u8 *src_addr,
+			     const u8 *enc, size_t enc_len,
+			     const u8 *auth, size_t auth_len,
+			     int noDefer))
+{
+	struct ft_remote_item *item = NULL;
+	u8 *packet = NULL;
+	size_t packet_len;
+
+	if (dl_list_len(&rkh_seq->rx.queue) >= ftRRBmaxQueueLen) {
+		wpa_printf(MSG_DEBUG, "FT: Sequence number queue too long");
+		goto err;
+	}
+
+	item = os_zalloc(sizeof(*item));
+	if (!item)
+		goto err;
+
+	dl_list_add(&rkh_seq->rx.queue, &item->list);
+	os_memcpy(item->src_addr, src_addr, ETH_ALEN);
+	item->cb = cb;
+
+	if (random_get_bytes(item->nonce, FT_RRB_NONCE_LEN) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: Seq num nonce: out of random bytes");
+		goto err;
+	}
+
+	if (os_get_reltime(&item->nonce_ts) < 0)
+		goto err;
+
+	if (enc && enc_len > 0) {
+		item->enc = os_memdup(enc, enc_len);
+		item->enc_len = enc_len;
+		if (!item->enc)
+			goto err;
+	}
+
+	if (auth && auth_len > 0) {
+		item->auth = os_memdup(auth, auth_len);
+		item->auth_len = auth_len;
+		if (!item->auth)
+			goto err;
+	}
+
+	eloop_register_timeout(ftRRBseqTimeout, 0, wpa_ft_rrb_seq_timeout,
+			       wpa_auth, item);
+
+	struct tlv_list seq_req_auth[] = {
+		{ .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN,
+		  .data = item->nonce },
+		{ .type = FT_RRB_R0KH_ID, .len = f_r0kh_id_len,
+		  .data = f_r0kh_id },
+		{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
+		  .data = f_r1kh_id },
+		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
+	};
+
+	if (wpa_ft_rrb_build(key, key_len, NULL, NULL, seq_req_auth,
+			     wpa_auth->addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
+			     &packet, &packet_len) < 0) {
+		item = NULL; /* some other seq resp might still accept this */
+		goto err;
+	}
+
+	wpa_ft_rrb_oui_send(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
+			    packet, packet_len);
+
+	os_free(packet);
+	packet = NULL;
+
+	return 0;
+err:
+	wpa_printf(MSG_DEBUG, "FT: Failed to send sequence number request");
+	if (item) {
+		os_free(item->auth);
+		os_free(item->enc);
+		os_free(item);
+	}
+
+	return -1;
+}
+
+#define FT_RRB_SEQ_OK    0
+#define FT_RRB_SEQ_DROP  1
+#define FT_RRB_SEQ_DEFER 2
+
+static int
+wpa_ft_rrb_seq_chk(struct ft_remote_seq *rkh_seq, const u8 *src_addr,
+		   const u8 *enc, size_t enc_len,
+		   const u8 *auth, size_t auth_len,
+		   const char *msgtype, int noDefer)
+{
+	const u8 *f_seq;
+	size_t f_seq_len;
+	struct ft_rrb_seq *msg_both;
+	u32 msg_dom, msg_seq, msg_off, rkh_off, msg_ts, msg_ts_now_remote;
+	struct os_reltime now, now_remote;
+	unsigned int i;
+
+	RRB_GET_AUTH(FT_RRB_SEQ, seq, msgtype, sizeof(*msg_both));
+	wpa_hexdump(MSG_DEBUG, "FT: sequence number", f_seq, f_seq_len);
+	msg_both = (struct ft_rrb_seq *) f_seq;
+
+	msg_dom = le_to_host32(msg_both->dom);
+	msg_seq = le_to_host32(msg_both->seq);
+	msg_ts = le_to_host32(msg_both->ts);
+
+	if (rkh_seq->rx.num_last == 0)
+		/* first packet from remote */
+		goto defer;
+
+	if (msg_dom != rkh_seq->rx.dom)
+		/* remote might have rebootet */
+		goto defer;
+
+	if (os_get_reltime(&now) == 0) {
+		os_reltime_sub(&now, &rkh_seq->rx.time_offset, &now_remote);
+		msg_ts_now_remote = now_remote.sec;
+		if (msg_ts > msg_ts_now_remote + ftRRBseqTimeout ||
+		    msg_ts < msg_ts_now_remote - ftRRBseqTimeout)
+			goto defer;
+	}
+
+	rkh_off = rkh_seq->rx.last[rkh_seq->rx.offsetidx];
+	msg_off = msg_seq - rkh_off;
+	if (msg_off > 0xC0000000)
+		goto out; /* too old message, drop it */
+
+	if (msg_off <= 0x40000000) {
+		for (i = 0; i < rkh_seq->rx.num_last; i++) {
+			if (rkh_seq->rx.last[i] == msg_seq)
+				goto out; /* duplicate message, drop it */
+		}
+
+		return FT_RRB_SEQ_OK;
+	}
+
+defer:
+	if (noDefer)
+		goto out;
+
+	wpa_printf(MSG_DEBUG, "FT: Possibly invalid sequence number in %s"
+		   " from " MACSTR, msgtype, MAC2STR(src_addr));
+
+	return FT_RRB_SEQ_DEFER;
+out:
+	wpa_printf(MSG_DEBUG, "FT: Invalid sequence number in %s"
+		   " from " MACSTR, msgtype, MAC2STR(src_addr));
+
+	return FT_RRB_SEQ_DROP;
+}
+
+
+static void
+wpa_ft_rrb_seq_accept(struct wpa_authenticator *wpa_auth,
+		      struct ft_remote_seq *rkh_seq, const u8 *src_addr,
+		      const u8 *auth, size_t auth_len,
+		      const char *msgtype)
+{
+	const u8 *f_seq;
+	size_t f_seq_len;
+	struct ft_rrb_seq *msg_both;
+	u32 msg_seq, msg_off, min_off, rkh_off;
+	int minidx = 0;
+	unsigned int i;
+
+	RRB_GET_AUTH(FT_RRB_SEQ, seq, msgtype, sizeof(*msg_both));
+	msg_both = (struct ft_rrb_seq *) f_seq;
+
+	msg_seq = le_to_host32(msg_both->seq);
+
+	if (rkh_seq->rx.num_last < FT_REMOTE_SEQ_BACKLOG) {
+		rkh_seq->rx.last[rkh_seq->rx.num_last] = msg_seq;
+		rkh_seq->rx.num_last++;
+		return;
+	}
+
+	rkh_off = rkh_seq->rx.last[rkh_seq->rx.offsetidx];
+	for (i = 0; i < rkh_seq->rx.num_last; i++) {
+		msg_off = rkh_seq->rx.last[i] - rkh_off;
+		min_off = rkh_seq->rx.last[minidx] - rkh_off;
+		if (msg_off < min_off && i != rkh_seq->rx.offsetidx)
+			minidx = i;
+	}
+	rkh_seq->rx.last[rkh_seq->rx.offsetidx] = msg_seq;
+	rkh_seq->rx.offsetidx = minidx;
+
+	return;
+out:
+	/* RRB_GET_AUTH should never fail here as
+	   wpa_ft_rrb_seq_chk verified FT_RRB_SEQ presence */
+
+	wpa_printf(MSG_ERROR, "FT: wpa_ft_rrb_seq_accept failed");
+}
+
+
+static int wpa_ft_new_seq(struct ft_remote_seq *rkh_seq,
+			  struct ft_rrb_seq *f_seq) {
+	struct os_reltime now;
+
+	if (os_get_reltime(&now) < 0)
+		return -1;
+
+	if (!rkh_seq->tx.dom) {
+		if (random_get_bytes((u8 *) &rkh_seq->tx.seq,
+				     sizeof(rkh_seq->tx.seq))) {
+			wpa_printf(MSG_ERROR, "FT: Failed to get random data "
+				   "for sequence number initialization.");
+			rkh_seq->tx.seq = now.usec;
+		}
+		if (random_get_bytes((u8 *) &rkh_seq->tx.dom,
+				     sizeof(rkh_seq->tx.dom))) {
+			wpa_printf(MSG_ERROR, "FT: Failed to get random data "
+				   "for sequence number initialization.");
+			rkh_seq->tx.dom = now.usec;
+		}
+		rkh_seq->tx.dom |= 1;
+	}
+
+	f_seq->dom = host_to_le32(rkh_seq->tx.dom);
+	f_seq->seq = host_to_le32(rkh_seq->tx.seq);
+	f_seq->ts  = host_to_le32(now.sec);
+
+	rkh_seq->tx.seq++;
+
+	return 0;
+}
+
+
 struct wpa_ft_pmk_r0_sa {
 	struct wpa_ft_pmk_r0_sa *next;
 	u8 pmk_r0[PMK_LEN];
@@ -694,50 +984,90 @@  static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth,
 }
 
 
+static int wpa_ft_rrb_init_r0kh_seq(struct ft_remote_r0kh *r0kh)
+{
+	if (r0kh->seq)
+		return 0;
+
+	r0kh->seq = os_zalloc(sizeof(*r0kh->seq));
+
+	if (!r0kh->seq) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to allocate r0kh->seq");
+		return -1;
+	}
+
+	dl_list_init(&r0kh->seq->rx.queue);
+
+	return 0;
+}
+
+
 static void wpa_ft_rrb_lookup_r0kh(struct wpa_authenticator *wpa_auth,
-				   const u8 *src_addr, const u8 *f_r0kh_id,
-				   size_t f_r0kh_id_len,
+				   const u8 *f_r0kh_id, size_t f_r0kh_id_len,
 				   struct ft_remote_r0kh **r0kh_out)
 {
-	struct ft_remote_r0kh *r0kh;
+	struct ft_remote_r0kh *r0kh = NULL;
+
+	*r0kh_out = NULL;
 
 	r0kh = wpa_auth->conf.r0kh_list;
 	for (; r0kh; r0kh = r0kh->next) {
-		if (src_addr &&  os_memcmp(r0kh->addr, src_addr, ETH_ALEN))
-			continue;
-		if (f_r0kh_id && (r0kh->id_len != f_r0kh_id_len ||
-		    os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len)))
-			continue;
-		break;
+		if (f_r0kh_id && r0kh->id_len == f_r0kh_id_len &&
+		    os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len) == 0) {
+			*r0kh_out = r0kh;
+			break;
+		}
 	}
 
-	if (!r0kh)
+	if (!*r0kh_out)
 		wpa_printf(MSG_DEBUG, "FT: No matching R0KH found");
 
-	*r0kh_out = r0kh;
+	if (*r0kh_out && wpa_ft_rrb_init_r0kh_seq(*r0kh_out) < 0)
+		*r0kh_out = NULL;
+}
+
+
+static int wpa_ft_rrb_init_r1kh_seq(struct ft_remote_r1kh *r1kh)
+{
+	if (r1kh->seq)
+		return 0;
+
+	r1kh->seq = os_zalloc(sizeof(*r1kh->seq));
+
+	if (!r1kh->seq) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to allocate r1kh->seq");
+		return -1;
+	}
+
+	dl_list_init(&r1kh->seq->rx.queue);
+
+	return 0;
 }
 
 
 static void wpa_ft_rrb_lookup_r1kh(struct wpa_authenticator *wpa_auth,
-				   const u8 *src_addr, const u8 *f_r1kh_id,
+				   const u8 *f_r1kh_id,
 				   struct ft_remote_r1kh **r1kh_out)
+
 {
-	struct ft_remote_r1kh *r1kh;
+	struct ft_remote_r1kh *r1kh = NULL;
+
+	*r1kh_out = NULL;
 
 	r1kh = wpa_auth->conf.r1kh_list;
 	for (; r1kh; r1kh = r1kh->next) {
-		if (src_addr && os_memcmp(r1kh->addr, src_addr, ETH_ALEN))
-			continue;
 		if (f_r1kh_id &&
-		    os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN))
-			continue;
-		break;
+		    os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN) == 0) {
+			*r1kh_out = r1kh;
+			break;
+		}
 	}
 
-	if (!r1kh)
+	if (!*r1kh_out)
 		wpa_printf(MSG_DEBUG, "FT: No matching R1KH found");
 
-	*r1kh_out = r1kh;
+	if (*r1kh_out && wpa_ft_rrb_init_r1kh_seq(*r1kh_out) < 0)
+		*r1kh_out = NULL;
 }
 
 
@@ -764,34 +1094,77 @@  static int wpa_ft_rrb_check_r1kh(struct wpa_authenticator *wpa_auth,
 }
 
 
+void wpa_ft_deinit_seq_timeout(struct wpa_authenticator *wpa_auth)
+{
+	struct ft_remote_r0kh *r0kh;
+	struct ft_remote_r1kh *r1kh;
+
+	eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, wpa_auth, ELOOP_ALL_CTX);
+
+	r0kh = wpa_auth->conf.r0kh_list;
+	while (r0kh) {
+		if (r0kh->seq)
+			wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0);
+		r0kh = r0kh->next;
+	}
+
+	r1kh = wpa_auth->conf.r1kh_list;
+	while (r1kh) {
+		if (r1kh->seq)
+			wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0);
+		r1kh = r1kh->next;
+	}
+}
+
+
+void wpa_ft_deinit(struct wpa_authenticator *wpa_auth)
+{
+	wpa_ft_deinit_seq_timeout(wpa_auth);
+}
+
+
 static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 			      const u8 *ies, size_t ies_len,
 			      const u8 *pmk_r0_name)
 {
 	struct ft_remote_r0kh *r0kh;
 	u8 *packet = NULL;
-	const u8 *key;
+	const u8 *key, *f_r1kh_id = sm->wpa_auth->conf.r1_key_holder;
 	size_t packet_len, key_len;
+	struct ft_rrb_seq f_seq;
 
-	wpa_ft_rrb_lookup_r0kh(sm->wpa_auth, NULL, sm->r0kh_id, sm->r0kh_id_len,
+	wpa_ft_rrb_lookup_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len,
 			       &r0kh);
 	if (r0kh == NULL) {
 		wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
 			    sm->r0kh_id, sm->r0kh_id_len);
 		return -1;
 	}
+
 	key = r0kh->key;
 	key_len = sizeof(r0kh->key);
 
 	wpa_printf(MSG_DEBUG, "FT: Send pull request to remote R0KH address "
 		   MACSTR, MAC2STR(r0kh->addr));
 
+	if (r0kh->seq->rx.num_last == 0)
+		/* a sequence request will be sent out anyway when pull
+		 * response is received. Send it out now to avoid one RTT.*/
+		wpa_ft_rrb_seq_req(sm->wpa_auth, r0kh->seq, r0kh->addr,
+				   r0kh->id, r0kh->id_len, f_r1kh_id, key,
+				   key_len, NULL, 0, NULL, 0, NULL);
+
 	if (random_get_bytes(sm->ft_pending_pull_nonce, FT_RRB_NONCE_LEN) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to get random data for "
 			   "nonce");
 		return -1;
 	}
 
+	if (wpa_ft_new_seq(r0kh->seq, &f_seq) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
+		return -1;
+	}
+
 	struct tlv_list req_enc[] = {
 		{ .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN,
 		  .data = pmk_r0_name },
@@ -803,10 +1176,12 @@  static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
 	struct tlv_list req_auth[] = {
 		{ .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN,
 		  .data = sm->ft_pending_pull_nonce },
+		{ .type = FT_RRB_SEQ, .len = sizeof(f_seq),
+		  .data = (u8 *) &f_seq },
 		{ .type = FT_RRB_R0KH_ID, .len = sm->r0kh_id_len,
 		  .data = sm->r0kh_id },
 		{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
-		  .data = sm->wpa_auth->conf.r1_key_holder },
+		  .data = f_r1kh_id },
 		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
 	};
 
@@ -1914,7 +2289,8 @@  static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len,
 static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 			      const u8 *src_addr,
 			      const u8 *enc, size_t enc_len,
-			      const u8 *auth, size_t auth_len)
+			      const u8 *auth, size_t auth_len,
+			      int noDefer)
 {
 	const char *msgtype = "pull request";
 	const int type = FT_PACKET_R0KH_R1KH_PULL;
@@ -1923,11 +2299,13 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	struct ft_remote_r1kh *r1kh;
 	const u8 *key = NULL;
 	size_t key_len = 0;
+	int seq_ret;
 	const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id, *f_s1kh_id, *f_pmk_r0_name;
 	size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len, f_s1kh_id_len;
 	size_t f_pmk_r0_name_len;
 	const struct wpa_ft_pmk_r0_sa *r0;
 	int ret = -1;
+	struct ft_rrb_seq f_seq;
 
 	wpa_printf(MSG_DEBUG, "FT: Received %s from " MACSTR,
 		   msgtype, MAC2STR(src_addr));
@@ -1943,8 +2321,8 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
 	wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR, MAC2STR(f_r1kh_id));
 
-	wpa_ft_rrb_lookup_r1kh(wpa_auth, src_addr, f_r1kh_id, &r1kh);
-	if (r1kh == NULL)
+	wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh);
+	if (r1kh == NULL || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)
 		goto out;
 	key = r1kh->key;
 	key_len = sizeof(r1kh->key);
@@ -1952,10 +2330,26 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	RRB_GET_AUTH(FT_RRB_NONCE, nonce, "pull request", FT_RRB_NONCE_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len);
 
+	seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len, auth,
+				     auth_len, msgtype, noDefer);
+	if (seq_ret == FT_RRB_SEQ_DROP)
+		goto out;
+
 	if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len,
 			       src_addr, type, &plain, &plain_len) < 0)
 		goto out;
 
+	if (seq_ret == FT_RRB_SEQ_DEFER) {
+		wpa_ft_rrb_seq_req(wpa_auth, r1kh->seq, src_addr, f_r0kh_id,
+				   f_r0kh_id_len, f_r1kh_id, key, key_len,
+				   enc, enc_len, auth, auth_len,
+				   &wpa_ft_rrb_rx_pull);
+		goto out;
+	}
+
+	wpa_ft_rrb_seq_accept(wpa_auth, r1kh->seq, src_addr, auth, auth_len,
+			      msgtype);
+
 	RRB_GET(FT_RRB_PMK_R0_NAME, pmk_r0_name, msgtype, WPA_PMK_NAME_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", f_pmk_r0_name,
 		    f_pmk_r0_name_len);
@@ -1963,6 +2357,11 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN);
 	wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id));
 
+	if (wpa_ft_new_seq(r1kh->seq, &f_seq) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
+		goto out;
+	}
+
 	struct tlv_list resp[] = {
 		{ .type = FT_RRB_S1KH_ID, .len = f_s1kh_id_len,
 		  .data = f_s1kh_id },
@@ -1971,6 +2370,8 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	struct tlv_list resp_auth[] = {
 		{ .type = FT_RRB_NONCE, .len = f_nonce_len,
 		  .data = f_nonce },
+		{ .type = FT_RRB_SEQ, .len = sizeof(f_seq),
+		  .data = (u8 *) &f_seq },
 		{ .type = FT_RRB_R0KH_ID, .len = f_r0kh_id_len,
 		  .data = f_r0kh_id },
 		{ .type = FT_RRB_R1KH_ID, .len = f_r1kh_id_len,
@@ -2008,13 +2409,19 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 			    const u8 *src_addr, const u8 type,
 			    const u8 *enc, size_t enc_len,
 			    const u8 *auth, size_t auth_len,
-			    const char *msgtype, u8 *s1kh_id_out)
+			    const char *msgtype, u8 *s1kh_id_out,
+			    int (*cb)(struct wpa_authenticator *wpa_auth,
+				      const u8 *src_addr,
+				      const u8 *enc, size_t enc_len,
+				      const u8 *auth, size_t auth_len,
+				      int noDefer))
 {
 	u8 *plain = NULL;
 	size_t plain_len = 0;
 	struct ft_remote_r0kh *r0kh;
 	const u8 *key;
 	size_t key_len;
+	int seq_ret;
 	const u8 *f_r1kh_id, *f_s1kh_id, *f_r0kh_id;
 	const u8 *f_pmk_r1_name, *f_pairwise, *f_pmk_r1;
 	size_t f_r1kh_id_len, f_s1kh_id_len, f_r0kh_id_len;
@@ -2033,17 +2440,31 @@  static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 		goto out;
 	}
 
-	wpa_ft_rrb_lookup_r0kh(wpa_auth, src_addr, f_r0kh_id, f_r0kh_id_len,
-			       &r0kh);
-	if (r0kh == NULL)
+	wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, &r0kh);
+	if (r0kh == NULL || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)
 		goto out;
 	key = r0kh->key;
 	key_len = sizeof(r0kh->key);
 
+	seq_ret = wpa_ft_rrb_seq_chk(r0kh->seq, src_addr, enc, enc_len, auth,
+				     auth_len, msgtype, cb ? 0 : 1);
+	if (seq_ret == FT_RRB_SEQ_DROP)
+		goto out;
+
 	if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len,
 			       src_addr, type, &plain, &plain_len) < 0)
 		goto out;
 
+	if (seq_ret == FT_RRB_SEQ_DEFER) {
+		wpa_ft_rrb_seq_req(wpa_auth, r0kh->seq, src_addr, f_r0kh_id,
+				   f_r0kh_id_len, f_r1kh_id, key, key_len,
+				   enc, enc_len, auth, auth_len, cb);
+		goto out;
+	}
+
+	wpa_ft_rrb_seq_accept(wpa_auth, r0kh->seq, src_addr, auth, auth_len,
+			      msgtype);
+
 	RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN);
 	wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id));
 
@@ -2129,7 +2550,8 @@  static int ft_get_sta_cb(struct wpa_state_machine *sm, void *ctx)
 static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 			      const u8 *src_addr,
 			      const u8 *enc, size_t enc_len,
-			      const u8 *auth, size_t auth_len)
+			      const u8 *auth, size_t auth_len,
+			      int noDefer)
 {
 	const char *msgtype = "pull response";
 	const int type = FT_PACKET_R0KH_R1KH_RESP;
@@ -2153,7 +2575,8 @@  static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 	}
 
 	ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, type, enc, enc_len, auth,
-			       auth_len, msgtype, s1kh_id);
+			       auth_len, msgtype, s1kh_id,
+			       noDefer ? NULL : &wpa_ft_rrb_rx_resp);
 	if (ret < 0)
 		return -1;
 
@@ -2172,32 +2595,231 @@  out:
 static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth,
 			      const u8 *src_addr,
 			      const u8 *enc, size_t enc_len,
-			      const u8 *auth, size_t auth_len)
+			      const u8 *auth, size_t auth_len,
+			      int noDefer)
 {
 	const char *msgtype = "push";
 	const int type = FT_PACKET_R0KH_R1KH_PUSH;
-	struct os_time now;
-	struct os_time tsend;
-	const u8 *f_timestamp;
-	size_t f_timestamp_len;
 
 	wpa_printf(MSG_DEBUG, "FT: Received %s", msgtype);
 
-	RRB_GET_AUTH(FT_RRB_TIMESTAMP, timestamp, msgtype, sizeof(le32));
-	tsend.sec = WPA_GET_LE32(f_timestamp);
-	wpa_printf(MSG_DEBUG, "FT: timestamp=%ld", tsend.sec);
-	os_get_time(&now);
-	if ((now.sec > tsend.sec && now.sec - tsend.sec > 60) ||
-	    (now.sec < tsend.sec && tsend.sec - now.sec > 60)) {
-		wpa_printf(MSG_DEBUG, "FT: did not have a valid "
-			   "timestamp: sender time %ld own time %ld\n",
-			   tsend.sec, now.sec);
+	if (wpa_ft_rrb_rx_r1(wpa_auth, src_addr, type, enc, enc_len, auth,
+			     auth_len, msgtype, NULL,
+			     noDefer ? NULL : &wpa_ft_rrb_rx_push) < 0)
 		return -1;
+
+	return 0;
+}
+
+
+static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth,
+			     const u8 *src_addr, int type,
+			     const u8 *enc, size_t enc_len,
+			     const u8 *auth, size_t auth_len,
+			     struct ft_remote_seq **rkh_seq,
+			     u8 **key, size_t *key_len)
+{
+	struct ft_remote_r0kh *r0kh = NULL;
+	struct ft_remote_r1kh *r1kh = NULL;
+	const u8 *f_r0kh_id, *f_r1kh_id;
+	size_t f_r0kh_id_len, f_r1kh_id_len;
+	int to_r0kh, to_r1kh;
+	u8 *plain = NULL;
+	size_t plain_len = 0;
+
+	RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1);
+	RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN);
+
+	to_r0kh = !wpa_ft_rrb_check_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len);
+	to_r1kh = !wpa_ft_rrb_check_r1kh(wpa_auth, f_r1kh_id);
+
+	if (to_r0kh && to_r1kh) {
+		wpa_printf(MSG_DEBUG, "FT: seq - local R0KH-ID and R1KH-ID");
+		goto out;
 	}
 
-	if (wpa_ft_rrb_rx_r1(wpa_auth, src_addr, type, enc, enc_len, auth,
-			     auth_len, msgtype, NULL) < 0)
-		return -1;
+	if (!to_r0kh && !to_r1kh) {
+		wpa_printf(MSG_DEBUG, "FT: seq - remote R0KH-ID and R1KH-ID");
+		goto out;
+	}
+
+	if (!to_r0kh) {
+		wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
+				       &r0kh);
+		if (r0kh == NULL ||
+		    os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0) {
+			wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
+				    f_r0kh_id, f_r0kh_id_len);
+			goto out;
+		}
+
+		*key = r0kh->key;
+		*key_len = sizeof(r0kh->key);
+		*rkh_seq = r0kh->seq;
+	}
+
+	if (!to_r1kh) {
+		wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh);
+		if (r1kh == NULL ||
+		    os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0) {
+			wpa_hexdump(MSG_DEBUG, "FT: Did not find R1KH-ID",
+				    f_r1kh_id, FT_R1KH_ID_LEN);
+			goto out;
+		}
+
+		*key = r1kh->key;
+		*key_len = sizeof(r1kh->key);
+		*rkh_seq = r1kh->seq;
+	}
+
+	if (wpa_ft_rrb_decrypt(*key, *key_len, enc, enc_len, auth, auth_len,
+			       src_addr, type, &plain, &plain_len) < 0)
+		goto out;
+
+	os_free(plain); plain = NULL;
+
+	return 0;
+out:
+	return -1;
+}
+
+static int wpa_ft_rrb_rx_seq_req(struct wpa_authenticator *wpa_auth,
+				 const u8 *src_addr,
+				 const u8 *enc, size_t enc_len,
+				 const u8 *auth, size_t auth_len,
+				 int noDefer)
+{
+	int ret = -1;
+	struct ft_rrb_seq f_seq;
+	const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id;
+	size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len;
+	struct ft_remote_seq *rkh_seq;
+	u8 *packet = NULL, *key = NULL;
+	size_t packet_len = 0, key_len = 0;
+
+	wpa_printf(MSG_DEBUG, "FT: Received sequence number request");
+
+	if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ,
+			      enc, enc_len, auth, auth_len, &rkh_seq, &key,
+			      &key_len) < 0)
+		goto out;
+
+	RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq request", FT_RRB_NONCE_LEN);
+	wpa_hexdump(MSG_DEBUG, "FT: seq request - nonce", f_nonce, f_nonce_len);
+
+	RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1);
+	RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN);
+
+	if (wpa_ft_new_seq(rkh_seq, &f_seq) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
+		goto out;
+	}
+
+	struct tlv_list seq_resp_auth[] = {
+		{ .type = FT_RRB_NONCE, .len = f_nonce_len,
+		  .data = f_nonce },
+		{ .type = FT_RRB_SEQ, .len = sizeof(f_seq),
+		  .data = (u8 *) &f_seq },
+		{ .type = FT_RRB_R0KH_ID, .len = f_r0kh_id_len,
+		  .data = f_r0kh_id },
+		{ .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN,
+		  .data = f_r1kh_id },
+		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
+	};
+
+	if (wpa_ft_rrb_build(key, key_len, NULL, NULL, seq_resp_auth,
+			     wpa_auth->addr, FT_PACKET_R0KH_R1KH_SEQ_RESP,
+			     &packet, &packet_len) < 0)
+		goto out;
+
+	wpa_ft_rrb_oui_send(wpa_auth, src_addr,
+			    FT_PACKET_R0KH_R1KH_SEQ_RESP, packet,
+			    packet_len);
+
+out:
+	os_free(packet); packet = NULL;
+
+	return ret;
+}
+
+
+
+static int wpa_ft_rrb_rx_seq_resp(struct wpa_authenticator *wpa_auth,
+				  const u8 *src_addr,
+				  const u8 *enc, size_t enc_len,
+				  const u8 *auth, size_t auth_len,
+				  int noDefer)
+{
+	u8 *key = NULL;
+	size_t key_len = 0;
+	const u8 *f_nonce, *f_seq;
+	size_t f_nonce_len, f_seq_len;
+	struct ft_remote_seq *rkh_seq = NULL;
+	struct ft_remote_item *item = NULL;
+	struct os_reltime now, now_remote;
+	int seq_ret, found;
+	struct ft_rrb_seq *msg_both;
+	u32 msg_dom, msg_seq;
+
+	wpa_printf(MSG_DEBUG, "FT: Received sequence number response");
+
+	if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_RESP,
+			      enc, enc_len, auth, auth_len, &rkh_seq, &key,
+			      &key_len) < 0)
+		goto out;
+
+	RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq response", FT_RRB_NONCE_LEN);
+	wpa_hexdump(MSG_DEBUG, "FT: seq response - nonce", f_nonce,
+		    f_nonce_len);
+
+	found = 0;
+	dl_list_for_each(item, &rkh_seq->rx.queue, struct ft_remote_item,
+			 list) {
+		if (os_memcmp_const(f_nonce, item->nonce, FT_RRB_NONCE_LEN) ||
+		    os_get_reltime(&now) < 0 ||
+		    os_reltime_expired(&now, &item->nonce_ts, ftRRBseqTimeout))
+			continue;
+
+		found = 1;
+		break;
+	}
+	if (!found) {
+		wpa_printf(MSG_DEBUG, "FT: seq response - bad nonce");
+		goto out;
+	}
+
+	seq_ret = wpa_ft_rrb_seq_chk(rkh_seq, src_addr, enc, enc_len, auth,
+				     auth_len, "seq response", 1);
+	if (seq_ret == FT_RRB_SEQ_OK) {
+		wpa_printf(MSG_DEBUG, "FT: seq response - valid seq number");
+		wpa_ft_rrb_seq_accept(wpa_auth, rkh_seq, src_addr, auth,
+				      auth_len, "seq response");
+	} else {
+		wpa_printf(MSG_DEBUG, "FT: seq response - reset seq number");
+
+		RRB_GET_AUTH(FT_RRB_SEQ, seq, "seq response",
+			     sizeof(*msg_both));
+		msg_both = (struct ft_rrb_seq *) f_seq;
+
+		msg_dom = le_to_host32(msg_both->dom);
+		msg_seq = le_to_host32(msg_both->seq);
+		now_remote.sec = le_to_host32(msg_both->ts);
+		now_remote.usec = 0;
+
+		rkh_seq->rx.num_last = 2;
+		rkh_seq->rx.dom = msg_dom;
+		rkh_seq->rx.offsetidx = 0;
+		/* accept some older, possibly  cached packets as well */
+		rkh_seq->rx.last[0] = msg_seq - FT_REMOTE_SEQ_BACKLOG
+					   - dl_list_len(&rkh_seq->rx.queue);
+		rkh_seq->rx.last[1] = msg_seq;
+
+		/* local time - offset = remote time
+		 * <=> local time - remote time = offset */
+		os_reltime_sub(&now, &now_remote, &rkh_seq->rx.time_offset);
+	}
+
+	wpa_ft_rrb_seq_flush(wpa_auth, rkh_seq, 1);
 
 	return 0;
 out:
@@ -2368,13 +2990,24 @@  void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr,
 
 	switch (oui_suffix) {
 	case FT_PACKET_R0KH_R1KH_PULL:
-		wpa_ft_rrb_rx_pull(wpa_auth, src_addr, enc, elen, auth, alen);
+		wpa_ft_rrb_rx_pull(wpa_auth, src_addr, enc, elen, auth, alen,
+				   0);
 		break;
 	case FT_PACKET_R0KH_R1KH_RESP:
-		wpa_ft_rrb_rx_resp(wpa_auth, src_addr, enc, elen, auth, alen);
+		wpa_ft_rrb_rx_resp(wpa_auth, src_addr, enc, elen, auth, alen,
+				   0);
 		break;
 	case FT_PACKET_R0KH_R1KH_PUSH:
-		wpa_ft_rrb_rx_push(wpa_auth, src_addr, enc, elen, auth, alen);
+		wpa_ft_rrb_rx_push(wpa_auth, src_addr, enc, elen, auth, alen,
+				   0);
+		break;
+	case FT_PACKET_R0KH_R1KH_SEQ_REQ:
+		wpa_ft_rrb_rx_seq_req(wpa_auth, src_addr, enc, elen, auth, alen,
+				      0);
+		break;
+	case FT_PACKET_R0KH_R1KH_SEQ_RESP:
+		wpa_ft_rrb_rx_seq_resp(wpa_auth, src_addr, enc, elen, auth,
+				       alen, 0);
 		break;
 	}
 }
@@ -2385,13 +3018,14 @@  static int wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 				  struct ft_remote_r1kh *r1kh,
 				  const u8 *s1kh_id)
 {
-	struct os_time now;
 	u8 *packet;
 	size_t packet_len;
-	u8 f_timestamp[sizeof(le32)];
+	struct ft_rrb_seq f_seq;
 
-	os_get_time(&now);
-	WPA_PUT_LE32(f_timestamp, now.sec);
+	if (wpa_ft_new_seq(r1kh->seq, &f_seq) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to get seq num");
+		return -1;
+	}
 
 	struct tlv_list push[] = {
 		{ .type = FT_RRB_S1KH_ID, .len = ETH_ALEN,
@@ -2401,8 +3035,8 @@  static int wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 		{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
 	};
 	struct tlv_list push_auth[] = {
-		{ .type = FT_RRB_TIMESTAMP, .len = sizeof(f_timestamp),
-		  .data = f_timestamp },
+		{ .type = FT_RRB_SEQ, .len = sizeof(f_seq),
+		  .data = (u8 *) &f_seq },
 		{ .type = FT_RRB_R0KH_ID,
 		  .len = wpa_auth->conf.r0_key_holder_len,
 		  .data = wpa_auth->conf.r0_key_holder },
@@ -2448,10 +3082,10 @@  void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
 	wpa_printf(MSG_DEBUG, "FT: Deriving and pushing PMK-R1 keys to R1KHs "
 		   "for STA " MACSTR, MAC2STR(addr));
 
-	r1kh = wpa_auth->conf.r1kh_list;
-	while (r1kh) {
+	for (r1kh = wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) {
+		if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0)
+			continue;
 		wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr);
-		r1kh = r1kh->next;
 	}
 }
 
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index cce7b1f..ba964e6 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -577,6 +577,10 @@  static struct eth_p_oui_ctx *hostapd_wpa_get_oui(struct hostapd_data *hapd,
 		return hapd->oui_resp;
 	case FT_PACKET_R0KH_R1KH_PUSH:
 		return hapd->oui_push;
+	case FT_PACKET_R0KH_R1KH_SEQ_REQ:
+		return hapd->oui_sreq;
+	case FT_PACKET_R0KH_R1KH_SEQ_RESP:
+		return hapd->oui_sresp;
 #endif /* CONFIG_IEEE80211R_AP */
 	default:
 		return NULL;
@@ -829,6 +833,18 @@  static int hostapd_wpa_register_ft_oui(struct hostapd_data *hapd,
 	if (!hapd->oui_push)
 		return -1;
 
+	hapd->oui_sreq = eth_p_oui_register(hapd, ft_iface,
+					    FT_PACKET_R0KH_R1KH_SEQ_REQ,
+					    hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_sreq)
+		return -1;
+
+	hapd->oui_sresp = eth_p_oui_register(hapd, ft_iface,
+					     FT_PACKET_R0KH_R1KH_SEQ_RESP,
+					     hostapd_rrb_oui_receive, hapd);
+	if (!hapd->oui_sresp)
+		return -1;
+
 	return 0;
 }
 
@@ -841,6 +857,10 @@  static void hostapd_wpa_unregister_ft_oui(struct hostapd_data *hapd)
 	hapd->oui_resp = NULL;
 	eth_p_oui_unregister(hapd->oui_push);
 	hapd->oui_push = NULL;
+	eth_p_oui_unregister(hapd->oui_sreq);
+	hapd->oui_sreq = NULL;
+	eth_p_oui_unregister(hapd->oui_sresp);
+	hapd->oui_sresp = NULL;
 }
 #endif /* CONFIG_IEEE80211R_AP */
 
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 3279ad4..4a9c629 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -211,6 +211,36 @@  struct wpa_authenticator {
 };
 
 
+#ifdef CONFIG_IEEE80211R_AP
+#define FT_REMOTE_SEQ_BACKLOG 16
+struct ft_remote_seq_rx {
+	u32 dom;
+	struct os_reltime time_offset; /* local time - offset = remote time */
+
+	/* accepted sequence numbers: (offset ... offset + 0x40000000 ]
+	 *   ( except those in last )
+	 * dropped sequence numbers: (offset - 0x40000000 ... offset ]
+	 * all others trigger SEQ_REQ message ( except first message )
+	 */
+	u32 last[FT_REMOTE_SEQ_BACKLOG];
+	unsigned int num_last;
+	u32 offsetidx;
+
+	struct dl_list queue; /* send nonces + rrb msgs awaiting seq resp */
+};
+
+struct ft_remote_seq_tx {
+	u32 dom; /* non zero if initialized */
+	u32 seq;
+};
+
+struct ft_remote_seq {
+	struct ft_remote_seq_rx rx;
+	struct ft_remote_seq_tx tx;
+};
+#endif /* CONFIG_IEEE80211R_AP */
+
+
 int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 		     const u8 *pmkid);
 void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr,
diff --git a/tests/hwsim/test_ap_ft.py b/tests/hwsim/test_ap_ft.py
index 7764495..a5a5aef 100644
--- a/tests/hwsim/test_ap_ft.py
+++ b/tests/hwsim/test_ap_ft.py
@@ -1057,11 +1057,11 @@  def test_ap_ft_ap_oom3(dev, apdev):
         # This will fail due to not being able to send out PMK-R1 pull request
         dev[0].roam(bssid1)
 
-    with fail_test(hapd1, 1, "os_get_random;wpa_ft_pull_pmk_r1"):
+    with fail_test(hapd1, 2, "os_get_random;wpa_ft_pull_pmk_r1"):
         # This will fail due to not being able to send out PMK-R1 pull request
         dev[0].roam(bssid1)
 
-    with fail_test(hapd1, 1, "aes_siv_encrypt;wpa_ft_pull_pmk_r1"):
+    with fail_test(hapd1, 2, "aes_siv_encrypt;wpa_ft_pull_pmk_r1"):
         # This will fail due to not being able to send out PMK-R1 pull request
         dev[0].roam(bssid1)