diff --git a/Documentation/networking/tcp_authopt.rst b/Documentation/networking/tcp_authopt.rst
index d0191d0c6c02..5631750cc3f7 100644
--- a/Documentation/networking/tcp_authopt.rst
+++ b/Documentation/networking/tcp_authopt.rst
@@ -44,10 +44,42 @@ new flags.
 
 RFC5925 requires that key ids do not overlap when tcp identifiers (addr/port)
 overlap. This is not enforced by linux, configuring ambiguous keys will result
 in packet drops and lost connections.
 
+Key selection
+-------------
+
+On getsockopt(TCP_AUTHOPT) information is provided about keyid/rnextkeyid in
+the last send packet and about the keyid/rnextkeyd in the last valid received
+packet.
+
+By default the sending keyid is selected to match the rnextkeyid value sent by
+the remote side, visible as recv_rnextkeyid in getsockopt. If that keyid is not
+available then the valid key with the longest send validity time is used, and
+otherwise ties are broken by preferring lowest numeric send_id.
+
+If the ``TCP_AUTHOPT_LOCK_KEYID`` flag is set then the sending key is selected
+by the `tcp_authopt.send_local_id` field and recv_rnextkeyid is ignored. If no
+key with local_id == send_local_id is valid then the same default is used
+as for missing recv_rnextkeyid.
+
+The rnextkeyid value sent on the wire is the recv_id of the valid key with the
+longest recv validity time, and otherwise ties are broken by preferring lowest
+numeric recv_id.
+
+If the TCP_AUTHOPT_LOCK_RNEXTKEY flag is set in `tcp_authopt.flags` the value of
+`tcp_authopt.send_rnextkeyid` is sent instead.
+
+The default key selection behavior is designed to implement key rollover in a
+way that is compatible with existing vendors without needing userspace key
+management. It also tries to behave predictably in all scenarios therefore it
+breaks ties by numeric IDs.
+
+A userspace daemon can use the "lock" flags to implement different key
+management and key rotation policies.
+
 ABI Reference
 =============
 
 .. kernel-doc:: include/uapi/linux/tcp.h
    :identifiers: tcp_authopt tcp_authopt_flag tcp_authopt_key tcp_authopt_key_flag tcp_authopt_alg
diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h
index 6ef893e75ee4..759b6d71fe86 100644
--- a/include/net/tcp_authopt.h
+++ b/include/net/tcp_authopt.h
@@ -83,10 +83,42 @@ struct tcp_authopt_info {
 	u32 dst_isn;
 	/** @rcv_sne: Recv-side Sequence Number Extension tracking tcp_sock.rcv_nxt */
 	u32 rcv_sne;
 	/** @snd_sne: Send-side Sequence Number Extension tracking tcp_sock.snd_nxt */
 	u32 snd_sne;
+
+	/**
+	 * @send_keyid: keyid currently being sent
+	 *
+	 * This is controlled by userspace by userspace if
+	 * TCP_AUTHOPT_FLAG_LOCK_KEYID, otherwise we try to match recv_rnextkeyid.
+	 *
+	 * This is the "currently effective" value from the last packet.
+	 */
+	u8 send_keyid;
+	/**
+	 * @user_pref_send_keyid: Preferred keyid requested by userspace
+	 */
+	u8 user_pref_send_keyid;
+	/**
+	 * @send_rnextkeyid: rnextkeyid currently being sent
+	 *
+	 * This is controlled by userspace if TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID is set
+	 */
+	u8 send_rnextkeyid;
+	/**
+	 * @recv_keyid: last keyid received from remote
+	 *
+	 * This is reported to userspace but has no other special behavior attached.
+	 */
+	u8 recv_keyid;
+	/**
+	 * @recv_rnextkeyid: last rnextkeyid received from remote
+	 *
+	 * Linux tries to honor this unless TCP_AUTHOPT_FLAG_LOCK_KEYID is set
+	 */
+	u8 recv_rnextkeyid;
 };
 
 /* TCP authopt as found in header */
 struct tcphdr_authopt {
 	u8 num;
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 52e6293048f5..4c3b1aef9976 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -346,10 +346,24 @@ struct tcp_diag_md5sig {
 
 /**
  * enum tcp_authopt_flag - flags for `tcp_authopt.flags`
  */
 enum tcp_authopt_flag {
+	/**
+	 * @TCP_AUTHOPT_FLAG_LOCK_KEYID: keyid controlled by sockopt
+	 *
+	 * If this is set `tcp_authopt.send_keyid` is used to determined sending
+	 * key. Otherwise a key with send_id == recv_rnextkeyid is preferred.
+	 */
+	TCP_AUTHOPT_FLAG_LOCK_KEYID = (1 << 0),
+	/**
+	 * @TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID: Override rnextkeyid from userspace
+	 *
+	 * If this is set then `tcp_authopt.send_rnextkeyid` is sent on outbound
+	 * packets. Other the recv_id of the current sending key is sent.
+	 */
+	TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID = (1 << 1),
 	/**
 	 * @TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED:
 	 *	Configure behavior of segments with TCP-AO coming from hosts for which no
 	 *	key is configured. The default recommended by RFC is to silently accept
 	 *	such connections.
@@ -361,10 +375,27 @@ enum tcp_authopt_flag {
  * struct tcp_authopt - Per-socket options related to TCP Authentication Option
  */
 struct tcp_authopt {
 	/** @flags: Combination of &enum tcp_authopt_flag */
 	__u32	flags;
+	/**
+	 * @send_keyid: `tcp_authopt_key.send_id` of preferred send key
+	 *
+	 * This is only used if `TCP_AUTHOPT_FLAG_LOCK_KEYID` is set.
+	 */
+	__u8	send_keyid;
+	/**
+	 * @send_rnextkeyid: The rnextkeyid to send in packets
+	 *
+	 * This is controlled by the user iff TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID is
+	 * set. Otherwise rnextkeyid is the recv_id of the current key.
+	 */
+	__u8	send_rnextkeyid;
+	/** @recv_keyid: A recently-received keyid value. Only for getsockopt. */
+	__u8	recv_keyid;
+	/** @recv_rnextkeyid: A recently-received rnextkeyid value. Only for getsockopt. */
+	__u8	recv_rnextkeyid;
 };
 
 /**
  * enum tcp_authopt_key_flag - flags for `tcp_authopt.flags`
  *
diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c
index 2bb7b2356e50..6db06e1edcc7 100644
--- a/net/ipv4/tcp_authopt.c
+++ b/net/ipv4/tcp_authopt.c
@@ -392,24 +392,85 @@ static bool better_key_match(struct tcp_authopt_key_info *old, struct tcp_authop
 		return true;
 
 	return false;
 }
 
+static int better_key_match_for_send(struct tcp_authopt_key_info *old,
+				     struct tcp_authopt_key_info *new)
+{
+	if (better_key_match(old, new))
+		return 1;
+
+	/* For keys with expiration dates prefer the one with longest lifetime */
+	if ((new->flags & TCP_AUTHOPT_KEY_SEND_LIFETIME_END) != 0 &&
+	    (old->flags & TCP_AUTHOPT_KEY_SEND_LIFETIME_END) == 0)
+		return -1;
+	if ((new->flags & TCP_AUTHOPT_KEY_SEND_LIFETIME_END) == 0 &&
+	    (old->flags & TCP_AUTHOPT_KEY_SEND_LIFETIME_END) != 0)
+		return 1;
+	if (old->flags & TCP_AUTHOPT_KEY_SEND_LIFETIME_END &&
+	    new->flags & TCP_AUTHOPT_KEY_SEND_LIFETIME_END) {
+		if (new->send_lifetime_end > old->send_lifetime_end)
+			return 1;
+		if (new->send_lifetime_end < old->send_lifetime_end)
+			return -1;
+	}
+
+	if (new->send_id != old->send_id)
+		return !!(old->send_id - new->send_id);
+
+	return 0;
+}
+
+static int better_rnextkey(struct tcp_authopt_key_info *old, struct tcp_authopt_key_info *new)
+{
+	if (better_key_match(old, new))
+		return 1;
+
+	/* For keys with expiration dates prefer the one with longest lifetime */
+	if ((new->flags & TCP_AUTHOPT_KEY_RECV_LIFETIME_END) != 0 &&
+	    (old->flags & TCP_AUTHOPT_KEY_RECV_LIFETIME_END) == 0)
+		return -1;
+	if ((new->flags & TCP_AUTHOPT_KEY_RECV_LIFETIME_END) == 0 &&
+	    (old->flags & TCP_AUTHOPT_KEY_RECV_LIFETIME_END) != 0)
+		return 1;
+	if (old->flags & TCP_AUTHOPT_KEY_RECV_LIFETIME_END &&
+	    new->flags & TCP_AUTHOPT_KEY_RECV_LIFETIME_END) {
+		if (new->recv_lifetime_end > old->recv_lifetime_end)
+			return 1;
+		if (new->recv_lifetime_end < old->recv_lifetime_end)
+			return -1;
+	}
+
+	/* Break ties by numeric ID */
+	if (new->recv_id != old->recv_id)
+		return !!(old->recv_id - new->recv_id);
+
+	return 0;
+}
+
 /**
  * tcp_authopt_lookup_send - lookup key for sending
  *
  * @net: Per-namespace information containing keys
  * @addr_sk: Socket used for destination address lookup
+ * @pref_send_id: Preferred send_id. If >= 0 then prefer keys that match
+ * @rnextkeyid: Output pointer to preferred rnextkeyid
+ * @anykey: Set to true if any keys are present for the peer
  *
  * If anykey is false then authentication is not required for peer.
  *
  * If anykey is true but no key was found then all our keys must be expired and sending should fail.
  */
 static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_authopt *net,
-							    const struct sock *addr_sk)
+							    const struct sock *addr_sk,
+							    int pref_send_id,
+							    u8 *rnextkeyid,
+							    bool *anykey)
 {
 	struct tcp_authopt_key_info *result = NULL;
+	struct tcp_authopt_key_info *rnext_result = NULL;
 	struct tcp_authopt_key_info *key;
 	time64_t now = ktime_get_real_seconds();
 	int l3index = -1;
 
 	hlist_for_each_entry_rcu(key, &net->head, node, 0) {
@@ -421,16 +482,35 @@ static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_aut
 				l3index = l3mdev_master_ifindex_by_index(sock_net(addr_sk),
 									 addr_sk->sk_bound_dev_if);
 			if (l3index != key->l3index)
 				continue;
 		}
-		if (!key_valid_for_send(key, now))
-			continue;
-		if (better_key_match(result, key))
-			result = key;
-		else if (result)
-			net_warn_ratelimited("ambiguous tcp authentication keys configured for send\n");
+		if (anykey)
+			*anykey = true;
+
+		if (rnextkeyid &&
+		    key_valid_for_recv(key, now) &&
+		    better_rnextkey(rnext_result, key) > 0)
+			rnext_result = key;
+
+		if (key_valid_for_send(key, now)) {
+			if (pref_send_id >= 0 && result &&
+			    key->send_id != pref_send_id &&
+			    result->send_id == pref_send_id)
+				continue;
+			if (better_key_match_for_send(result, key) > 0)
+				result = key;
+			else if (result)
+				net_warn_ratelimited("ambiguous tcp authentication keys configured for send\n");
+		}
+	}
+
+	if (rnextkeyid) {
+		if (rnext_result)
+			*rnextkeyid = rnext_result->recv_id;
+		else
+			*rnextkeyid = 0;
 	}
 
 	return result;
 }
 
@@ -442,19 +522,59 @@ static struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct netns_tcp_aut
  * @addr_sk: socket used for address lookup. Same as sk except for synack case
  * @rnextkeyid: value of rnextkeyid caller should write in packet
  *
  * Result is protected by RCU and can't be stored, it may only be passed to
  * tcp_authopt_hash and only under a single rcu_read_lock.
+ *
+ * Returns NULL if no key is required or ERR_PTR(-ENOKEY) if key is required but
+ * none is currently valid.
  */
 struct tcp_authopt_key_info *__tcp_authopt_select_key(const struct sock *sk,
 						      struct tcp_authopt_info *info,
 						      const struct sock *addr_sk,
 						      u8 *rnextkeyid)
 {
+	struct tcp_authopt_key_info *key;
 	struct netns_tcp_authopt *net = sock_net_tcp_authopt(sk);
+	bool anykey = false;
+	int pref_send_id;
+
+	/* Listen sockets don't refer to any specific connection so we don't try
+	 * to keep using the same key and ignore any received keyids.
+	 */
+	if (sk->sk_state == TCP_LISTEN) {
+		if (info->flags & TCP_AUTHOPT_FLAG_LOCK_KEYID)
+			pref_send_id = info->user_pref_send_keyid;
+		else
+			pref_send_id = -1;
+		key = tcp_authopt_lookup_send(net, addr_sk, pref_send_id, rnextkeyid, &anykey);
+
+		return key;
+	}
+
+	/* Try to keep the same sending key unless user or peer requires a different key
+	 * User request (via TCP_AUTHOPT_FLAG_LOCK_KEYID) always overrides peer request.
+	 */
+	if (info->flags & TCP_AUTHOPT_FLAG_LOCK_KEYID)
+		pref_send_id = info->user_pref_send_keyid;
+	else
+		pref_send_id = info->recv_rnextkeyid;
 
-	return tcp_authopt_lookup_send(net, addr_sk);
+	key = tcp_authopt_lookup_send(net, addr_sk, pref_send_id, rnextkeyid, &anykey);
+
+	if (!key)
+		return NULL;
+
+	info->send_keyid = key->send_id;
+	if (rnextkeyid) {
+		if (info->flags & TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID)
+			*rnextkeyid = info->send_rnextkeyid;
+		else
+			info->send_rnextkeyid = *rnextkeyid;
+	}
+
+	return key;
 }
 EXPORT_SYMBOL(__tcp_authopt_select_key);
 
 static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk)
 {
@@ -476,10 +596,12 @@ static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk
 
 	return info;
 }
 
 #define TCP_AUTHOPT_KNOWN_FLAGS ( \
+	TCP_AUTHOPT_FLAG_LOCK_KEYID | \
+	TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID | \
 	TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED)
 
 /* Like copy_from_sockptr except tolerate different optlen for compatibility reasons
  *
  * If the src is shorter then it's from an old userspace and the rest of dst is
@@ -547,10 +669,14 @@ int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
 	info = __tcp_authopt_info_get_or_create(sk);
 	if (IS_ERR(info))
 		return PTR_ERR(info);
 
 	info->flags = opt.flags & TCP_AUTHOPT_KNOWN_FLAGS;
+	if (opt.flags & TCP_AUTHOPT_FLAG_LOCK_KEYID)
+		info->user_pref_send_keyid = opt.send_keyid;
+	if (opt.flags & TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID)
+		info->send_rnextkeyid = opt.send_rnextkeyid;
 
 	return 0;
 }
 
 int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
@@ -568,10 +694,18 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
 	info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk));
 	if (!info)
 		return -ENOENT;
 
 	opt->flags = info->flags & TCP_AUTHOPT_KNOWN_FLAGS;
+	/* These keyids might be undefined, for example before connect.
+	 * Reporting zero is not strictly correct because there are no reserved
+	 * values.
+	 */
+	opt->send_keyid = info->send_keyid;
+	opt->send_rnextkeyid = info->send_rnextkeyid;
+	opt->recv_keyid = info->recv_keyid;
+	opt->recv_rnextkeyid = info->recv_rnextkeyid;
 
 	return 0;
 }
 
 #define TCP_AUTHOPT_KEY_KNOWN_FLAGS ( \
@@ -1571,10 +1705,25 @@ static void print_tcpao_notice(const char *msg, struct sk_buff *skb)
 	} else {
 		WARN_ONCE(1, "%s unknown IP version\n", msg);
 	}
 }
 
+static void save_inbound_key_info(
+		struct tcp_authopt_info *info,
+		struct tcphdr_authopt *opt)
+{
+	/* Doing this for all valid packets will results in keyids temporarily
+	 * flipping back and forth if packets are reordered or retransmitted
+	 * but keys should eventually stabilize.
+	 *
+	 * This is connection-specific so don't store for listen sockets.
+	 *
+	 */
+	info->recv_keyid = opt->keyid;
+	info->recv_rnextkeyid = opt->rnextkeyid;
+}
+
 /**
  * __tcp_authopt_inbound_check - Check inbound TCP authentication option
  *
  * @sk: Receive socket. For the SYN_RECV state this must be the request_sock, not the listener
  * @skb: Input Packet
@@ -1617,10 +1766,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb,
 			NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE);
 			print_tcpao_notice("TCP Authentication Unexpected: Rejected", skb);
 			return -SKB_DROP_REASON_TCP_AOUNEXPECTED;
 		}
 		print_tcpao_notice("TCP Authentication Unexpected: Accepted", skb);
+		save_inbound_key_info(info, opt);
 		return 0;
 	}
 	if (opt && !key) {
 		/* Keys are configured for peer but with different keyid than packet */
 		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE);
@@ -1640,10 +1790,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb,
 		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE);
 		print_tcpao_notice("TCP Authentication Failed", skb);
 		return -SKB_DROP_REASON_TCP_AOFAILURE;
 	}
 
+	save_inbound_key_info(info, opt);
 	return 1;
 }
 EXPORT_SYMBOL(__tcp_authopt_inbound_check);
 
 static int tcp_authopt_init_net(struct net *full_net)
