diff mbox series

[11/31] net/tcp: Add TCP-AO sign to outgoing packets

Message ID 20220818170005.747015-12-dima@arista.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net/tcp: Add TCP-AO support | expand

Checks

Context Check Description
netdev/tree_selection success Guessed tree name to be net-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count fail Series longer than 15 patches (and no cover letter)
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 39085 this patch: 39085
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang fail Errors and warnings before: 134 this patch: 137
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 39803 this patch: 39806
netdev/checkpatch warning CHECK: Alignment should match open parenthesis WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Dmitry Safonov Aug. 18, 2022, 4:59 p.m. UTC
Using precalculated traffic keys, sign TCP segments as prescribed by
RFC5925. Per RFC, TCP header options are included in sign calculation:
"The TCP header, by default including options, and where the TCP
checksum and TCP-AO MAC fields are set to zero, all in network-
byte order." (5.1.3)

tcp_ao_hash_header() has exclude_options parameter to optionally exclude
TCP header from hash calculation, as described in RFC5925 (9.1), this is
needed for interaction with middleboxes that may change "some TCP
options". This is wired up to AO key flags and setsockopt() later.

Similarly to TCP-MD5 hash TCP segment fragments.

>From this moment a user can start sending TCP-AO signed segments with
one of crypto ahash algorithms from supported by Linux kernel. It can
have a user-specified MAC length, to either save TCP option header space
or provide higher protection using a longer signature.
The inbound segments are not yet verified, TCP-AO option is ignored and
they are accepted.

Co-developed-by: Francesco Ruggeri <fruggeri@arista.com>
Signed-off-by: Francesco Ruggeri <fruggeri@arista.com>
Co-developed-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Dmitry Safonov <dima@arista.com>
---
 include/net/tcp.h     |   8 ++
 include/net/tcp_ao.h  |  14 +++
 net/ipv4/tcp_ao.c     | 209 ++++++++++++++++++++++++++++++++++++++++++
 net/ipv4/tcp_ipv4.c   |   1 +
 net/ipv4/tcp_output.c | 121 +++++++++++++++++++++---
 net/ipv6/tcp_ao.c     |  27 ++++++
 net/ipv6/tcp_ipv6.c   |   2 +
 7 files changed, 371 insertions(+), 11 deletions(-)

Comments

Dan Carpenter Aug. 22, 2022, 12:03 p.m. UTC | #1
Hi Dmitry,

url:    https://github.com/intel-lab-lkp/linux/commits/Dmitry-Safonov/net-tcp-Add-TCP-AO-support/20220819-010628  
base:   e34cfee65ec891a319ce79797dda18083af33a76
config: x86_64-randconfig-m001 (https://download.01.org/0day-ci/archive/20220822/202208221901.Fs6wW5Jd-lkp@intel.com/config  )
compiler: gcc-11 (Debian 11.3.0-5) 11.3.0

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>

smatch warnings:
net/ipv4/tcp_output.c:640 tcp_options_write() error: uninitialized symbol 'maclen'.
net/ipv4/tcp_output.c:686 tcp_options_write() error: we previously assumed 'tp' could be null (see line 626)

vim +/maclen +640 net/ipv4/tcp_output.c

ea66758c1795cef Paolo Abeni           2022-05-04  608  static void tcp_options_write(struct tcphdr *th, struct tcp_sock *tp,
85df6b860d509a9 Dmitry Safonov        2022-08-18  609  			      struct tcp_out_options *opts,
85df6b860d509a9 Dmitry Safonov        2022-08-18  610  			      struct tcp_ao_key *ao_key)
bd0388ae7707502 William Allen Simpson 2009-12-02  611  {
ea66758c1795cef Paolo Abeni           2022-05-04  612  	__be32 *ptr = (__be32 *)(th + 1);
2100c8d2d9db23c Yuchung Cheng         2012-07-19  613  	u16 options = opts->options;	/* mungable copy */
bd0388ae7707502 William Allen Simpson 2009-12-02  614  
bd0388ae7707502 William Allen Simpson 2009-12-02  615  	if (unlikely(OPTION_MD5 & options)) {
1a2c6181c4a1922 Christoph Paasch      2013-03-17  616  		*ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
1a2c6181c4a1922 Christoph Paasch      2013-03-17  617  			       (TCPOPT_MD5SIG << 8) | TCPOLEN_MD5SIG);
bd0388ae7707502 William Allen Simpson 2009-12-02  618  		/* overload cookie hash location */
bd0388ae7707502 William Allen Simpson 2009-12-02  619  		opts->hash_location = (__u8 *)ptr;
33ad798c924b4a1 Adam Langley          2008-07-19  620  		ptr += 4;
33ad798c924b4a1 Adam Langley          2008-07-19  621  	}
85df6b860d509a9 Dmitry Safonov        2022-08-18  622  #ifdef CONFIG_TCP_AO
85df6b860d509a9 Dmitry Safonov        2022-08-18  623  	if (unlikely(OPTION_AO & options)) {
85df6b860d509a9 Dmitry Safonov        2022-08-18  624  		u8 maclen;
33ad798c924b4a1 Adam Langley          2008-07-19  625  
85df6b860d509a9 Dmitry Safonov        2022-08-18 @626  		if (tp) {

Can "tp" really be NULL?  Everything else assumes it can't.

85df6b860d509a9 Dmitry Safonov        2022-08-18  627  			struct tcp_ao_info *ao_info;
85df6b860d509a9 Dmitry Safonov        2022-08-18  628  
85df6b860d509a9 Dmitry Safonov        2022-08-18  629  			ao_info = rcu_dereference_check(tp->ao_info,
85df6b860d509a9 Dmitry Safonov        2022-08-18  630  				lockdep_sock_is_held(&tp->inet_conn.icsk_inet.sk));
85df6b860d509a9 Dmitry Safonov        2022-08-18  631  			if (WARN_ON_ONCE(!ao_key || !ao_info || !ao_info->rnext_key))
85df6b860d509a9 Dmitry Safonov        2022-08-18  632  				goto out_ao;
85df6b860d509a9 Dmitry Safonov        2022-08-18  633  			maclen = tcp_ao_maclen(ao_key);
85df6b860d509a9 Dmitry Safonov        2022-08-18  634  			*ptr++ = htonl((TCPOPT_AO << 24) |
85df6b860d509a9 Dmitry Safonov        2022-08-18  635  				       (tcp_ao_len(ao_key) << 16) |
85df6b860d509a9 Dmitry Safonov        2022-08-18  636  				       (ao_key->sndid << 8) |
85df6b860d509a9 Dmitry Safonov        2022-08-18  637  				       (ao_info->rnext_key->rcvid));
85df6b860d509a9 Dmitry Safonov        2022-08-18  638  		}

"maclen" not initialized on else path.

85df6b860d509a9 Dmitry Safonov        2022-08-18  639  		opts->hash_location = (__u8 *)ptr;
85df6b860d509a9 Dmitry Safonov        2022-08-18 @640  		ptr += maclen / sizeof(*ptr);

Uninitialized.

85df6b860d509a9 Dmitry Safonov        2022-08-18  641  		if (unlikely(maclen % sizeof(*ptr))) {
85df6b860d509a9 Dmitry Safonov        2022-08-18  642  			memset(ptr, TCPOPT_NOP, sizeof(*ptr));
85df6b860d509a9 Dmitry Safonov        2022-08-18  643  			ptr++;
85df6b860d509a9 Dmitry Safonov        2022-08-18  644  		}
85df6b860d509a9 Dmitry Safonov        2022-08-18  645  	}
85df6b860d509a9 Dmitry Safonov        2022-08-18  646  out_ao:
85df6b860d509a9 Dmitry Safonov        2022-08-18  647  #endif
fd6149d332973ba Ilpo Järvinen         2008-10-23  648  	if (unlikely(opts->mss)) {
fd6149d332973ba Ilpo Järvinen         2008-10-23  649  		*ptr++ = htonl((TCPOPT_MSS << 24) |
fd6149d332973ba Ilpo Järvinen         2008-10-23  650  			       (TCPOLEN_MSS << 16) |
fd6149d332973ba Ilpo Järvinen         2008-10-23  651  			       opts->mss);
fd6149d332973ba Ilpo Järvinen         2008-10-23  652  	}
fd6149d332973ba Ilpo Järvinen         2008-10-23  653  
bd0388ae7707502 William Allen Simpson 2009-12-02  654  	if (likely(OPTION_TS & options)) {
bd0388ae7707502 William Allen Simpson 2009-12-02  655  		if (unlikely(OPTION_SACK_ADVERTISE & options)) {
33ad798c924b4a1 Adam Langley          2008-07-19  656  			*ptr++ = htonl((TCPOPT_SACK_PERM << 24) |
33ad798c924b4a1 Adam Langley          2008-07-19  657  				       (TCPOLEN_SACK_PERM << 16) |
33ad798c924b4a1 Adam Langley          2008-07-19  658  				       (TCPOPT_TIMESTAMP << 8) |
33ad798c924b4a1 Adam Langley          2008-07-19  659  				       TCPOLEN_TIMESTAMP);
bd0388ae7707502 William Allen Simpson 2009-12-02  660  			options &= ~OPTION_SACK_ADVERTISE;
33ad798c924b4a1 Adam Langley          2008-07-19  661  		} else {
496c98dff8e3538 YOSHIFUJI Hideaki     2006-10-10  662  			*ptr++ = htonl((TCPOPT_NOP << 24) |
40efc6fa179f440 Stephen Hemminger     2006-01-03  663  				       (TCPOPT_NOP << 16) |
40efc6fa179f440 Stephen Hemminger     2006-01-03  664  				       (TCPOPT_TIMESTAMP << 8) |
40efc6fa179f440 Stephen Hemminger     2006-01-03  665  				       TCPOLEN_TIMESTAMP);
40efc6fa179f440 Stephen Hemminger     2006-01-03  666  		}
33ad798c924b4a1 Adam Langley          2008-07-19  667  		*ptr++ = htonl(opts->tsval);
33ad798c924b4a1 Adam Langley          2008-07-19  668  		*ptr++ = htonl(opts->tsecr);
33ad798c924b4a1 Adam Langley          2008-07-19  669  	}
33ad798c924b4a1 Adam Langley          2008-07-19  670  
bd0388ae7707502 William Allen Simpson 2009-12-02  671  	if (unlikely(OPTION_SACK_ADVERTISE & options)) {
33ad798c924b4a1 Adam Langley          2008-07-19  672  		*ptr++ = htonl((TCPOPT_NOP << 24) |
33ad798c924b4a1 Adam Langley          2008-07-19  673  			       (TCPOPT_NOP << 16) |
33ad798c924b4a1 Adam Langley          2008-07-19  674  			       (TCPOPT_SACK_PERM << 8) |
33ad798c924b4a1 Adam Langley          2008-07-19  675  			       TCPOLEN_SACK_PERM);
33ad798c924b4a1 Adam Langley          2008-07-19  676  	}
33ad798c924b4a1 Adam Langley          2008-07-19  677  
bd0388ae7707502 William Allen Simpson 2009-12-02  678  	if (unlikely(OPTION_WSCALE & options)) {
33ad798c924b4a1 Adam Langley          2008-07-19  679  		*ptr++ = htonl((TCPOPT_NOP << 24) |
33ad798c924b4a1 Adam Langley          2008-07-19  680  			       (TCPOPT_WINDOW << 16) |
33ad798c924b4a1 Adam Langley          2008-07-19  681  			       (TCPOLEN_WINDOW << 8) |
33ad798c924b4a1 Adam Langley          2008-07-19  682  			       opts->ws);
33ad798c924b4a1 Adam Langley          2008-07-19  683  	}
33ad798c924b4a1 Adam Langley          2008-07-19  684  
33ad798c924b4a1 Adam Langley          2008-07-19  685  	if (unlikely(opts->num_sack_blocks)) {
33ad798c924b4a1 Adam Langley          2008-07-19 @686  		struct tcp_sack_block *sp = tp->rx_opt.dsack ?

Unchecked dereference.

33ad798c924b4a1 Adam Langley          2008-07-19  687  			tp->duplicate_sack : tp->selective_acks;
40efc6fa179f440 Stephen Hemminger     2006-01-03  688  		int this_sack;
40efc6fa179f440 Stephen Hemminger     2006-01-03  689  
40efc6fa179f440 Stephen Hemminger     2006-01-03  690  		*ptr++ = htonl((TCPOPT_NOP  << 24) |
40efc6fa179f440 Stephen Hemminger     2006-01-03  691  			       (TCPOPT_NOP  << 16) |
40efc6fa179f440 Stephen Hemminger     2006-01-03  692  			       (TCPOPT_SACK <<  8) |
33ad798c924b4a1 Adam Langley          2008-07-19  693  			       (TCPOLEN_SACK_BASE + (opts->num_sack_blocks *
40efc6fa179f440 Stephen Hemminger     2006-01-03  694  						     TCPOLEN_SACK_PERBLOCK)));
2de979bd7da9c8b Stephen Hemminger     2007-03-08  695  
33ad798c924b4a1 Adam Langley          2008-07-19  696  		for (this_sack = 0; this_sack < opts->num_sack_blocks;
33ad798c924b4a1 Adam Langley          2008-07-19  697  		     ++this_sack) {
40efc6fa179f440 Stephen Hemminger     2006-01-03  698  			*ptr++ = htonl(sp[this_sack].start_seq);
40efc6fa179f440 Stephen Hemminger     2006-01-03  699  			*ptr++ = htonl(sp[this_sack].end_seq);
40efc6fa179f440 Stephen Hemminger     2006-01-03  700  		}
2de979bd7da9c8b Stephen Hemminger     2007-03-08  701  
40efc6fa179f440 Stephen Hemminger     2006-01-03  702  		tp->rx_opt.dsack = 0;
40efc6fa179f440 Stephen Hemminger     2006-01-03  703  	}
2100c8d2d9db23c Yuchung Cheng         2012-07-19  704  
2100c8d2d9db23c Yuchung Cheng         2012-07-19  705  	if (unlikely(OPTION_FAST_OPEN_COOKIE & options)) {
2100c8d2d9db23c Yuchung Cheng         2012-07-19  706  		struct tcp_fastopen_cookie *foc = opts->fastopen_cookie;
7f9b838b71eb78a Daniel Lee            2015-04-06  707  		u8 *p = (u8 *)ptr;
7f9b838b71eb78a Daniel Lee            2015-04-06  708  		u32 len; /* Fast Open option length */
2100c8d2d9db23c Yuchung Cheng         2012-07-19  709  
7f9b838b71eb78a Daniel Lee            2015-04-06  710  		if (foc->exp) {
7f9b838b71eb78a Daniel Lee            2015-04-06  711  			len = TCPOLEN_EXP_FASTOPEN_BASE + foc->len;
7f9b838b71eb78a Daniel Lee            2015-04-06  712  			*ptr = htonl((TCPOPT_EXP << 24) | (len << 16) |
2100c8d2d9db23c Yuchung Cheng         2012-07-19  713  				     TCPOPT_FASTOPEN_MAGIC);
7f9b838b71eb78a Daniel Lee            2015-04-06  714  			p += TCPOLEN_EXP_FASTOPEN_BASE;
7f9b838b71eb78a Daniel Lee            2015-04-06  715  		} else {
7f9b838b71eb78a Daniel Lee            2015-04-06  716  			len = TCPOLEN_FASTOPEN_BASE + foc->len;
7f9b838b71eb78a Daniel Lee            2015-04-06  717  			*p++ = TCPOPT_FASTOPEN;
7f9b838b71eb78a Daniel Lee            2015-04-06  718  			*p++ = len;
7f9b838b71eb78a Daniel Lee            2015-04-06  719  		}
2100c8d2d9db23c Yuchung Cheng         2012-07-19  720  
7f9b838b71eb78a Daniel Lee            2015-04-06  721  		memcpy(p, foc->val, foc->len);
7f9b838b71eb78a Daniel Lee            2015-04-06  722  		if ((len & 3) == 2) {
7f9b838b71eb78a Daniel Lee            2015-04-06  723  			p[foc->len] = TCPOPT_NOP;
7f9b838b71eb78a Daniel Lee            2015-04-06  724  			p[foc->len + 1] = TCPOPT_NOP;
2100c8d2d9db23c Yuchung Cheng         2012-07-19  725  		}
7f9b838b71eb78a Daniel Lee            2015-04-06  726  		ptr += (len + 3) >> 2;
2100c8d2d9db23c Yuchung Cheng         2012-07-19  727  	}
60e2a7780793bae Ursula Braun          2017-10-25  728  
60e2a7780793bae Ursula Braun          2017-10-25  729  	smc_options_write(ptr, &options);
eda7acddf8080bb Peter Krystad         2020-01-21  730  
ea66758c1795cef Paolo Abeni           2022-05-04  731  	mptcp_options_write(th, ptr, tp, opts);
                                                                                     ^^
Not checked here either.

60e2a7780793bae Ursula Braun          2017-10-25  732  }
Dmitry Safonov Aug. 29, 2022, 5:55 p.m. UTC | #2
Hi Dan,

On 8/22/22 13:03, Dan Carpenter wrote:
> Hi Dmitry,
[..]
> ea66758c1795cef Paolo Abeni           2022-05-04  608  static void tcp_options_write(struct tcphdr *th, struct tcp_sock *tp,
[..]
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  622  #ifdef CONFIG_TCP_AO
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  623  	if (unlikely(OPTION_AO & options)) {
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  624  		u8 maclen;
> 33ad798c924b4a1 Adam Langley          2008-07-19  625  
> 85df6b860d509a9 Dmitry Safonov        2022-08-18 @626  		if (tp) {
> 
> Can "tp" really be NULL?  Everything else assumes it can't.

It actually can be NULL, see tcp_make_synack().
At this moment code assumes that *either* (tp == NULL) or (tcprsk == NULL).

> 85df6b860d509a9 Dmitry Safonov        2022-08-18  627  			struct tcp_ao_info *ao_info;
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  628  
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  629  			ao_info = rcu_dereference_check(tp->ao_info,
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  630  				lockdep_sock_is_held(&tp->inet_conn.icsk_inet.sk));
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  631  			if (WARN_ON_ONCE(!ao_key || !ao_info || !ao_info->rnext_key))
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  632  				goto out_ao;
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  633  			maclen = tcp_ao_maclen(ao_key);
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  634  			*ptr++ = htonl((TCPOPT_AO << 24) |
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  635  				       (tcp_ao_len(ao_key) << 16) |
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  636  				       (ao_key->sndid << 8) |
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  637  				       (ao_info->rnext_key->rcvid));
> 85df6b860d509a9 Dmitry Safonov        2022-08-18  638  		}
> 
> "maclen" not initialized on else path.

Patch 15 ("net/tcp: Wire TCP-AO to request sockets") adds
+		if (tcprsk) {
+			u8 aolen = tcprsk->maclen + sizeof(struct tcp_ao_hdr);
+
+			maclen = tcprsk->maclen;
+			*ptr++ = htonl((TCPOPT_AO << 24) | (aolen << 16) |
+				       (tcprsk->ao_keyid << 8) |
+				       (tcprsk->ao_rcv_next));
+		}

Assuming that tp != NULL OR tcprsk != NULL, maclen is always initialized
_after both patches_.
For version 2, I'll make it (break patches) in more bisect-able way for
static analyzers and do WARN_ON(tp == NULL && tcprsk == NULL) with break
off TCP-AO signing to make sure the assumption will be valid with
the code changing later on.

[..]
> ea66758c1795cef Paolo Abeni           2022-05-04  731  	mptcp_options_write(th, ptr, tp, opts);
>                                                                                      ^^
> Not checked here either.

The function has checks inside.

Thanks,
          Dmitry
diff mbox series

Patch

diff --git a/include/net/tcp.h b/include/net/tcp.h
index e140ae4fe653..d91a963f430d 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -26,6 +26,7 @@ 
 #include <linux/kref.h>
 #include <linux/ktime.h>
 #include <linux/indirect_call_wrapper.h>
+#include <crypto/pool.h>
 
 #include <net/inet_connection_sock.h>
 #include <net/inet_timewait_sock.h>
@@ -187,6 +188,7 @@  void tcp_time_wait(struct sock *sk, int state, int timeo);
 #define TCPOPT_SACK             5       /* SACK Block */
 #define TCPOPT_TIMESTAMP	8	/* Better RTT estimations/PAWS */
 #define TCPOPT_MD5SIG		19	/* MD5 Signature (RFC2385) */
+#define TCPOPT_AO		29	/* Authentication Option (RFC5925) */
 #define TCPOPT_MPTCP		30	/* Multipath TCP (RFC6824) */
 #define TCPOPT_FASTOPEN		34	/* Fast open (RFC7413) */
 #define TCPOPT_EXP		254	/* Experimental */
@@ -2078,6 +2080,12 @@  struct tcp_sock_af_ops {
 	struct tcp_ao_key	*(*ao_lookup)(const struct sock *sk,
 					      struct sock  *addr_sk,
 					      int sndid, int rcvid);
+	int			(*calc_ao_hash)(char *location,
+						struct tcp_ao_key *ao,
+						const struct sock *sk,
+						const struct sk_buff *skb,
+						const u8 *tkey,
+						int hash_offset, u32 sne);
 	int			(*ao_calc_key_sk)(struct tcp_ao_key *mkt,
 						  u8 *key,
 						  const struct sock *sk,
diff --git a/include/net/tcp_ao.h b/include/net/tcp_ao.h
index f83a4d09a4ce..f840b693d038 100644
--- a/include/net/tcp_ao.h
+++ b/include/net/tcp_ao.h
@@ -108,11 +108,16 @@  struct tcp6_ao_context {
 	__be32		disn;
 };
 
+int tcp_ao_hash_skb(unsigned short int family,
+		    char *ao_hash, struct tcp_ao_key *key,
+		    const struct sock *sk, const struct sk_buff *skb,
+		    const u8 *tkey, int hash_offset, u32 sne);
 int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family,
 		 sockptr_t optval, int optlen);
 int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx,
 			    unsigned int len);
 void tcp_ao_destroy_sock(struct sock *sk);
+u32 tcp_ao_compute_sne(u32 sne, u32 seq, u32 new_seq);
 int tcp_ao_cache_traffic_keys(const struct sock *sk, struct tcp_ao_info *ao,
 			      struct tcp_ao_key *ao_key);
 struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
@@ -125,13 +130,22 @@  struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
 int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
 			  const struct sock *sk,
 			  __be32 sisn, __be32 disn, bool send);
+int tcp_v4_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key,
+		       const struct sock *sk, const struct sk_buff *skb,
+		       const u8 *tkey, int hash_offset, u32 sne);
 /* ipv6 specific functions */
+int tcp_v6_ao_hash_pseudoheader(struct crypto_pool_ahash *hp,
+				const struct in6_addr *daddr,
+				const struct in6_addr *saddr, int nbytes);
 int tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
 				 const struct sock *sk, __be32 sisn,
 				 __be32 disn, bool send);
 struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk,
 				    struct sock *addr_sk,
 				    int sndid, int rcvid);
+int tcp_v6_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key,
+		       const struct sock *sk, const struct sk_buff *skb,
+		       const u8 *tkey, int hash_offset, u32 sne);
 int tcp_v6_parse_ao(struct sock *sk, int cmd,
 		    sockptr_t optval, int optlen);
 void tcp_ao_finish_connect(struct sock *sk, struct sk_buff *skb);
diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c
index 60fe385fda1c..ec3ab0c3dc77 100644
--- a/net/ipv4/tcp_ao.c
+++ b/net/ipv4/tcp_ao.c
@@ -253,6 +253,215 @@  int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
 					  htons(sk->sk_num), disn, sisn);
 }
 
+static int tcp_v4_ao_hash_pseudoheader(struct crypto_pool_ahash *hp,
+				       __be32 daddr, __be32 saddr,
+				       int nbytes)
+{
+	struct tcp4_pseudohdr *bp;
+	struct scatterlist sg;
+
+	bp = hp->base.scratch;
+	bp->saddr = saddr;
+	bp->daddr = daddr;
+	bp->pad = 0;
+	bp->protocol = IPPROTO_TCP;
+	bp->len = cpu_to_be16(nbytes);
+
+	sg_init_one(&sg, bp, sizeof(*bp));
+	ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp));
+	return crypto_ahash_update(hp->req);
+}
+
+static int tcp_ao_hash_pseudoheader(unsigned short int family,
+				    const struct sock *sk,
+				    const struct sk_buff *skb,
+				    struct crypto_pool_ahash *hp, int nbytes)
+{
+	const struct tcphdr *th = tcp_hdr(skb);
+
+	/* TODO: Can we rely on checksum being zero to mean outbound pkt? */
+	if (!th->check) {
+		if (family == AF_INET)
+			return tcp_v4_ao_hash_pseudoheader(hp, sk->sk_daddr,
+					sk->sk_rcv_saddr, skb->len);
+		else
+			return tcp_v6_ao_hash_pseudoheader(hp, &sk->sk_v6_daddr,
+					&sk->sk_v6_rcv_saddr, skb->len);
+	}
+
+	if (family == AF_INET) {
+		const struct iphdr *iph = ip_hdr(skb);
+
+		return tcp_v4_ao_hash_pseudoheader(hp, iph->daddr,
+				iph->saddr, skb->len);
+	} else {
+		const struct ipv6hdr *iph = ipv6_hdr(skb);
+
+		return tcp_v6_ao_hash_pseudoheader(hp, &iph->daddr,
+				&iph->saddr, skb->len);
+	}
+}
+
+u32 tcp_ao_compute_sne(u32 prev_sne, u32 prev_seq, u32 seq)
+{
+	u32 sne = prev_sne;
+
+	if (before(seq, prev_seq)) {
+		if (seq > prev_seq)
+			sne--;
+	} else {
+		if (seq < prev_seq)
+			sne++;
+	}
+
+	return sne;
+}
+
+/* tcp_ao_hash_sne(struct crypto_pool_ahash *hp)
+ * @hp	- used for hashing
+ * @sne - sne value
+ */
+static int tcp_ao_hash_sne(struct crypto_pool_ahash *hp, u32 sne)
+{
+	struct scatterlist sg;
+	__be32 *bp;
+
+	bp = (__be32 *)hp->base.scratch;
+	*bp = htonl(sne);
+
+	sg_init_one(&sg, bp, sizeof(*bp));
+	ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp));
+	return crypto_ahash_update(hp->req);
+}
+
+static int tcp_ao_hash_header(struct crypto_pool_ahash *hp,
+			      const struct tcphdr *th,
+			      bool exclude_options, u8 *hash,
+			      int hash_offset, int hash_len)
+{
+	struct scatterlist sg;
+	u8 *hdr = hp->base.scratch;
+	int err, len = th->doff << 2;
+
+	/* We are not allowed to change tcphdr, make a local copy */
+	if (exclude_options) {
+		len = sizeof(*th) + sizeof(struct tcp_ao_hdr) + hash_len;
+		memcpy(hdr, th, sizeof(*th));
+		memcpy(hdr + sizeof(*th),
+		       (u8 *)th + hash_offset - sizeof(struct tcp_ao_hdr),
+		       sizeof(struct tcp_ao_hdr));
+		memset(hdr + sizeof(*th) + sizeof(struct tcp_ao_hdr),
+		       0, hash_len);
+		((struct tcphdr *)hdr)->check = 0;
+	} else {
+		len = th->doff << 2;
+		memcpy(hdr, th, len);
+		/* zero out tcp-ao hash */
+		((struct tcphdr *)hdr)->check = 0;
+		memset(hdr + hash_offset, 0, hash_len);
+	}
+
+	sg_init_one(&sg, hdr, len);
+	ahash_request_set_crypt(hp->req, &sg, NULL, len);
+	err = crypto_ahash_update(hp->req);
+	WARN_ON_ONCE(err != 0);
+	return err;
+}
+
+static int tcp_ao_hash_skb_data(struct crypto_pool_ahash *hp,
+				const struct sk_buff *skb,
+				unsigned int header_len)
+{
+	struct scatterlist sg;
+	const struct tcphdr *tp = tcp_hdr(skb);
+	struct ahash_request *req = hp->req;
+	unsigned int i;
+	const unsigned int head_data_len = skb_headlen(skb) > header_len ?
+					   skb_headlen(skb) - header_len : 0;
+	const struct skb_shared_info *shi = skb_shinfo(skb);
+	struct sk_buff *frag_iter;
+
+	WARN_ON(skb_headlen(skb) < header_len);
+
+	sg_init_table(&sg, 1);
+
+	sg_set_buf(&sg, ((u8 *)tp) + header_len, head_data_len);
+	ahash_request_set_crypt(req, &sg, NULL, head_data_len);
+	if (crypto_ahash_update(req))
+		return 1;
+
+	for (i = 0; i < shi->nr_frags; ++i) {
+		const skb_frag_t *f = &shi->frags[i];
+		unsigned int offset = skb_frag_off(f);
+		struct page *page = skb_frag_page(f) + (offset >> PAGE_SHIFT);
+
+		sg_set_page(&sg, page, skb_frag_size(f),
+			    offset_in_page(offset));
+		ahash_request_set_crypt(req, &sg, NULL, skb_frag_size(f));
+		if (crypto_ahash_update(req))
+			return 1;
+	}
+
+	skb_walk_frags(skb, frag_iter)
+		if (tcp_ao_hash_skb_data(hp, frag_iter, 0))
+			return 1;
+
+	return 0;
+}
+
+int tcp_ao_hash_skb(unsigned short int family,
+		    char *ao_hash, struct tcp_ao_key *key,
+		    const struct sock *sk, const struct sk_buff *skb,
+		    const u8 *tkey,  int hash_offset, u32 sne)
+{
+	const struct tcphdr *th = tcp_hdr(skb);
+	int tkey_len = tcp_ao_digest_size(key);
+	__u8 tmp_hash[TCP_AO_MAX_HASH_SIZE] __tcp_ao_key_align;
+	struct crypto_pool_ahash hp;
+
+	if (crypto_pool_get(key->crypto_pool_id, (struct crypto_pool *)&hp))
+		goto clear_hash_noput;
+
+	if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp.req), tkey, tkey_len))
+		goto clear_hash;
+
+	/* For now use sha1 by default. Depends on alg in tcp_ao_key */
+	if (crypto_ahash_init(hp.req))
+		goto clear_hash;
+
+	if (tcp_ao_hash_sne(&hp, sne))
+		goto clear_hash;
+	if (tcp_ao_hash_pseudoheader(family, sk, skb, &hp, skb->len))
+		goto clear_hash;
+	if (tcp_ao_hash_header(&hp, th, false,
+			       ao_hash, hash_offset, tcp_ao_maclen(key)))
+		goto clear_hash;
+	if (tcp_ao_hash_skb_data(&hp, skb, th->doff << 2))
+		goto clear_hash;
+	ahash_request_set_crypt(hp.req, NULL, tmp_hash, 0);
+	if (crypto_ahash_final(hp.req))
+		goto clear_hash;
+
+	memcpy(ao_hash, tmp_hash, tcp_ao_maclen(key));
+	crypto_pool_put();
+	return 0;
+
+clear_hash:
+	crypto_pool_put();
+clear_hash_noput:
+	memset(ao_hash, 0, tcp_ao_maclen(key));
+	return 1;
+}
+EXPORT_SYMBOL(tcp_ao_hash_skb);
+
+int tcp_v4_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key,
+		       const struct sock *sk, const struct sk_buff *skb,
+		       const u8 *tkey, int hash_offset, u32 sne)
+{
+	return tcp_ao_hash_skb(AF_INET, ao_hash, key, sk, skb,
+			       tkey, hash_offset, sne);
+}
+
 struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
 				    int sndid, int rcvid)
 {
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 64fea0945901..f6fe9ec1c99d 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -2245,6 +2245,7 @@  static const struct tcp_sock_af_ops tcp_sock_ipv4_specific = {
 #endif
 #ifdef CONFIG_TCP_AO
 	.ao_lookup		= tcp_v4_ao_lookup,
+	.calc_ao_hash		= tcp_v4_ao_hash_skb,
 	.ao_parse		= tcp_v4_parse_ao,
 	.ao_calc_key_sk		= tcp_v4_ao_calc_key_sk,
 #endif
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index ee0c642a87e5..26315cac09dd 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -413,6 +413,7 @@  static inline bool tcp_urg_mode(const struct tcp_sock *tp)
 #define OPTION_FAST_OPEN_COOKIE	BIT(8)
 #define OPTION_SMC		BIT(9)
 #define OPTION_MPTCP		BIT(10)
+#define OPTION_AO		BIT(11)
 
 static void smc_options_write(__be32 *ptr, u16 *options)
 {
@@ -605,7 +606,8 @@  static void bpf_skops_write_hdr_opt(struct sock *sk, struct sk_buff *skb,
  * (but it may well be that other scenarios fail similarly).
  */
 static void tcp_options_write(struct tcphdr *th, struct tcp_sock *tp,
-			      struct tcp_out_options *opts)
+			      struct tcp_out_options *opts,
+			      struct tcp_ao_key *ao_key)
 {
 	__be32 *ptr = (__be32 *)(th + 1);
 	u16 options = opts->options;	/* mungable copy */
@@ -617,7 +619,32 @@  static void tcp_options_write(struct tcphdr *th, struct tcp_sock *tp,
 		opts->hash_location = (__u8 *)ptr;
 		ptr += 4;
 	}
-
+#ifdef CONFIG_TCP_AO
+	if (unlikely(OPTION_AO & options)) {
+		u8 maclen;
+
+		if (tp) {
+			struct tcp_ao_info *ao_info;
+
+			ao_info = rcu_dereference_check(tp->ao_info,
+				lockdep_sock_is_held(&tp->inet_conn.icsk_inet.sk));
+			if (WARN_ON_ONCE(!ao_key || !ao_info || !ao_info->rnext_key))
+				goto out_ao;
+			maclen = tcp_ao_maclen(ao_key);
+			*ptr++ = htonl((TCPOPT_AO << 24) |
+				       (tcp_ao_len(ao_key) << 16) |
+				       (ao_key->sndid << 8) |
+				       (ao_info->rnext_key->rcvid));
+		}
+		opts->hash_location = (__u8 *)ptr;
+		ptr += maclen / sizeof(*ptr);
+		if (unlikely(maclen % sizeof(*ptr))) {
+			memset(ptr, TCPOPT_NOP, sizeof(*ptr));
+			ptr++;
+		}
+	}
+out_ao:
+#endif
 	if (unlikely(opts->mss)) {
 		*ptr++ = htonl((TCPOPT_MSS << 24) |
 			       (TCPOLEN_MSS << 16) |
@@ -758,7 +785,8 @@  static void mptcp_set_option_cond(const struct request_sock *req,
  */
 static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
 				struct tcp_out_options *opts,
-				struct tcp_md5sig_key **md5)
+				struct tcp_md5sig_key **md5,
+				struct tcp_ao_key *ao_key)
 {
 	struct tcp_sock *tp = tcp_sk(sk);
 	unsigned int remaining = MAX_TCP_OPTION_SPACE;
@@ -775,6 +803,12 @@  static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
 		}
 	}
 #endif
+#ifdef CONFIG_TCP_AO
+	if (ao_key) {
+		opts->options |= OPTION_AO;
+		remaining -= tcp_ao_len(ao_key);
+	}
+#endif
 
 	/* We always get an MSS option.  The option bytes which will be seen in
 	 * normal data packets should timestamps be used, must be in the MSS
@@ -842,6 +876,7 @@  static unsigned int tcp_synack_options(const struct sock *sk,
 				       unsigned int mss, struct sk_buff *skb,
 				       struct tcp_out_options *opts,
 				       const struct tcp_md5sig_key *md5,
+				       const struct tcp_ao_key *ao,
 				       struct tcp_fastopen_cookie *foc,
 				       enum tcp_synack_type synack_type,
 				       struct sk_buff *syn_skb)
@@ -863,6 +898,14 @@  static unsigned int tcp_synack_options(const struct sock *sk,
 			ireq->tstamp_ok &= !ireq->sack_ok;
 	}
 #endif
+#ifdef CONFIG_TCP_AO
+	if (ao) {
+		opts->options |= OPTION_AO;
+		remaining -= tcp_ao_len(ao);
+		ireq->tstamp_ok &= !ireq->sack_ok;
+	}
+#endif
+	WARN_ON_ONCE(md5 && ao);
 
 	/* We always send an MSS option. */
 	opts->mss = mss;
@@ -912,7 +955,8 @@  static unsigned int tcp_synack_options(const struct sock *sk,
  */
 static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb,
 					struct tcp_out_options *opts,
-					struct tcp_md5sig_key **md5)
+					struct tcp_md5sig_key **md5,
+					struct tcp_ao_key *ao_key)
 {
 	struct tcp_sock *tp = tcp_sk(sk);
 	unsigned int size = 0;
@@ -931,6 +975,12 @@  static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
 		}
 	}
 #endif
+#ifdef CONFIG_TCP_AO
+	if (ao_key) {
+		opts->options |= OPTION_AO;
+		size += tcp_ao_len(ao_key);
+	}
+#endif
 
 	if (likely(tp->rx_opt.tstamp_ok)) {
 		opts->options |= OPTION_TS;
@@ -1245,6 +1295,10 @@  static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
 	unsigned int tcp_options_size, tcp_header_size;
 	struct sk_buff *oskb = NULL;
 	struct tcp_md5sig_key *md5;
+#ifdef CONFIG_TCP_AO
+	struct tcp_ao_info *ao;
+#endif
+	struct tcp_ao_key *ao_key = NULL;
 	struct tcphdr *th;
 	u64 prior_wstamp;
 	int err;
@@ -1276,11 +1330,17 @@  static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
 	tcb = TCP_SKB_CB(skb);
 	memset(&opts, 0, sizeof(opts));
 
+#ifdef CONFIG_TCP_AO
+	ao = rcu_dereference_protected(tcp_sk(sk)->ao_info,
+				       lockdep_sock_is_held(sk));
+	if (ao)
+		ao_key = ao->current_key;
+#endif
 	if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
-		tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
+		tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5, ao_key);
 	} else {
 		tcp_options_size = tcp_established_options(sk, skb, &opts,
-							   &md5);
+							   &md5, ao_key);
 		/* Force a PSH flag on all (GSO) packets to expedite GRO flush
 		 * at receiver : This slightly improve GRO performance.
 		 * Note that we do not force the PSH flag for non GSO packets,
@@ -1354,7 +1414,7 @@  static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
 		th->window	= htons(min(tp->rcv_wnd, 65535U));
 	}
 
-	tcp_options_write(th, tp, &opts);
+	tcp_options_write(th, tp, &opts, ao_key);
 
 #ifdef CONFIG_TCP_MD5SIG
 	/* Calculate the MD5 hash, as we have all we need now */
@@ -1364,6 +1424,32 @@  static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
 					       md5, sk, skb);
 	}
 #endif
+#ifdef CONFIG_TCP_AO
+	if (ao) {
+		u8 *traffic_key;
+		u8 key_buf[TCP_AO_MAX_HASH_SIZE];
+		u32 sne;
+		__u32 disn;
+
+		sk_gso_disable(sk);
+		if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
+			if (tcb->tcp_flags & TCPHDR_ACK)
+				disn = ao->risn;
+			else
+				disn = 0;
+			traffic_key = key_buf;
+			tp->af_specific->ao_calc_key_sk(ao_key, traffic_key,
+							sk, ao->lisn, disn, true);
+		} else {
+			traffic_key = snd_other_key(ao_key);
+		}
+		sne = tcp_ao_compute_sne(ao->snd_sne, ao->snd_sne_seq,
+					 ntohl(th->seq));
+		tp->af_specific->calc_ao_hash(opts.hash_location, ao_key, sk, skb,
+					      traffic_key,
+					      opts.hash_location - (u8 *)th, sne);
+	}
+#endif
 
 	/* BPF prog is the last one writing header option */
 	bpf_skops_write_hdr_opt(sk, skb, NULL, NULL, 0, &opts);
@@ -1825,6 +1911,10 @@  unsigned int tcp_current_mss(struct sock *sk)
 	unsigned int header_len;
 	struct tcp_out_options opts;
 	struct tcp_md5sig_key *md5;
+	struct tcp_ao_key *ao_key = NULL;
+#ifdef CONFIG_TCP_AO
+	struct tcp_ao_info *ao_info;
+#endif
 
 	mss_now = tp->mss_cache;
 
@@ -1833,8 +1923,17 @@  unsigned int tcp_current_mss(struct sock *sk)
 		if (mtu != inet_csk(sk)->icsk_pmtu_cookie)
 			mss_now = tcp_sync_mss(sk, mtu);
 	}
-
-	header_len = tcp_established_options(sk, NULL, &opts, &md5) +
+#ifdef CONFIG_TCP_AO
+	ao_info = rcu_dereference_check(tp->ao_info, lockdep_sock_is_held(sk));
+	if (ao_info)
+		/* TODO: verify if we can access current_key or we need to pass
+		 * it from every caller of tcp_current_mss instead. The reason
+		 * is that the current_key pointer can change asynchronously
+		 * from the rx path.
+		 */
+		ao_key = ao_info->current_key;
+#endif
+	header_len = tcp_established_options(sk, NULL, &opts, &md5, ao_key) +
 		     sizeof(struct tcphdr);
 	/* The mss_cache is sized based on tp->tcp_header_len, which assumes
 	 * some common options. If this is an odd packet (because we have SACK
@@ -3580,7 +3679,7 @@  struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst,
 	/* bpf program will be interested in the tcp_flags */
 	TCP_SKB_CB(skb)->tcp_flags = TCPHDR_SYN | TCPHDR_ACK;
 	tcp_header_size = tcp_synack_options(sk, req, mss, skb, &opts, md5,
-					     foc, synack_type,
+					     NULL, foc, synack_type,
 					     syn_skb) + sizeof(*th);
 
 	skb_push(skb, tcp_header_size);
@@ -3601,7 +3700,7 @@  struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst,
 
 	/* RFC1323: The window in SYN & SYN/ACK segments is never scaled. */
 	th->window = htons(min(req->rsk_rcv_wnd, 65535U));
-	tcp_options_write(th, NULL, &opts);
+	tcp_options_write(th, NULL, &opts, NULL);
 	th->doff = (tcp_header_size >> 2);
 	__TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTSEGS);
 
diff --git a/net/ipv6/tcp_ao.c b/net/ipv6/tcp_ao.c
index 888ee6242334..7fd31c60488a 100644
--- a/net/ipv6/tcp_ao.c
+++ b/net/ipv6/tcp_ao.c
@@ -70,6 +70,33 @@  struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk,
 	return tcp_v6_ao_do_lookup(sk, addr, sndid, rcvid);
 }
 
+int tcp_v6_ao_hash_pseudoheader(struct crypto_pool_ahash *hp,
+				const struct in6_addr *daddr,
+				const struct in6_addr *saddr, int nbytes)
+{
+	struct tcp6_pseudohdr *bp;
+	struct scatterlist sg;
+
+	bp = hp->base.scratch;
+	/* 1. TCP pseudo-header (RFC2460) */
+	bp->saddr = *saddr;
+	bp->daddr = *daddr;
+	bp->len = cpu_to_be32(nbytes);
+	bp->protocol = cpu_to_be32(IPPROTO_TCP);
+
+	sg_init_one(&sg, bp, sizeof(*bp));
+	ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp));
+	return crypto_ahash_update(hp->req);
+}
+
+int tcp_v6_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key,
+		       const struct sock *sk, const struct sk_buff *skb,
+		       const u8 *tkey, int hash_offset, u32 sne)
+{
+	return tcp_ao_hash_skb(AF_INET6, ao_hash, key, sk, skb, tkey,
+			hash_offset, sne);
+}
+
 int tcp_v6_parse_ao(struct sock *sk, int cmd,
 		    sockptr_t optval, int optlen)
 {
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index c6d2389030f2..b5fa5ae53a47 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -1904,6 +1904,7 @@  static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
 #endif
 #ifdef CONFIG_TCP_AO
 	.ao_lookup	=	tcp_v6_ao_lookup,
+	.calc_ao_hash	=	tcp_v6_ao_hash_skb,
 	.ao_parse	=	tcp_v6_parse_ao,
 	.ao_calc_key_sk	=	tcp_v6_ao_calc_key_sk,
 #endif
@@ -1937,6 +1938,7 @@  static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {
 #endif
 #ifdef CONFIG_TCP_AO
 	.ao_lookup	=	tcp_v6_ao_lookup,
+	.calc_ao_hash	=	tcp_v6_ao_hash_skb,
 	.ao_parse	=	tcp_v6_parse_ao,
 #endif
 };