Message ID | 20220818170005.747015-9-dima@arista.com (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | net/tcp: Add TCP-AO support | expand |
Hi Dmitry, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on e34cfee65ec891a319ce79797dda18083af33a76] 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-a015 (https://download.01.org/0day-ci/archive/20220819/202208190223.JG4u6Nhm-lkp@intel.com/config) compiler: gcc-11 (Debian 11.3.0-5) 11.3.0 reproduce (this is a W=1 build): # https://github.com/intel-lab-lkp/linux/commit/469bd71e5ea011f6ae5a1554b75157471448341d git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Dmitry-Safonov/net-tcp-Add-TCP-AO-support/20220819-010628 git checkout 469bd71e5ea011f6ae5a1554b75157471448341d # save the config file mkdir build_dir && cp config build_dir/.config make W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash net/ipv4/ net/ipv6/ If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot <lkp@intel.com> All warnings (new ones prefixed by >>): >> net/ipv4/tcp_ao.c:19:20: warning: no previous prototype for 'tcp_ao_do_lookup_rcvid' [-Wmissing-prototypes] 19 | struct tcp_ao_key *tcp_ao_do_lookup_rcvid(struct sock *sk, u8 keyid) | ^~~~~~~~~~~~~~~~~~~~~~ net/ipv4/tcp_ao.c:37:20: warning: no previous prototype for 'tcp_ao_do_lookup_sndid' [-Wmissing-prototypes] 37 | struct tcp_ao_key *tcp_ao_do_lookup_sndid(const struct sock *sk, u8 keyid) | ^~~~~~~~~~~~~~~~~~~~~~ net/ipv4/tcp_ao.c: In function '__tcp_ao_key_cmp': net/ipv4/tcp_ao.c:88:46: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 88 | if (ipv6_addr_any(&key->addr.a6) || ipv6_addr_any(&addr->a6)) | ^~ | a4 net/ipv4/tcp_ao.c:88:74: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 88 | if (ipv6_addr_any(&key->addr.a6) || ipv6_addr_any(&addr->a6)) | ^~ | a4 net/ipv4/tcp_ao.c:90:50: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 90 | if (ipv6_prefix_equal(&key->addr.a6, &addr->a6, prefixlen)) | ^~ | a4 net/ipv4/tcp_ao.c:90:61: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 90 | if (ipv6_prefix_equal(&key->addr.a6, &addr->a6, prefixlen)) | ^~ | a4 net/ipv4/tcp_ao.c:92:42: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 92 | return memcmp(&key->addr.a6, &addr->a6, prefixlen); | ^~ | a4 net/ipv4/tcp_ao.c:92:53: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 92 | return memcmp(&key->addr.a6, &addr->a6, prefixlen); | ^~ | a4 net/ipv4/tcp_ao.c: At top level: >> net/ipv4/tcp_ao.c:96:5: warning: no previous prototype for 'tcp_ao_key_cmp' [-Wmissing-prototypes] 96 | int tcp_ao_key_cmp(const struct tcp_ao_key *key, | ^~~~~~~~~~~~~~ net/ipv4/tcp_ao.c: In function 'tcp_ao_key_cmp': net/ipv4/tcp_ao.c:100:61: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 100 | if (family == AF_INET6 && ipv6_addr_v4mapped(&addr->a6)) { | ^~ | a4 net/ipv4/tcp_ao.c:101:38: error: 'const union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 101 | __be32 addr4 = addr->a6.s6_addr32[3]; | ^~ | a4 net/ipv4/tcp_ao.c: At top level: net/ipv4/tcp_ao.c:109:20: warning: no previous prototype for 'tcp_ao_do_lookup' [-Wmissing-prototypes] 109 | struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk, | ^~~~~~~~~~~~~~~~ >> net/ipv4/tcp_ao.c:145:6: warning: no previous prototype for 'tcp_ao_link_mkt' [-Wmissing-prototypes] 145 | void tcp_ao_link_mkt(struct tcp_ao_info *ao, struct tcp_ao_key *mkt) | ^~~~~~~~~~~~~~~ In file included from include/linux/tcp.h:19, from net/ipv4/tcp_ao.c:13: net/ipv4/tcp_ao.c: In function 'tcp_ao_verify_ipv6': include/net/sock.h:385:45: error: 'struct sock_common' has no member named 'skc_v6_daddr'; did you mean 'skc_daddr'? 385 | #define sk_v6_daddr __sk_common.skc_v6_daddr | ^~~~~~~~~~~~ net/ipv4/tcp_ao.c:292:41: note: in expansion of macro 'sk_v6_daddr' 292 | if (!ipv6_addr_any(&sk->sk_v6_daddr)) { | ^~~~~~~~~~~ include/net/sock.h:385:45: error: 'struct sock_common' has no member named 'skc_v6_daddr'; did you mean 'skc_daddr'? 385 | #define sk_v6_daddr __sk_common.skc_v6_daddr | ^~~~~~~~~~~~ net/ipv4/tcp_ao.c:293:45: note: in expansion of macro 'sk_v6_daddr' 293 | __be32 daddr4 = sk->sk_v6_daddr.s6_addr32[3]; | ^~~~~~~~~~~ include/net/sock.h:385:45: error: 'struct sock_common' has no member named 'skc_v6_daddr'; did you mean 'skc_daddr'? 385 | #define sk_v6_daddr __sk_common.skc_v6_daddr | ^~~~~~~~~~~~ net/ipv4/tcp_ao.c:295:54: note: in expansion of macro 'sk_v6_daddr' 295 | if (!ipv6_addr_v4mapped(&sk->sk_v6_daddr)) | ^~~~~~~~~~~ include/net/sock.h:385:45: error: 'struct sock_common' has no member named 'skc_v6_daddr'; did you mean 'skc_daddr'? 385 | #define sk_v6_daddr __sk_common.skc_v6_daddr | ^~~~~~~~~~~~ net/ipv4/tcp_ao.c:316:41: note: in expansion of macro 'sk_v6_daddr' 316 | if (!ipv6_addr_any(&sk->sk_v6_daddr) && | ^~~~~~~~~~~ include/net/sock.h:385:45: error: 'struct sock_common' has no member named 'skc_v6_daddr'; did you mean 'skc_daddr'? 385 | #define sk_v6_daddr __sk_common.skc_v6_daddr | ^~~~~~~~~~~~ net/ipv4/tcp_ao.c:317:45: note: in expansion of macro 'sk_v6_daddr' 317 | !ipv6_prefix_equal(&sk->sk_v6_daddr, addr, prefix)) | ^~~~~~~~~~~ net/ipv4/tcp_ao.c: In function 'tcp_ao_mkt_overlap_v6': net/ipv4/tcp_ao.c:531:39: error: 'union tcp_ao_addr' has no member named 'a6'; did you mean 'a4'? 531 | key_addr = &key->addr.a6; | ^~ | a4 net/ipv4/tcp_ao.c: In function '__tcp_ao_key_cmp': net/ipv4/tcp_ao.c:94:1: error: control reaches end of non-void function [-Werror=return-type] 94 | } | ^ cc1: some warnings being treated as errors vim +/tcp_ao_do_lookup_rcvid +19 net/ipv4/tcp_ao.c 18 > 19 struct tcp_ao_key *tcp_ao_do_lookup_rcvid(struct sock *sk, u8 keyid) 20 { 21 struct tcp_sock *tp = tcp_sk(sk); 22 struct tcp_ao_key *key; 23 struct tcp_ao_info *ao; 24 25 ao = rcu_dereference_check(tp->ao_info, lockdep_sock_is_held(sk)); 26 27 if (!ao) 28 return NULL; 29 30 hlist_for_each_entry_rcu(key, &ao->head, node) { 31 if (key->rcvid == keyid) 32 return key; 33 } 34 return NULL; 35 } 36 37 struct tcp_ao_key *tcp_ao_do_lookup_sndid(const struct sock *sk, u8 keyid) 38 { 39 struct tcp_ao_key *key; 40 struct tcp_ao_info *ao; 41 42 ao = rcu_dereference_check(tcp_sk(sk)->ao_info, 43 lockdep_sock_is_held(sk)); 44 if (!ao) 45 return NULL; 46 47 hlist_for_each_entry_rcu(key, &ao->head, node) { 48 if (key->sndid == keyid) 49 return key; 50 } 51 return NULL; 52 } 53 54 static inline int ipv4_prefix_cmp(const struct in_addr *addr1, 55 const struct in_addr *addr2, 56 unsigned int prefixlen) 57 { 58 __be32 mask = inet_make_mask(prefixlen); 59 60 if ((addr1->s_addr & mask) == (addr2->s_addr & mask)) 61 return 0; 62 return ((addr1->s_addr & mask) > (addr2->s_addr & mask)) ? 1 : -1; 63 } 64 65 static int __tcp_ao_key_cmp(const struct tcp_ao_key *key, 66 const union tcp_ao_addr *addr, u8 prefixlen, 67 int family, int sndid, int rcvid, u16 port) 68 { 69 if (sndid >= 0 && key->sndid != sndid) 70 return (key->sndid > sndid) ? 1 : -1; 71 if (rcvid >= 0 && key->rcvid != rcvid) 72 return (key->rcvid > rcvid) ? 1 : -1; 73 if (port != 0 && key->port != 0 && port != key->port) 74 return (key->port > port) ? 1 : -1; 75 76 if (family == AF_UNSPEC) 77 return 0; 78 if (key->family != family) 79 return (key->family > family) ? 1 : -1; 80 81 if (family == AF_INET) { 82 if (key->addr.a4.s_addr == INADDR_ANY) 83 return 0; 84 if (addr->a4.s_addr == INADDR_ANY) 85 return 0; 86 return ipv4_prefix_cmp(&key->addr.a4, &addr->a4, prefixlen); 87 } else { 88 if (ipv6_addr_any(&key->addr.a6) || ipv6_addr_any(&addr->a6)) 89 return 0; 90 if (ipv6_prefix_equal(&key->addr.a6, &addr->a6, prefixlen)) 91 return 0; > 92 return memcmp(&key->addr.a6, &addr->a6, prefixlen); 93 } 94 } 95 > 96 int tcp_ao_key_cmp(const struct tcp_ao_key *key, 97 const union tcp_ao_addr *addr, u8 prefixlen, 98 int family, int sndid, int rcvid, u16 port) 99 { 100 if (family == AF_INET6 && ipv6_addr_v4mapped(&addr->a6)) { > 101 __be32 addr4 = addr->a6.s6_addr32[3]; 102 103 return __tcp_ao_key_cmp(key, (union tcp_ao_addr *)&addr4, 104 prefixlen, AF_INET, sndid, rcvid, port); 105 } 106 return __tcp_ao_key_cmp(key, addr, prefixlen, family, sndid, rcvid, port); 107 } 108 109 struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk, 110 const union tcp_ao_addr *addr, 111 int family, int sndid, int rcvid, u16 port) 112 { 113 struct tcp_ao_key *key; 114 struct tcp_ao_info *ao; 115 116 ao = rcu_dereference_check(tcp_sk(sk)->ao_info, 117 lockdep_sock_is_held(sk)); 118 if (!ao) 119 return NULL; 120 121 hlist_for_each_entry_rcu(key, &ao->head, node) { 122 if (!tcp_ao_key_cmp(key, addr, key->prefixlen, 123 family, sndid, rcvid, port)) 124 return key; 125 } 126 return NULL; 127 } 128 EXPORT_SYMBOL(tcp_ao_do_lookup); 129 130 static struct tcp_ao_info *tcp_ao_alloc_info(gfp_t flags, 131 struct tcp_ao_info *cloned_from) 132 { 133 struct tcp_ao_info *ao; 134 135 ao = kzalloc(sizeof(*ao), flags); 136 if (!ao) 137 return NULL; 138 INIT_HLIST_HEAD(&ao->head); 139 140 if (cloned_from) 141 ao->ao_flags = cloned_from->ao_flags; 142 return ao; 143 } 144 > 145 void tcp_ao_link_mkt(struct tcp_ao_info *ao, struct tcp_ao_key *mkt) 146 { 147 hlist_add_head_rcu(&mkt->node, &ao->head); 148 } 149
Hi Dmitry, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on e34cfee65ec891a319ce79797dda18083af33a76] url: https://github.com/intel-lab-lkp/linux/commits/Dmitry-Safonov/net-tcp-Add-TCP-AO-support/20220819-010628 base: e34cfee65ec891a319ce79797dda18083af33a76 config: x86_64-defconfig (https://download.01.org/0day-ci/archive/20220819/202208190249.8CCVAMql-lkp@intel.com/config) compiler: gcc-11 (Debian 11.3.0-5) 11.3.0 reproduce (this is a W=1 build): # https://github.com/intel-lab-lkp/linux/commit/469bd71e5ea011f6ae5a1554b75157471448341d git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Dmitry-Safonov/net-tcp-Add-TCP-AO-support/20220819-010628 git checkout 469bd71e5ea011f6ae5a1554b75157471448341d # save the config file mkdir build_dir && cp config build_dir/.config make W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash net/ipv4/ net/ipv6/ If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot <lkp@intel.com> All warnings (new ones prefixed by >>): >> net/ipv4/tcp_ao.c:19:20: warning: no previous prototype for 'tcp_ao_do_lookup_rcvid' [-Wmissing-prototypes] 19 | struct tcp_ao_key *tcp_ao_do_lookup_rcvid(struct sock *sk, u8 keyid) | ^~~~~~~~~~~~~~~~~~~~~~ net/ipv4/tcp_ao.c:37:20: warning: no previous prototype for 'tcp_ao_do_lookup_sndid' [-Wmissing-prototypes] 37 | struct tcp_ao_key *tcp_ao_do_lookup_sndid(const struct sock *sk, u8 keyid) | ^~~~~~~~~~~~~~~~~~~~~~ >> net/ipv4/tcp_ao.c:96:5: warning: no previous prototype for 'tcp_ao_key_cmp' [-Wmissing-prototypes] 96 | int tcp_ao_key_cmp(const struct tcp_ao_key *key, | ^~~~~~~~~~~~~~ net/ipv4/tcp_ao.c:109:20: warning: no previous prototype for 'tcp_ao_do_lookup' [-Wmissing-prototypes] 109 | struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk, | ^~~~~~~~~~~~~~~~ >> net/ipv4/tcp_ao.c:145:6: warning: no previous prototype for 'tcp_ao_link_mkt' [-Wmissing-prototypes] 145 | void tcp_ao_link_mkt(struct tcp_ao_info *ao, struct tcp_ao_key *mkt) | ^~~~~~~~~~~~~~~ vim +/tcp_ao_do_lookup_rcvid +19 net/ipv4/tcp_ao.c 18 > 19 struct tcp_ao_key *tcp_ao_do_lookup_rcvid(struct sock *sk, u8 keyid) 20 { 21 struct tcp_sock *tp = tcp_sk(sk); 22 struct tcp_ao_key *key; 23 struct tcp_ao_info *ao; 24 25 ao = rcu_dereference_check(tp->ao_info, lockdep_sock_is_held(sk)); 26 27 if (!ao) 28 return NULL; 29 30 hlist_for_each_entry_rcu(key, &ao->head, node) { 31 if (key->rcvid == keyid) 32 return key; 33 } 34 return NULL; 35 } 36 37 struct tcp_ao_key *tcp_ao_do_lookup_sndid(const struct sock *sk, u8 keyid) 38 { 39 struct tcp_ao_key *key; 40 struct tcp_ao_info *ao; 41 42 ao = rcu_dereference_check(tcp_sk(sk)->ao_info, 43 lockdep_sock_is_held(sk)); 44 if (!ao) 45 return NULL; 46 47 hlist_for_each_entry_rcu(key, &ao->head, node) { 48 if (key->sndid == keyid) 49 return key; 50 } 51 return NULL; 52 } 53 54 static inline int ipv4_prefix_cmp(const struct in_addr *addr1, 55 const struct in_addr *addr2, 56 unsigned int prefixlen) 57 { 58 __be32 mask = inet_make_mask(prefixlen); 59 60 if ((addr1->s_addr & mask) == (addr2->s_addr & mask)) 61 return 0; 62 return ((addr1->s_addr & mask) > (addr2->s_addr & mask)) ? 1 : -1; 63 } 64 65 static int __tcp_ao_key_cmp(const struct tcp_ao_key *key, 66 const union tcp_ao_addr *addr, u8 prefixlen, 67 int family, int sndid, int rcvid, u16 port) 68 { 69 if (sndid >= 0 && key->sndid != sndid) 70 return (key->sndid > sndid) ? 1 : -1; 71 if (rcvid >= 0 && key->rcvid != rcvid) 72 return (key->rcvid > rcvid) ? 1 : -1; 73 if (port != 0 && key->port != 0 && port != key->port) 74 return (key->port > port) ? 1 : -1; 75 76 if (family == AF_UNSPEC) 77 return 0; 78 if (key->family != family) 79 return (key->family > family) ? 1 : -1; 80 81 if (family == AF_INET) { 82 if (key->addr.a4.s_addr == INADDR_ANY) 83 return 0; 84 if (addr->a4.s_addr == INADDR_ANY) 85 return 0; 86 return ipv4_prefix_cmp(&key->addr.a4, &addr->a4, prefixlen); 87 } else { 88 if (ipv6_addr_any(&key->addr.a6) || ipv6_addr_any(&addr->a6)) 89 return 0; 90 if (ipv6_prefix_equal(&key->addr.a6, &addr->a6, prefixlen)) 91 return 0; 92 return memcmp(&key->addr.a6, &addr->a6, prefixlen); 93 } 94 } 95 > 96 int tcp_ao_key_cmp(const struct tcp_ao_key *key, 97 const union tcp_ao_addr *addr, u8 prefixlen, 98 int family, int sndid, int rcvid, u16 port) 99 { 100 if (family == AF_INET6 && ipv6_addr_v4mapped(&addr->a6)) { 101 __be32 addr4 = addr->a6.s6_addr32[3]; 102 103 return __tcp_ao_key_cmp(key, (union tcp_ao_addr *)&addr4, 104 prefixlen, AF_INET, sndid, rcvid, port); 105 } 106 return __tcp_ao_key_cmp(key, addr, prefixlen, family, sndid, rcvid, port); 107 } 108 109 struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk, 110 const union tcp_ao_addr *addr, 111 int family, int sndid, int rcvid, u16 port) 112 { 113 struct tcp_ao_key *key; 114 struct tcp_ao_info *ao; 115 116 ao = rcu_dereference_check(tcp_sk(sk)->ao_info, 117 lockdep_sock_is_held(sk)); 118 if (!ao) 119 return NULL; 120 121 hlist_for_each_entry_rcu(key, &ao->head, node) { 122 if (!tcp_ao_key_cmp(key, addr, key->prefixlen, 123 family, sndid, rcvid, port)) 124 return key; 125 } 126 return NULL; 127 } 128 EXPORT_SYMBOL(tcp_ao_do_lookup); 129 130 static struct tcp_ao_info *tcp_ao_alloc_info(gfp_t flags, 131 struct tcp_ao_info *cloned_from) 132 { 133 struct tcp_ao_info *ao; 134 135 ao = kzalloc(sizeof(*ao), flags); 136 if (!ao) 137 return NULL; 138 INIT_HLIST_HEAD(&ao->head); 139 140 if (cloned_from) 141 ao->ao_flags = cloned_from->ao_flags; 142 return ao; 143 } 144 > 145 void tcp_ao_link_mkt(struct tcp_ao_info *ao, struct tcp_ao_key *mkt) 146 { 147 hlist_add_head_rcu(&mkt->node, &ao->head); 148 } 149
On 8/18/22 19:59, Dmitry Safonov wrote: > Add 3 setsockopt()s: > 1. to add a new Master Key Tuple (MKT) on a socket > 2. to delete present MKT from a socket > 3. to change flags of an MKT > > Userspace has to introduce keys on every socket it wants to use TCP-AO > option on, similarly to TCP_MD5SIG/TCP_MD5SIG_EXT. > RFC5925 prohibits definition of MKTs that would match the same peer, > so do sanity checks on the data provided by userspace. Be as > conservative as possible, including refusal of defining MKT on > an established connection with no AO, removing the key in-use and etc. > > (1) and (2) are to be used by userspace key manager to add/remove keys. > (3) main purpose is to set rnext_key, which (as prescribed by RFC5925) > is the key id that will be requested in TCP-AO header from the peer to > sign their segments with. > > At this moment the life of ao_info ends in tcp_v4_destroy_sock(). > +#define TCP_AO 38 /* (Add/Set MKT) */ > +#define TCP_AO_DEL 39 /* (Delete MKT) */ > +#define TCP_AO_MOD 40 /* (Modify MKT) */ The TCP_AO_MOD sockopt doesn't actually modify and MKT, it only controls per-socket properties. It is equivalent to my TCP_AUTHOPT sockopt while TCP_AO is equivalent to TCP_AUTHOPT_KEY. My equivalent of TCP_AO_DEL sockopt is a flag inside tcp_authopt_key. > +struct tcp_ao { /* setsockopt(TCP_AO) */ > + struct __kernel_sockaddr_storage tcpa_addr; > + char tcpa_alg_name[64]; > + __u16 tcpa_flags; This field accept TCP_AO_CMDF_CURR and TCP_AO_CMDF_NEXT which means that you are combining key addition with key selection. Not clear it shouldn't just always be a separate sockopt? > + __u8 tcpa_prefix; > + __u8 tcpa_sndid; > + __u8 tcpa_rcvid; > + __u8 tcpa_maclen; > + __u8 tcpa_keyflags; > + __u8 tcpa_keylen; > + __u8 tcpa_key[TCP_AO_MAXKEYLEN]; > +} __attribute__((aligned(8))); > + > +struct tcp_ao_del { /* setsockopt(TCP_AO_DEL) */ > + struct __kernel_sockaddr_storage tcpa_addr; > + __u16 tcpa_flags; > + __u8 tcpa_prefix; > + __u8 tcpa_sndid; > + __u8 tcpa_rcvid; > + __u8 tcpa_current; > + __u8 tcpa_rnext; > +} __attribute__((aligned(8))); > + > +struct tcp_ao_mod { /* setsockopt(TCP_AO_MOD) */ > + __u16 tcpa_flags; > + __u8 tcpa_current; > + __u8 tcpa_rnext; > +} __attribute__((aligned(8))); This is quite similar to my "struct tcp_authopt" in the fact that it is intented to support controlling the "current keys". * tcpa_current is equivalent to send_keyid * tcpa_rnext is equivalent to send_rnextkeyid I also have two fields called "recv_keyid" and "recv_rnextkeyid" which inform userspace about what the remote is sending, I'm not seeing an equivalent on your side. The specification around send_keyid in the RFC is conflicting: * User must be able to control it * Implementation must respect rnextkeyid in incoming packet I solved this apparent conflict by adding a "TCP_AUTHOPT_FLAG_LOCK_KEYID" flag so that user can choose if it wants to control the sending key or let it be controlled from the other side. The "send_rnextkeyid" is also optional in my patch, if TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID is not passed then the recv_id of the sending key is sent. Here's a link to my implementation of key selection controls: https://lore.kernel.org/netdev/2956d99e7fbf9ff2a8cc720c67baaef35bc32343.1660852705.git.cdleonard@gmail.com/ > +static int tcp_ao_parse_crypto(struct tcp_ao *cmd, struct tcp_ao_key *key) > +{ > + unsigned int syn_tcp_option_space; > + struct crypto_pool_ahash hp; > + bool is_kdf_aes_128_cmac = false; > + struct crypto_ahash *tfm; > + int err, pool_id; > + > + /* Force null-termination of tcpa_alg_name */ > + cmd->tcpa_alg_name[ARRAY_SIZE(cmd->tcpa_alg_name) - 1] = '\0'; > + > + /* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */ > + if (!strcmp("cmac(aes128)", cmd->tcpa_alg_name)) { > + strcpy(cmd->tcpa_alg_name, "cmac(aes)"); > + is_kdf_aes_128_cmac = (cmd->tcpa_keylen != 16); > + } Only two algorithms are defined in RFC5926 and you have to treat one of them as a special case. I remain convinced that generic support for arbitrary algorithms is undesirable; it's better for the algorithm to be specified as an enum. -- Regards, Leonard
On 8/18/22 9:59 AM, Dmitry Safonov wrote: > diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h > index 849bbf2d3c38..5369458ae89f 100644 > --- a/include/uapi/linux/tcp.h > +++ b/include/uapi/linux/tcp.h > @@ -129,6 +129,9 @@ enum { > > #define TCP_TX_DELAY 37 /* delay outgoing packets by XX usec */ > > +#define TCP_AO 38 /* (Add/Set MKT) */ > +#define TCP_AO_DEL 39 /* (Delete MKT) */ > +#define TCP_AO_MOD 40 /* (Modify MKT) */ > > #define TCP_REPAIR_ON 1 > #define TCP_REPAIR_OFF 0 > @@ -344,6 +347,38 @@ struct tcp_diag_md5sig { > > #define TCP_AO_MAXKEYLEN 80 > > +#define TCP_AO_CMDF_CURR (1 << 0) /* Only checks field sndid */ > +#define TCP_AO_CMDF_NEXT (1 << 1) /* Only checks field rcvid */ > + > +struct tcp_ao { /* setsockopt(TCP_AO) */ > + struct __kernel_sockaddr_storage tcpa_addr; > + char tcpa_alg_name[64]; > + __u16 tcpa_flags; > + __u8 tcpa_prefix; > + __u8 tcpa_sndid; > + __u8 tcpa_rcvid; > + __u8 tcpa_maclen; > + __u8 tcpa_keyflags; > + __u8 tcpa_keylen; > + __u8 tcpa_key[TCP_AO_MAXKEYLEN]; > +} __attribute__((aligned(8))); > + > +struct tcp_ao_del { /* setsockopt(TCP_AO_DEL) */ > + struct __kernel_sockaddr_storage tcpa_addr; > + __u16 tcpa_flags; > + __u8 tcpa_prefix; > + __u8 tcpa_sndid; > + __u8 tcpa_rcvid; > + __u8 tcpa_current; > + __u8 tcpa_rnext; > +} __attribute__((aligned(8))); > + > +struct tcp_ao_mod { /* setsockopt(TCP_AO_MOD) */ > + __u16 tcpa_flags; > + __u8 tcpa_current; > + __u8 tcpa_rnext; > +} __attribute__((aligned(8))); > + > /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */ > > #define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1 I do not see anything in the uapi that would specify the VRF for the address.
From: David Ahern > Sent: 25 August 2022 16:32 > > On 8/18/22 9:59 AM, Dmitry Safonov wrote: > > diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h > > index 849bbf2d3c38..5369458ae89f 100644 > > --- a/include/uapi/linux/tcp.h > > +++ b/include/uapi/linux/tcp.h > > @@ -129,6 +129,9 @@ enum { > > > > #define TCP_TX_DELAY 37 /* delay outgoing packets by XX usec */ > > > > +#define TCP_AO 38 /* (Add/Set MKT) */ > > +#define TCP_AO_DEL 39 /* (Delete MKT) */ > > +#define TCP_AO_MOD 40 /* (Modify MKT) */ > > > > #define TCP_REPAIR_ON 1 > > #define TCP_REPAIR_OFF 0 > > @@ -344,6 +347,38 @@ struct tcp_diag_md5sig { > > > > #define TCP_AO_MAXKEYLEN 80 > > > > +#define TCP_AO_CMDF_CURR (1 << 0) /* Only checks field sndid */ > > +#define TCP_AO_CMDF_NEXT (1 << 1) /* Only checks field rcvid */ > > + > > +struct tcp_ao { /* setsockopt(TCP_AO) */ > > + struct __kernel_sockaddr_storage tcpa_addr; > > + char tcpa_alg_name[64]; > > + __u16 tcpa_flags; > > + __u8 tcpa_prefix; > > + __u8 tcpa_sndid; > > + __u8 tcpa_rcvid; > > + __u8 tcpa_maclen; > > + __u8 tcpa_keyflags; > > + __u8 tcpa_keylen; > > + __u8 tcpa_key[TCP_AO_MAXKEYLEN]; > > +} __attribute__((aligned(8))); > > + > > +struct tcp_ao_del { /* setsockopt(TCP_AO_DEL) */ > > + struct __kernel_sockaddr_storage tcpa_addr; > > + __u16 tcpa_flags; > > + __u8 tcpa_prefix; > > + __u8 tcpa_sndid; > > + __u8 tcpa_rcvid; > > + __u8 tcpa_current; > > + __u8 tcpa_rnext; > > +} __attribute__((aligned(8))); > > + > > +struct tcp_ao_mod { /* setsockopt(TCP_AO_MOD) */ > > + __u16 tcpa_flags; > > + __u8 tcpa_current; > > + __u8 tcpa_rnext; > > +} __attribute__((aligned(8))); > > + > > /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */ > > > > #define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1 > > > I do not see anything in the uapi that would specify the VRF for the > address. (Having not spotted the original...) You've also got implicit padding in the API structures. That is generally a recipe for disaster. David - Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK Registration No: 1397386 (Wales)
On 8/23/22 15:45, Leonard Crestez wrote: > On 8/18/22 19:59, Dmitry Safonov wrote: [..] >> +#define TCP_AO 38 /* (Add/Set MKT) */ >> +#define TCP_AO_DEL 39 /* (Delete MKT) */ >> +#define TCP_AO_MOD 40 /* (Modify MKT) */ > > The TCP_AO_MOD sockopt doesn't actually modify and MKT, it only controls > per-socket properties. It is equivalent to my TCP_AUTHOPT sockopt while > TCP_AO is equivalent to TCP_AUTHOPT_KEY. My equivalent of TCP_AO_DEL > sockopt is a flag inside tcp_authopt_key. Fair point, the comment could be "Modify AO", rather than "Modify MKT". On the other side, this can later support more per-key changes than in the initial proposal: i.e., zero per-key counters. Password and rcv/snd ids can't change to follow RFC text, but non-essentials may. So, the comment to the command here is not really incorrect. >> +struct tcp_ao { /* setsockopt(TCP_AO) */ >> + struct __kernel_sockaddr_storage tcpa_addr; >> + char tcpa_alg_name[64]; >> + __u16 tcpa_flags; > > This field accept TCP_AO_CMDF_CURR and TCP_AO_CMDF_NEXT which means that > you are combining key addition with key selection. Not clear it > shouldn't just always be a separate sockopt? I don't see any downside. A user can add a key and start using it immediately with one syscall instead of two. It's not necessary, one can do it in 2 setsockopt()s if they want. [..] > I also have two fields called "recv_keyid" and "recv_rnextkeyid" which > inform userspace about what the remote is sending, I'm not seeing an > equivalent on your side. Sounds like a good candidate for getsockopt() for logs/debugging. > The specification around send_keyid in the RFC is conflicting: > * User must be able to control it I don't see where you read it, care to point it out? I see choosing the current_key by marking the preferred key during an establishment of a connection, but I don't see any "MUST control current_key". We allow changing current_key, but that's actually not something required by RFC, the only thing required is to respect rnext_key that's asked by peer. > * Implementation must respect rnextkeyid in incoming packet > > I solved this apparent conflict by adding a > "TCP_AUTHOPT_FLAG_LOCK_KEYID" flag so that user can choose if it wants > to control the sending key or let it be controlled from the other side. That's exactly violating the above "Implementation must respect rnextkeyid in incoming packet". See RFC5925 (7.5.2.e). [..] > Only two algorithms are defined in RFC5926 and you have to treat one of > them as a special case. I remain convinced that generic support for > arbitrary algorithms is undesirable; it's better for the algorithm to be > specified as an enum. On contrary, I see that as a really big feature. RFC5926 was published in 2010, when sha1 was yet hard to break. These days sha1 is considered insecure. I.e., the first link from Google: > Starting with version 56, released this month, Google Chrome will mark all > SHA-1-signed HTTPS certificates as unsafe. Other major browser vendors > plan to do the same. > "Hopefully these new efforts of Google of making a real-world attack possible > will lead to vendors and infrastructure managers quickly removing SHA-1 from > their products and configurations as, despite it being a deprecated algorithm, > some vendors still sell products that do not support more modern hashing > algorithms or charge an extra cost to do so," [..] So, why limit a new TCP sign feature to already insecure algorithms? One can already use any crypto algorithms for example, in tunnels. And I don't see any benefit in defining new magic macros, only downside. I prefer UAPI that takes crypto algo name as a string, rather than new defined magic number from one of kernel headers. IOW, : strcpy(ao.tcpa_alg_name, "cmac(aes128)"); : setsockopt(sk, IPPROTO_TCP, opt, &ao, sizeof(ao)); is better than : ao.tcp_alg = TCP_AO_CMAC_MAGIC_DEFINE; : setsockopt(sk, IPPROTO_TCP, opt, &ao, sizeof(ao)); Neither I see a point in more patches adding new #define TCP_AO_NEW_ALGO BTW, I had some patches to add testing in fcnal-test.sh and covered the following algorithms, that worked just fine (test changes not included in v1): hmac(sha1) cmac(aes128) hmac(rmd128) hmac(rmd160) hmac(sha512) hmac(sha384) hmac(sha256) hmac(md5) hmac(sha224) hmac(sha3-512) No point in artificially disabling them or introducing new magic #defines. Thanks, Dmitry
On 8/31/22 21:48, Dmitry Safonov wrote: > On 8/23/22 15:45, Leonard Crestez wrote: >> On 8/18/22 19:59, Dmitry Safonov wrote: > [..] >>> +#define TCP_AO 38 /* (Add/Set MKT) */ >>> +#define TCP_AO_DEL 39 /* (Delete MKT) */ >>> +#define TCP_AO_MOD 40 /* (Modify MKT) */ >> >> The TCP_AO_MOD sockopt doesn't actually modify and MKT, it only controls >> per-socket properties. It is equivalent to my TCP_AUTHOPT sockopt while >> TCP_AO is equivalent to TCP_AUTHOPT_KEY. My equivalent of TCP_AO_DEL >> sockopt is a flag inside tcp_authopt_key. > > Fair point, the comment could be "Modify AO", rather than "Modify MKT". > On the other side, this can later support more per-key changes than in > the initial proposal: i.e., zero per-key counters. Password and rcv/snd > ids can't change to follow RFC text, but non-essentials may. > So, the comment to the command here is not really incorrect. I think it makes sense to at least separate per-key and per-socket options. This way a sockopt for per-socket info doesn't contain fields used to identify keys which is much clearer. >> I also have two fields called "recv_keyid" and "recv_rnextkeyid" which >> inform userspace about what the remote is sending, I'm not seeing an >> equivalent on your side. > > Sounds like a good candidate for getsockopt() for logs/debugging. > >> The specification around send_keyid in the RFC is conflicting: >> * User must be able to control it > > I don't see where you read it, care to point it out? > I see choosing the current_key by marking the preferred key during > an establishment of a connection, but I don't see any "MUST control > current_key". We allow changing current_key, but that's actually > not something required by RFC, the only thing required is to respect > rnext_key that's asked by peer. > >> * Implementation must respect rnextkeyid in incoming packet >> >> I solved this apparent conflict by adding a >> "TCP_AUTHOPT_FLAG_LOCK_KEYID" flag so that user can choose if it wants >> to control the sending key or let it be controlled from the other side. > > That's exactly violating the above "Implementation must respect > rnextkeyid in incoming packet". See RFC5925 (7.5.2.e). This is based on paragraphs towards the end of Section 7.1: >> TCP SEND, or a sequence of commands resulting in a SEND, MUST be augmented so that the preferred outgoing MKT (current_key) and/or the preferred incoming MKT (rnext_key) of a connection can be indicated. This is for TCP SEND, not just open/connect. I'm reading this as a requirement that userspace *MUST* be able to control the current key. Yes, it does seem contradict 7.5.2.e which is why I implemented this as a "key lock flag". >> TCP RECEIVE, or the sequence of commands resulting in a RECEIVE, MUST be augmented so that the KeyID and RNextKeyID of a recently received segment is available to the user out of band (e.g., as an additional parameter to RECEIVE or via a STATUS call). It seems to me that it *MUST* be possible for userspace to read the incoming rnextkeyid and handle it by itself. It could choose to follow 7.5.2.e or it could do something entirely different. When it can't respect rnextkeyid because the key is not yet valid then userspace has more information to make an alternative current_key decision. > > [..] >> Only two algorithms are defined in RFC5926 and you have to treat one of >> them as a special case. I remain convinced that generic support for >> arbitrary algorithms is undesirable; it's better for the algorithm to be >> specified as an enum. > > So, why limit a new TCP sign feature to already insecure algorithms? > One can already use any crypto algorithms for example, in tunnels. > And I don't see any benefit in defining new magic macros, only downside. Adding support for arbitrary algorithms increases complexity for no real-world gain. There are also lots of corner cases that must be treated correctly like odd traffic_keylen and maclen, having an enum means that userspace can't attempt to trick us. The ABI is also smaller. There's also a special case in one of the two concrete KDFs defined by RFC5925. What if there are more, will the ABI be expanded to support all the cases? Disagreements over whether a particular form of extensibility is "useful" are unlikely to result in any sort of useful conclusion. I'm lazy so I only care about interop with existing implementations from Juniper and Cisco. -- Regards, Leonard
diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h index d45902fb4cad..f42575ffda53 100644 --- a/include/linux/sockptr.h +++ b/include/linux/sockptr.h @@ -55,6 +55,29 @@ static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size) return copy_from_sockptr_offset(dst, src, 0, size); } +static inline int copy_struct_from_sockptr(void *dst, size_t ksize, + sockptr_t src, size_t usize) +{ + size_t size = min(ksize, usize); + size_t rest = max(ksize, usize) - size; + + if (!sockptr_is_kernel(src)) + return copy_struct_from_user(dst, ksize, src.user, size); + + if (usize < ksize) { + memset(dst + size, 0, rest); + } else if (usize > ksize) { + char *p = src.kernel; + + while (rest--) { + if (*p++) + return -E2BIG; + } + } + memcpy(dst, src.kernel, size); + return 0; +} + static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, const void *src, size_t size) { diff --git a/include/net/tcp.h b/include/net/tcp.h index b4b009094bf6..278d0ab81796 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -2054,6 +2054,12 @@ struct tcp_sock_af_ops { sockptr_t optval, int optlen); #endif +#ifdef CONFIG_TCP_AO + int (*ao_parse)(struct sock *sk, + int optname, + sockptr_t optval, + int optlen); +#endif }; struct tcp_request_sock_ops { diff --git a/include/net/tcp_ao.h b/include/net/tcp_ao.h index 39b3fc31e5a1..6d0d30e5542b 100644 --- a/include/net/tcp_ao.h +++ b/include/net/tcp_ao.h @@ -87,4 +87,19 @@ struct tcp_ao_info { u32 rcv_sne_seq; }; +#ifdef CONFIG_TCP_AO +int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family, + sockptr_t optval, int optlen); +void tcp_ao_destroy_sock(struct sock *sk); +/* ipv4 specific functions */ +int tcp_v4_parse_ao(struct sock *sk, int optname, sockptr_t optval, int optlen); +/* ipv6 specific functions */ +int tcp_v6_parse_ao(struct sock *sk, int cmd, + sockptr_t optval, int optlen); +#else +static inline void tcp_ao_destroy_sock(struct sock *sk) +{ +} +#endif + #endif /* _TCP_AO_H */ diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h index 849bbf2d3c38..5369458ae89f 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -129,6 +129,9 @@ enum { #define TCP_TX_DELAY 37 /* delay outgoing packets by XX usec */ +#define TCP_AO 38 /* (Add/Set MKT) */ +#define TCP_AO_DEL 39 /* (Delete MKT) */ +#define TCP_AO_MOD 40 /* (Modify MKT) */ #define TCP_REPAIR_ON 1 #define TCP_REPAIR_OFF 0 @@ -344,6 +347,38 @@ struct tcp_diag_md5sig { #define TCP_AO_MAXKEYLEN 80 +#define TCP_AO_CMDF_CURR (1 << 0) /* Only checks field sndid */ +#define TCP_AO_CMDF_NEXT (1 << 1) /* Only checks field rcvid */ + +struct tcp_ao { /* setsockopt(TCP_AO) */ + struct __kernel_sockaddr_storage tcpa_addr; + char tcpa_alg_name[64]; + __u16 tcpa_flags; + __u8 tcpa_prefix; + __u8 tcpa_sndid; + __u8 tcpa_rcvid; + __u8 tcpa_maclen; + __u8 tcpa_keyflags; + __u8 tcpa_keylen; + __u8 tcpa_key[TCP_AO_MAXKEYLEN]; +} __attribute__((aligned(8))); + +struct tcp_ao_del { /* setsockopt(TCP_AO_DEL) */ + struct __kernel_sockaddr_storage tcpa_addr; + __u16 tcpa_flags; + __u8 tcpa_prefix; + __u8 tcpa_sndid; + __u8 tcpa_rcvid; + __u8 tcpa_current; + __u8 tcpa_rnext; +} __attribute__((aligned(8))); + +struct tcp_ao_mod { /* setsockopt(TCP_AO_MOD) */ + __u16 tcpa_flags; + __u8 tcpa_current; + __u8 tcpa_rnext; +} __attribute__((aligned(8))); + /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */ #define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1 diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile index bbdd9c44f14e..6d0b3e228b8a 100644 --- a/net/ipv4/Makefile +++ b/net/ipv4/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NETLABEL) += cipso_ipv4.o obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \ xfrm4_output.o xfrm4_protocol.o +obj-$(CONFIG_TCP_AO) += tcp_ao.o ifeq ($(CONFIG_BPF_JIT),y) obj-$(CONFIG_BPF_SYSCALL) += bpf_tcp_ca.o diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 7c54b47e848f..85854b8afc47 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -3704,6 +3704,23 @@ static int do_tcp_setsockopt(struct sock *sk, int level, int optname, __tcp_sock_set_quickack(sk, val); break; +#ifdef CONFIG_TCP_AO + case TCP_AO: + case TCP_AO_DEL: + case TCP_AO_MOD: { + u32 state = (1 << sk->sk_state) & + (TCPF_CLOSE | TCPF_ESTABLISHED | TCPF_LISTEN); + + if (!state || (state == TCPF_ESTABLISHED && + !rcu_dereference_protected(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)))) + err = -EINVAL; + else + err = tp->af_specific->ao_parse(sk, optname, optval, + optlen); + break; + } +#endif #ifdef CONFIG_TCP_MD5SIG case TCP_MD5SIG: case TCP_MD5SIG_EXT: diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c new file mode 100644 index 000000000000..7f53417ebdf7 --- /dev/null +++ b/net/ipv4/tcp_ao.c @@ -0,0 +1,828 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * INET An implementation of the TCP Authentication Option (TCP-AO). + * See RFC5925. + * + * Authors: Dmitry Safonov <dima@arista.com> + * Francesco Ruggeri <fruggeri@arista.com> + * Salam Noureddine <noureddine@arista.com> + */ +#define pr_fmt(fmt) "TCP: " fmt + +#include <linux/inetdevice.h> +#include <linux/tcp.h> +#include <crypto/pool.h> + +#include <net/tcp.h> +#include <net/ipv6.h> + +struct tcp_ao_key *tcp_ao_do_lookup_rcvid(struct sock *sk, u8 keyid) +{ + struct tcp_sock *tp = tcp_sk(sk); + struct tcp_ao_key *key; + struct tcp_ao_info *ao; + + ao = rcu_dereference_check(tp->ao_info, lockdep_sock_is_held(sk)); + + if (!ao) + return NULL; + + hlist_for_each_entry_rcu(key, &ao->head, node) { + if (key->rcvid == keyid) + return key; + } + return NULL; +} + +struct tcp_ao_key *tcp_ao_do_lookup_sndid(const struct sock *sk, u8 keyid) +{ + struct tcp_ao_key *key; + struct tcp_ao_info *ao; + + ao = rcu_dereference_check(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)); + if (!ao) + return NULL; + + hlist_for_each_entry_rcu(key, &ao->head, node) { + if (key->sndid == keyid) + return key; + } + return NULL; +} + +static inline int ipv4_prefix_cmp(const struct in_addr *addr1, + const struct in_addr *addr2, + unsigned int prefixlen) +{ + __be32 mask = inet_make_mask(prefixlen); + + if ((addr1->s_addr & mask) == (addr2->s_addr & mask)) + return 0; + return ((addr1->s_addr & mask) > (addr2->s_addr & mask)) ? 1 : -1; +} + +static int __tcp_ao_key_cmp(const struct tcp_ao_key *key, + const union tcp_ao_addr *addr, u8 prefixlen, + int family, int sndid, int rcvid, u16 port) +{ + if (sndid >= 0 && key->sndid != sndid) + return (key->sndid > sndid) ? 1 : -1; + if (rcvid >= 0 && key->rcvid != rcvid) + return (key->rcvid > rcvid) ? 1 : -1; + if (port != 0 && key->port != 0 && port != key->port) + return (key->port > port) ? 1 : -1; + + if (family == AF_UNSPEC) + return 0; + if (key->family != family) + return (key->family > family) ? 1 : -1; + + if (family == AF_INET) { + if (key->addr.a4.s_addr == INADDR_ANY) + return 0; + if (addr->a4.s_addr == INADDR_ANY) + return 0; + return ipv4_prefix_cmp(&key->addr.a4, &addr->a4, prefixlen); + } else { + if (ipv6_addr_any(&key->addr.a6) || ipv6_addr_any(&addr->a6)) + return 0; + if (ipv6_prefix_equal(&key->addr.a6, &addr->a6, prefixlen)) + return 0; + return memcmp(&key->addr.a6, &addr->a6, prefixlen); + } +} + +int tcp_ao_key_cmp(const struct tcp_ao_key *key, + const union tcp_ao_addr *addr, u8 prefixlen, + int family, int sndid, int rcvid, u16 port) +{ + if (family == AF_INET6 && ipv6_addr_v4mapped(&addr->a6)) { + __be32 addr4 = addr->a6.s6_addr32[3]; + + return __tcp_ao_key_cmp(key, (union tcp_ao_addr *)&addr4, + prefixlen, AF_INET, sndid, rcvid, port); + } + return __tcp_ao_key_cmp(key, addr, prefixlen, family, sndid, rcvid, port); +} + +struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk, + const union tcp_ao_addr *addr, + int family, int sndid, int rcvid, u16 port) +{ + struct tcp_ao_key *key; + struct tcp_ao_info *ao; + + ao = rcu_dereference_check(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)); + if (!ao) + return NULL; + + hlist_for_each_entry_rcu(key, &ao->head, node) { + if (!tcp_ao_key_cmp(key, addr, key->prefixlen, + family, sndid, rcvid, port)) + return key; + } + return NULL; +} +EXPORT_SYMBOL(tcp_ao_do_lookup); + +static struct tcp_ao_info *tcp_ao_alloc_info(gfp_t flags, + struct tcp_ao_info *cloned_from) +{ + struct tcp_ao_info *ao; + + ao = kzalloc(sizeof(*ao), flags); + if (!ao) + return NULL; + INIT_HLIST_HEAD(&ao->head); + + if (cloned_from) + ao->ao_flags = cloned_from->ao_flags; + return ao; +} + +void tcp_ao_link_mkt(struct tcp_ao_info *ao, struct tcp_ao_key *mkt) +{ + hlist_add_head_rcu(&mkt->node, &ao->head); +} + +static void tcp_ao_key_free_rcu(struct rcu_head *head) +{ + struct tcp_ao_key *key = container_of(head, struct tcp_ao_key, rcu); + + crypto_pool_release(key->crypto_pool_id); + kfree(key); +} + +void tcp_ao_destroy_sock(struct sock *sk) +{ + struct tcp_ao_info *ao; + struct tcp_ao_key *key; + struct hlist_node *n; + + ao = rcu_dereference_protected(tcp_sk(sk)->ao_info, 1); + tcp_sk(sk)->ao_info = NULL; + + if (!ao) + return; + + hlist_for_each_entry_safe(key, n, &ao->head, node) { + hlist_del_rcu(&key->node); + atomic_sub(tcp_ao_sizeof_key(key), &sk->sk_omem_alloc); + call_rcu(&key->rcu, tcp_ao_key_free_rcu); + } + + kfree_rcu(ao, rcu); +} + +static int tcp_ao_current_rnext(struct sock *sk, u16 tcpa_flags, + u8 tcpa_sndid, u8 tcpa_rcvid) +{ + struct tcp_ao_info *ao_info; + struct tcp_ao_key *key; + + ao_info = rcu_dereference_protected(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)); + if ((tcpa_flags & (TCP_AO_CMDF_CURR | TCP_AO_CMDF_NEXT)) && !ao_info) + return -EINVAL; + + if (tcpa_flags & TCP_AO_CMDF_CURR) { + key = tcp_ao_do_lookup_sndid(sk, tcpa_sndid); + if (!key) + return -ENOENT; + if (ao_info->current_key != key) + WRITE_ONCE(ao_info->current_key, key); + } + + if (tcpa_flags & TCP_AO_CMDF_NEXT) { + key = tcp_ao_do_lookup_rcvid(sk, tcpa_rcvid); + if (!key) + return -ENOENT; + if (ao_info->rnext_key != key) + WRITE_ONCE(ao_info->rnext_key, key); + } + + return 0; +} + +static int tcp_ao_verify_port(struct sock *sk, u16 port) +{ + struct inet_sock *inet = inet_sk(sk); + + if (port != 0) /* FIXME */ + return -EINVAL; + + /* Check that MKT port is consistent with socket */ + if (port != 0 && inet->inet_dport != 0 && port != inet->inet_dport) + return -EINVAL; + + return 0; +} + +static int tcp_ao_verify_ipv4(struct sock *sk, struct tcp_ao *cmd, + union tcp_md5_addr **addr, u16 *port) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)&cmd->tcpa_addr; + struct inet_sock *inet = inet_sk(sk); + + if (sin->sin_family != AF_INET) + return -EINVAL; + + if (tcp_ao_verify_port(sk, ntohs(sin->sin_port))) + return -EINVAL; + + /* Check prefix and trailing 0's in addr */ + if (cmd->tcpa_prefix != 0) { + __be32 mask; + + if (sin->sin_addr.s_addr == INADDR_ANY) + return -EINVAL; + if (cmd->tcpa_prefix > 32) + return -EINVAL; + + mask = inet_make_mask(cmd->tcpa_prefix); + if (sin->sin_addr.s_addr & ~mask) + return -EINVAL; + + /* Check that MKT address is consistent with socket */ + if (inet->inet_daddr != INADDR_ANY && + (inet->inet_daddr & mask) != sin->sin_addr.s_addr) + return -EINVAL; + } else { + if (sin->sin_addr.s_addr != INADDR_ANY) + return -EINVAL; + } + + *addr = (union tcp_md5_addr *)&sin->sin_addr; + *port = ntohs(sin->sin_port); + return 0; +} + +static int tcp_ao_verify_ipv6(struct sock *sk, struct tcp_ao *cmd, + union tcp_md5_addr **paddr, u16 *port, + unsigned short int *family) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd->tcpa_addr; + struct in6_addr *addr = &sin6->sin6_addr; + u8 prefix = cmd->tcpa_prefix; + + if (sin6->sin6_family != AF_INET6) + return -EINVAL; + /* Not supposed to happen: here from af-specific callback */ + if (WARN_ON_ONCE(!sk_fullsock(sk))) + return -EINVAL; + + if (tcp_ao_verify_port(sk, ntohs(sin6->sin6_port))) + return -EINVAL; + + /* Check prefix and trailing 0's in addr */ + if (cmd->tcpa_prefix != 0 && ipv6_addr_v4mapped(addr)) { + __be32 addr4 = addr->s6_addr32[3]; + __be32 mask; + + if (prefix > 32 || addr4 == INADDR_ANY) + return -EINVAL; + + mask = inet_make_mask(prefix); + if (addr4 & ~mask) + return -EINVAL; + + /* Check that MKT address is consistent with socket */ + if (!ipv6_addr_any(&sk->sk_v6_daddr)) { + __be32 daddr4 = sk->sk_v6_daddr.s6_addr32[3]; + + if (!ipv6_addr_v4mapped(&sk->sk_v6_daddr)) + return -EINVAL; + if ((daddr4 & mask) != addr4) + return -EINVAL; + } + + *paddr = (union tcp_md5_addr *)&addr->s6_addr32[3]; + *family = AF_INET; + *port = ntohs(sin6->sin6_port); + return 0; + } else if (cmd->tcpa_prefix != 0) { + struct in6_addr pfx; + + if (ipv6_addr_any(addr) || prefix > 128) + return -EINVAL; + + ipv6_addr_prefix(&pfx, addr, prefix); + if (ipv6_addr_cmp(&pfx, addr)) + return -EINVAL; + + /* Check that MKT address is consistent with socket */ + if (!ipv6_addr_any(&sk->sk_v6_daddr) && + !ipv6_prefix_equal(&sk->sk_v6_daddr, addr, prefix)) + + return -EINVAL; + } else { + if (!ipv6_addr_any(addr)) + return -EINVAL; + } + + *paddr = (union tcp_md5_addr *)addr; + *port = ntohs(sin6->sin6_port); + return 0; +} + +static int tcp_ao_parse_crypto(struct tcp_ao *cmd, struct tcp_ao_key *key) +{ + unsigned int syn_tcp_option_space; + struct crypto_pool_ahash hp; + bool is_kdf_aes_128_cmac = false; + struct crypto_ahash *tfm; + int err, pool_id; + + /* Force null-termination of tcpa_alg_name */ + cmd->tcpa_alg_name[ARRAY_SIZE(cmd->tcpa_alg_name) - 1] = '\0'; + + /* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */ + if (!strcmp("cmac(aes128)", cmd->tcpa_alg_name)) { + strcpy(cmd->tcpa_alg_name, "cmac(aes)"); + is_kdf_aes_128_cmac = (cmd->tcpa_keylen != 16); + } + + key->maclen = cmd->tcpa_maclen ?: 12; /* 12 is the default in RFC5925 */ + + /* Check: maclen + tcp-ao header <= (MAX_TCP_OPTION_SPACE - mss + * - tstamp - wscale - sackperm), + * see tcp_syn_options(), tcp_synack_options(), commit 33ad798c924b. + * + * In order to allow D-SACK with TCP-AO, the header size should be: + * (MAX_TCP_OPTION_SPACE - TCPOLEN_TSTAMP_ALIGNED + * - TCPOLEN_SACK_BASE_ALIGNED + * - 2 * TCPOLEN_SACK_PERBLOCK) = 8 (maclen = 4), + * see tcp_established_options(). + * + * RFC5925, 2.2: + * Typical MACs are 96-128 bits (12-16 bytes), but any length + * that fits in the header of the segment being authenticated + * is allowed. + * + * RFC5925, 7.6: + * TCP-AO continues to consume 16 bytes in non-SYN segments, + * leaving a total of 24 bytes for other options, of which + * the timestamp consumes 10. This leaves 14 bytes, of which 10 + * are used for a single SACK block. When two SACK blocks are used, + * such as to handle D-SACK, a smaller TCP-AO MAC would be required + * to make room for the additional SACK block (i.e., to leave 18 + * bytes for the D-SACK variant of the SACK option) [RFC2883]. + * Note that D-SACK is not supportable in TCP MD5 in the presence + * of timestamps, because TCP MD5’s MAC length is fixed and too + * large to leave sufficient option space. + */ + syn_tcp_option_space = MAX_TCP_OPTION_SPACE; + syn_tcp_option_space -= TCPOLEN_TSTAMP_ALIGNED; + syn_tcp_option_space -= TCPOLEN_WSCALE_ALIGNED; + syn_tcp_option_space -= TCPOLEN_SACKPERM_ALIGNED; + if (tcp_ao_len(key) > syn_tcp_option_space) + return -EMSGSIZE; + + key->keylen = cmd->tcpa_keylen; + memcpy(key->key, cmd->tcpa_key, cmd->tcpa_keylen); + + pool_id = crypto_pool_alloc_ahash(cmd->tcpa_alg_name); + if (pool_id < 0) + return pool_id; + + if (is_kdf_aes_128_cmac) { + err = crypto_pool_reserve_scratch(16); + if (err) + goto err_free_pool; + } + + err = crypto_pool_get(pool_id, (struct crypto_pool *)&hp); + if (err) + goto err_free_pool; + + tfm = crypto_ahash_reqtfm(hp.req); + if (crypto_ahash_alignmask(tfm) > TCP_AO_KEY_ALIGN) { + err = -EOPNOTSUPP; + goto err_put_pool; + } + + if (is_kdf_aes_128_cmac) { + void *scratch = hp.base.scratch; + struct scatterlist sg; + + /* Using zero-key of 16 bytes as described in RFC5926 */ + memset(scratch, 0, 16); + sg_init_one(&sg, cmd->tcpa_key, cmd->tcpa_keylen); + + err = crypto_ahash_setkey(tfm, scratch, 16); + if (err) + goto err_put_pool; + + err = crypto_ahash_init(hp.req); + if (err) + goto err_put_pool; + + ahash_request_set_crypt(hp.req, &sg, key->key, cmd->tcpa_keylen); + err = crypto_ahash_update(hp.req); + if (err) + goto err_put_pool; + + err |= crypto_ahash_final(hp.req); + if (err) + goto err_put_pool; + key->keylen = 16; + } + + err = crypto_ahash_setkey(tfm, key->key, key->keylen); + if (err) + goto err_put_pool; + + key->digest_size = crypto_ahash_digestsize(tfm); + crypto_pool_put(); + + err = crypto_pool_reserve_scratch(sizeof(struct tcphdr) + + sizeof(struct tcp_ao_hdr) + + key->digest_size); + if (err) + goto err_free_pool; + + if (key->digest_size > TCP_AO_MAX_HASH_SIZE) { + err = -ENOBUFS; + goto err_free_pool; + } + if (key->maclen > key->digest_size) { + err = -EINVAL; + goto err_free_pool; + } + + key->crypto_pool_id = pool_id; + return 0; + +err_put_pool: + crypto_pool_put(); +err_free_pool: + crypto_pool_release(pool_id); + return err; +} + +/* tcp_ao_mkt_overlap_v4() assumes cmd already went through tcp_ao_verify_ipv4. + * RFC5925 3.1 The IDs of MKTs MUST NOT overlap where their TCP connection + * identifiers overlap. + */ +static bool tcp_ao_mkt_overlap_v4(struct tcp_ao *cmd, + struct tcp_ao_info *ao_info) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)&cmd->tcpa_addr; + __be32 addr = sin->sin_addr.s_addr; + __u8 prefix = cmd->tcpa_prefix; + __u16 port = ntohs(sin->sin_port); + __u8 sndid = cmd->tcpa_sndid; + __u8 rcvid = cmd->tcpa_rcvid; + struct tcp_ao_key *key; + + /* Check for TCP connection identifiers overlap */ + + hlist_for_each_entry_rcu(key, &ao_info->head, node) { + __be32 key_addr; + __be32 mask; + + /* Check for overlapping ids */ + if (key->sndid != sndid && key->rcvid != rcvid) + continue; + + key_addr = key->addr.a4.s_addr; + mask = inet_make_mask(min(prefix, key->prefixlen)); + + /* Check for overlapping addresses */ + if (addr == INADDR_ANY || key_addr == INADDR_ANY || + (addr & mask) == (key_addr & mask)) { + /* Check for overlapping ports */ + if (port == 0 || key->port == 0 || port == key->port) + return true; + } + } + + return false; +} + +/* tcp_ao_mkt_overlap_v6() assumes cmd already went through tcp_ao_verify_ipv6. + * RFC5925 3.1 The IDs of MKTs MUST NOT overlap where their TCP connection + * identifiers overlap. + */ +static bool tcp_ao_mkt_overlap_v6(struct tcp_ao *cmd, + struct tcp_ao_info *ao_info) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd->tcpa_addr; + struct in6_addr *addr = &sin6->sin6_addr; + bool v4_mapped = ipv6_addr_v4mapped(addr); + __u8 prefix = cmd->tcpa_prefix; + __u16 port = ntohs(sin6->sin6_port); + __u8 sndid = cmd->tcpa_sndid; + __u8 rcvid = cmd->tcpa_rcvid; + struct tcp_ao_key *key; + __be32 addr4 = v4_mapped ? addr->s6_addr32[3] : 0; + + hlist_for_each_entry_rcu(key, &ao_info->head, node) { + struct in6_addr pfx, key_pfx; + struct in6_addr *key_addr; + int min_prefixlen; + + /* Check for overlapping ids */ + if (key->sndid != sndid && key->rcvid != rcvid) + continue; + + key_addr = &key->addr.a6; + + if (v4_mapped) { + __be32 key_addr4; + __be32 mask; + + if (!ipv6_addr_v4mapped(key_addr)) + continue; + + key_addr4 = key_addr->s6_addr32[3]; + mask = inet_make_mask(min(prefix, key->prefixlen)); + + /* Check for overlapping addresses */ + if (addr4 == INADDR_ANY || key_addr4 == INADDR_ANY || + (addr4 & mask) == (key_addr4 & mask)) { + /* Check for overlapping ports */ + if (port == 0 || key->port == 0 || + port == key->port) + return true; + } + } else { + min_prefixlen = min(prefix, key->prefixlen); + ipv6_addr_prefix(&pfx, addr, min_prefixlen); + ipv6_addr_prefix(&key_pfx, key_addr, min_prefixlen); + + /* Check for overlapping addresses */ + if (ipv6_addr_any(addr) || ipv6_addr_any(key_addr) || + !ipv6_addr_cmp(&pfx, &key_pfx)) { + /* Check for overlapping ports */ + if (port == 0 || key->port == 0 || + port == key->port) + return true; + } + } + } + + return false; +} + +#define TCP_AO_KEYF_ALL (0) +#define TCP_AO_CMDF_ADDMOD_VALID \ + (TCP_AO_CMDF_CURR | TCP_AO_CMDF_NEXT) +#define TCP_AO_CMDF_DEL_VALID \ + (TCP_AO_CMDF_CURR | TCP_AO_CMDF_NEXT) + +static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family, + sockptr_t optval, int optlen) +{ + struct tcp_ao_info *ao_info; + union tcp_md5_addr *addr; + struct tcp_ao_key *key; + bool first = false; + struct tcp_ao cmd; + int ret, size; + u16 port; + + if (optlen < sizeof(cmd)) + return -EINVAL; + + ret = copy_struct_from_sockptr(&cmd, sizeof(cmd), optval, optlen); + if (ret) + return ret; + + if (cmd.tcpa_keylen > TCP_AO_MAXKEYLEN) + return -EINVAL; + + if (cmd.tcpa_flags & ~TCP_AO_CMDF_ADDMOD_VALID) + return -EINVAL; + + if (family == AF_INET) + ret = tcp_ao_verify_ipv4(sk, &cmd, &addr, &port); + else + ret = tcp_ao_verify_ipv6(sk, &cmd, &addr, &port, &family); + if (ret) + return ret; + + if (cmd.tcpa_keyflags & ~TCP_AO_KEYF_ALL) + return -EINVAL; + + ao_info = rcu_dereference_protected(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)); + + if (!ao_info) { + ao_info = tcp_ao_alloc_info(GFP_KERNEL, NULL); + if (!ao_info) + return -ENOMEM; + first = true; + } else { + if (family == AF_INET) { + if (tcp_ao_mkt_overlap_v4(&cmd, ao_info)) + return -EEXIST; + } else { + if (tcp_ao_mkt_overlap_v6(&cmd, ao_info)) + return -EEXIST; + } + } + + /* TODO: We should add twice the key->diget_size instead of the max + * so rework this in a way to know the digest_size before allocating + * the tcp_ao_key struct. + */ + size = sizeof(struct tcp_ao_key) + (TCP_AO_MAX_HASH_SIZE << 1); + key = sock_kmalloc(sk, size, GFP_KERNEL); + if (!key) { + ret = -ENOMEM; + goto err_free_ao; + } + + INIT_HLIST_NODE(&key->node); + memcpy(&key->addr, addr, (family == AF_INET) ? sizeof(struct in_addr) : + sizeof(struct in6_addr)); + key->port = port; + key->prefixlen = cmd.tcpa_prefix; + key->family = family; + key->keyflags = cmd.tcpa_keyflags; + key->sndid = cmd.tcpa_sndid; + key->rcvid = cmd.tcpa_rcvid; + + ret = tcp_ao_parse_crypto(&cmd, key); + if (ret < 0) + goto err_free_sock; + + tcp_ao_link_mkt(ao_info, key); + if (first) { + sk_gso_disable(sk); + rcu_assign_pointer(tcp_sk(sk)->ao_info, ao_info); + } + + /* Can't fail: the key with sndid/rcvid was just added */ + WARN_ON_ONCE(tcp_ao_current_rnext(sk, cmd.tcpa_flags, + cmd.tcpa_sndid, cmd.tcpa_rcvid)); + return 0; + +err_free_sock: + atomic_sub(tcp_ao_sizeof_key(key), &sk->sk_omem_alloc); + kfree(key); +err_free_ao: + if (first) + kfree(ao_info); + return ret; +} + +static int tcp_ao_delete_key(struct sock *sk, struct tcp_ao_key *key, + struct tcp_ao_info *ao_info, struct tcp_ao_del *cmd) +{ + int err; + + hlist_del_rcu(&key->node); + + /* At this moment another CPU could have looked this key up + * while it was unlinked from the list. Wait for RCU grace period, + * after which the key is off-list and can't be looked up again; + * the rx path [just before RCU came] might have used it and set it + * as current_key (very unlikely). + */ + synchronize_rcu(); + err = tcp_ao_current_rnext(sk, cmd->tcpa_flags, + cmd->tcpa_current, cmd->tcpa_rnext); + if (err) + goto add_key; + + if (unlikely(READ_ONCE(ao_info->current_key) == key || + READ_ONCE(ao_info->rnext_key) == key)) { + err = -EBUSY; + goto add_key; + } + + atomic_sub(tcp_ao_sizeof_key(key), &sk->sk_omem_alloc); + call_rcu(&key->rcu, tcp_ao_key_free_rcu); + + return 0; +add_key: + hlist_add_head_rcu(&key->node, &ao_info->head); + return err; +} + +static int tcp_ao_del_cmd(struct sock *sk, unsigned short int family, + sockptr_t optval, int optlen) +{ + struct tcp_ao_info *ao_info; + struct tcp_ao_key *key; + struct tcp_ao_del cmd; + int err; + union tcp_md5_addr *addr; + __u8 prefix; + __be16 port; + int addr_len; + + if (optlen < sizeof(cmd)) + return -EINVAL; + + err = copy_struct_from_sockptr(&cmd, sizeof(cmd), optval, optlen); + if (err) + return err; + + if (cmd.tcpa_flags & ~TCP_AO_CMDF_DEL_VALID) + return -EINVAL; + + ao_info = rcu_dereference_protected(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)); + if (!ao_info) + return -ENOENT; + + if (family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&cmd.tcpa_addr; + + addr = (union tcp_md5_addr *)&sin->sin_addr; + addr_len = sizeof(struct in_addr); + port = ntohs(sin->sin_port); + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd.tcpa_addr; + struct in6_addr *addr6 = &sin6->sin6_addr; + + if (ipv6_addr_v4mapped(addr6)) { + addr = (union tcp_md5_addr *)&addr6->s6_addr32[3]; + addr_len = sizeof(struct in_addr); + family = AF_INET; + } else { + addr = (union tcp_md5_addr *)addr6; + addr_len = sizeof(struct in6_addr); + } + port = ntohs(sin6->sin6_port); + } + prefix = cmd.tcpa_prefix; + + /* We could choose random present key here for current/rnext + * but that's less predictable. Let's be strict and don't + * allow removing a key that's in use. RFC5925 doesn't + * specify how-to coordinate key removal, but says: + * "It is presumed that an MKT affecting a particular + * connection cannot be destroyed during an active connection" + */ + hlist_for_each_entry_rcu(key, &ao_info->head, node) { + if (cmd.tcpa_sndid != key->sndid || + cmd.tcpa_rcvid != key->rcvid) + continue; + + if (family != key->family || + prefix != key->prefixlen || + port != key->port || + memcmp(addr, &key->addr, addr_len)) + continue; + + return tcp_ao_delete_key(sk, key, ao_info, &cmd); + } + return -ENOENT; +} + +static int tcp_ao_mod_cmd(struct sock *sk, unsigned short int family, + sockptr_t optval, int optlen) +{ + struct tcp_ao_info *ao_info; + struct tcp_ao_mod cmd; + int err; + + if (optlen < sizeof(cmd)) + return -EINVAL; + + err = copy_struct_from_sockptr(&cmd, sizeof(cmd), optval, optlen); + if (err) + return err; + + if (cmd.tcpa_flags & ~TCP_AO_CMDF_ADDMOD_VALID) + return -EINVAL; + + ao_info = rcu_dereference_protected(tcp_sk(sk)->ao_info, + lockdep_sock_is_held(sk)); + if (!ao_info) + return -ENOENT; + /* TODO: make tcp_ao_current_rnext() and flags set atomic */ + return tcp_ao_current_rnext(sk, cmd.tcpa_flags, + cmd.tcpa_current, cmd.tcpa_rnext); +} + +int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family, + sockptr_t optval, int optlen) +{ + if (WARN_ON_ONCE(family != AF_INET && family != AF_INET6)) + return -EOPNOTSUPP; + + switch (cmd) { + case TCP_AO: + return tcp_ao_add_cmd(sk, family, optval, optlen); + case TCP_AO_DEL: + return tcp_ao_del_cmd(sk, family, optval, optlen); + case TCP_AO_MOD: + return tcp_ao_mod_cmd(sk, family, optval, optlen); + default: + WARN_ON_ONCE(1); + return -EINVAL; + } +} + +int tcp_v4_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen) +{ + return tcp_parse_ao(sk, cmd, AF_INET, optval, optlen); +} + diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 6bafe7429902..755154523ffc 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -2231,11 +2231,16 @@ const struct inet_connection_sock_af_ops ipv4_specific = { }; EXPORT_SYMBOL(ipv4_specific); -#ifdef CONFIG_TCP_MD5SIG +#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO) static const struct tcp_sock_af_ops tcp_sock_ipv4_specific = { +#ifdef CONFIG_TCP_MD5SIG .md5_lookup = tcp_v4_md5_lookup, .calc_md5_hash = tcp_v4_md5_hash_skb, .md5_parse = tcp_v4_parse_md5_keys, +#endif +#ifdef CONFIG_TCP_AO + .ao_parse = tcp_v4_parse_ao, +#endif }; #endif @@ -2301,6 +2306,7 @@ void tcp_v4_destroy_sock(struct sock *sk) rcu_assign_pointer(tp->md5sig_info, NULL); } #endif + tcp_ao_destroy_sock(sk); /* Clean up a referenced TCP bind bucket. */ if (inet_csk(sk)->icsk_bind_hash) diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 3036a45e8a1e..68a991499e63 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -53,3 +53,4 @@ ifneq ($(CONFIG_IPV6),) obj-$(CONFIG_NET_UDP_TUNNEL) += ip6_udp_tunnel.o obj-y += mcast_snoop.o endif +obj-$(CONFIG_TCP_AO) += tcp_ao.o diff --git a/net/ipv6/tcp_ao.c b/net/ipv6/tcp_ao.c new file mode 100644 index 000000000000..f9f242a7e0f2 --- /dev/null +++ b/net/ipv6/tcp_ao.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * INET An implementation of the TCP Authentication Option (TCP-AO). + * See RFC5925. + * + * Authors: Dmitry Safonov <dima@arista.com> + * Francesco Ruggeri <fruggeri@arista.com> + * Salam Noureddine <noureddine@arista.com> + */ +#include <linux/tcp.h> +#include <crypto/pool.h> + +#include <net/tcp.h> +#include <net/ipv6.h> + +int tcp_v6_parse_ao(struct sock *sk, int cmd, + sockptr_t optval, int optlen) +{ + return tcp_parse_ao(sk, cmd, AF_INET6, optval, optlen); +} diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index f75569f889e7..741cbeb52117 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1883,11 +1883,16 @@ const struct inet_connection_sock_af_ops ipv6_specific = { .mtu_reduced = tcp_v6_mtu_reduced, }; -#ifdef CONFIG_TCP_MD5SIG +#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO) static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = { +#ifdef CONFIG_TCP_MD5SIG .md5_lookup = tcp_v6_md5_lookup, .calc_md5_hash = tcp_v6_md5_hash_skb, .md5_parse = tcp_v6_parse_md5_keys, +#endif +#ifdef CONFIG_TCP_AO + .ao_parse = tcp_v6_parse_ao, +#endif }; #endif @@ -1909,11 +1914,16 @@ static const struct inet_connection_sock_af_ops ipv6_mapped = { .mtu_reduced = tcp_v4_mtu_reduced, }; -#ifdef CONFIG_TCP_MD5SIG +#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO) static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = { +#ifdef CONFIG_TCP_MD5SIG .md5_lookup = tcp_v4_md5_lookup, .calc_md5_hash = tcp_v4_md5_hash_skb, .md5_parse = tcp_v6_parse_md5_keys, +#endif +#ifdef CONFIG_TCP_AO + .ao_parse = tcp_v6_parse_ao, +#endif }; #endif