]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net/tcp: Add TCP-AO getsockopt()s
authorDmitry Safonov <dima@arista.com>
Mon, 23 Oct 2023 19:22:10 +0000 (20:22 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 27 Oct 2023 09:35:45 +0000 (10:35 +0100)
Introduce getsockopt(TCP_AO_GET_KEYS) that lets a user get TCP-AO keys
and their properties from a socket. The user can provide a filter
to match the specific key to be dumped or ::get_all = 1 may be
used to dump all keys in one syscall.

Add another getsockopt(TCP_AO_INFO) for providing per-socket/per-ao_info
stats: packet counters, Current_key/RNext_key and flags like
::ao_required and ::accept_icmps.

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>
Acked-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/tcp_ao.h
include/uapi/linux/tcp.h
net/ipv4/tcp.c
net/ipv4/tcp_ao.c

index a9d38b9e8bcbf801082b9d6c266fc379a688df8b..061c358a3c8ae72e72183c90d71c084c38bfac3a 100644 (file)
@@ -194,6 +194,8 @@ int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx,
 void tcp_ao_destroy_sock(struct sock *sk, bool twsk);
 void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw, struct tcp_sock *tp);
 bool tcp_ao_ignore_icmp(const struct sock *sk, int family, int type, int code);
+int tcp_ao_get_mkts(struct sock *sk, sockptr_t optval, sockptr_t optlen);
+int tcp_ao_get_sock_info(struct sock *sk, sockptr_t optval, sockptr_t optlen);
 enum skb_drop_reason tcp_inbound_ao_hash(struct sock *sk,
                        const struct sk_buff *skb, unsigned short int family,
                        const struct request_sock *req,
@@ -316,6 +318,16 @@ static inline void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw,
 static inline void tcp_ao_connect_init(struct sock *sk)
 {
 }
+
+static inline int tcp_ao_get_mkts(struct sock *sk, sockptr_t optval, sockptr_t optlen)
+{
+       return -ENOPROTOOPT;
+}
+
+static inline int tcp_ao_get_sock_info(struct sock *sk, sockptr_t optval, sockptr_t optlen)
+{
+       return -ENOPROTOOPT;
+}
 #endif
 
 #if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
index 320aab010f9ab105a97df0ca8d65eb137390d23b..201b3cbd6020da35212ba3e147dfe95fecc3ebf5 100644 (file)
@@ -131,7 +131,8 @@ enum {
 
 #define TCP_AO_ADD_KEY         38      /* Add/Set MKT */
 #define TCP_AO_DEL_KEY         39      /* Delete MKT */
-#define TCP_AO_INFO            40      /* Modify TCP-AO per-socket options */
+#define TCP_AO_INFO            40      /* Set/list TCP-AO per-socket options */
+#define TCP_AO_GET_KEYS                41      /* List MKT(s) */
 
 #define TCP_REPAIR_ON          1
 #define TCP_REPAIR_OFF         0
@@ -405,21 +406,55 @@ struct tcp_ao_del { /* setsockopt(TCP_AO_DEL_KEY) */
        __u8    keyflags;               /* see TCP_AO_KEYF_ */
 } __attribute__((aligned(8)));
 
-struct tcp_ao_info_opt { /* setsockopt(TCP_AO_INFO) */
-       __u32   set_current     :1,     /* corresponding ::current_key */
-               set_rnext       :1,     /* corresponding ::rnext */
-               ao_required     :1,     /* don't accept non-AO connects */
-               set_counters    :1,     /* set/clear ::pkt_* counters */
-               accept_icmps    :1,     /* accept incoming ICMPs */
+struct tcp_ao_info_opt { /* setsockopt(TCP_AO_INFO), getsockopt(TCP_AO_INFO) */
+       /* Here 'in' is for setsockopt(), 'out' is for getsockopt() */
+       __u32   set_current     :1,     /* in/out: corresponding ::current_key */
+               set_rnext       :1,     /* in/out: corresponding ::rnext */
+               ao_required     :1,     /* in/out: don't accept non-AO connects */
+               set_counters    :1,     /* in: set/clear ::pkt_* counters */
+               accept_icmps    :1,     /* in/out: accept incoming ICMPs */
                reserved        :27;    /* must be 0 */
        __u16   reserved2;              /* padding, must be 0 */
-       __u8    current_key;            /* KeyID to set as Current_key */
-       __u8    rnext;                  /* KeyID to set as Rnext_key */
-       __u64   pkt_good;               /* verified segments */
-       __u64   pkt_bad;                /* failed verification */
-       __u64   pkt_key_not_found;      /* could not find a key to verify */
-       __u64   pkt_ao_required;        /* segments missing TCP-AO sign */
-       __u64   pkt_dropped_icmp;       /* ICMPs that were ignored */
+       __u8    current_key;            /* in/out: KeyID of Current_key */
+       __u8    rnext;                  /* in/out: keyid of RNext_key */
+       __u64   pkt_good;               /* in/out: verified segments */
+       __u64   pkt_bad;                /* in/out: failed verification */
+       __u64   pkt_key_not_found;      /* in/out: could not find a key to verify */
+       __u64   pkt_ao_required;        /* in/out: segments missing TCP-AO sign */
+       __u64   pkt_dropped_icmp;       /* in/out: ICMPs that were ignored */
+} __attribute__((aligned(8)));
+
+struct tcp_ao_getsockopt { /* getsockopt(TCP_AO_GET_KEYS) */
+       struct __kernel_sockaddr_storage addr;  /* in/out: dump keys for peer
+                                                * with this address/prefix
+                                                */
+       char    alg_name[64];           /* out: crypto hash algorithm */
+       __u8    key[TCP_AO_MAXKEYLEN];
+       __u32   nkeys;                  /* in: size of the userspace buffer
+                                        * @optval, measured in @optlen - the
+                                        * sizeof(struct tcp_ao_getsockopt)
+                                        * out: number of keys that matched
+                                        */
+       __u16   is_current      :1,     /* in: match and dump Current_key,
+                                        * out: the dumped key is Current_key
+                                        */
+
+               is_rnext        :1,     /* in: match and dump RNext_key,
+                                        * out: the dumped key is RNext_key
+                                        */
+               get_all         :1,     /* in: dump all keys */
+               reserved        :13;    /* padding, must be 0 */
+       __u8    sndid;                  /* in/out: dump keys with SendID */
+       __u8    rcvid;                  /* in/out: dump keys with RecvID */
+       __u8    prefix;                 /* in/out: dump keys with address/prefix */
+       __u8    maclen;                 /* out: key's length of authentication
+                                        * code (hash)
+                                        */
+       __u8    keyflags;               /* in/out: see TCP_AO_KEYF_ */
+       __u8    keylen;                 /* out: length of ::key */
+       __s32   ifindex;                /* in/out: L3 dev index for VRF */
+       __u64   pkt_good;               /* out: verified segments */
+       __u64   pkt_bad;                /* out: segments that failed verification */
 } __attribute__((aligned(8)));
 
 /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
index 1be6467a059ac8fead260050592bf9d070475da8..ce33eee9d0f27195151397031c87d47d19bc349c 100644 (file)
@@ -4284,6 +4284,19 @@ zerocopy_rcv_out:
                return err;
        }
 #endif
+       case TCP_AO_GET_KEYS:
+       case TCP_AO_INFO: {
+               int err;
+
+               sockopt_lock_sock(sk);
+               if (optname == TCP_AO_GET_KEYS)
+                       err = tcp_ao_get_mkts(sk, optval, optlen);
+               else
+                       err = tcp_ao_get_sock_info(sk, optval, optlen);
+               sockopt_release_sock(sk);
+
+               return err;
+       }
        default:
                return -ENOPROTOOPT;
        }
index 10cc6be4d537352e25cde56d3c7d711f90f5d7aa..cbc1ee0f5b9a93da4fba39aa320d11ef0f4760da 100644 (file)
@@ -1894,3 +1894,298 @@ 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);
 }
 
+/* tcp_ao_copy_mkts_to_user(ao_info, optval, optlen)
+ *
+ * @ao_info:   struct tcp_ao_info on the socket that
+ *             socket getsockopt(TCP_AO_GET_KEYS) is executed on
+ * @optval:    pointer to array of tcp_ao_getsockopt structures in user space.
+ *             Must be != NULL.
+ * @optlen:    pointer to size of tcp_ao_getsockopt structure.
+ *             Must be != NULL.
+ *
+ * Return value: 0 on success, a negative error number otherwise.
+ *
+ * optval points to an array of tcp_ao_getsockopt structures in user space.
+ * optval[0] is used as both input and output to getsockopt. It determines
+ * which keys are returned by the kernel.
+ * optval[0].nkeys is the size of the array in user space. On return it contains
+ * the number of keys matching the search criteria.
+ * If tcp_ao_getsockopt::get_all is set, then all keys in the socket are
+ * returned, otherwise only keys matching <addr, prefix, sndid, rcvid>
+ * in optval[0] are returned.
+ * optlen is also used as both input and output. The user provides the size
+ * of struct tcp_ao_getsockopt in user space, and the kernel returns the size
+ * of the structure in kernel space.
+ * The size of struct tcp_ao_getsockopt may differ between user and kernel.
+ * There are three cases to consider:
+ *  * If usize == ksize, then keys are copied verbatim.
+ *  * If usize < ksize, then the userspace has passed an old struct to a
+ *    newer kernel. The rest of the trailing bytes in optval[0]
+ *    (ksize - usize) are interpreted as 0 by the kernel.
+ *  * If usize > ksize, then the userspace has passed a new struct to an
+ *    older kernel. The trailing bytes unknown to the kernel (usize - ksize)
+ *    are checked to ensure they are zeroed, otherwise -E2BIG is returned.
+ * On return the kernel fills in min(usize, ksize) in each entry of the array.
+ * The layout of the fields in the user and kernel structures is expected to
+ * be the same (including in the 32bit vs 64bit case).
+ */
+static int tcp_ao_copy_mkts_to_user(struct tcp_ao_info *ao_info,
+                                   sockptr_t optval, sockptr_t optlen)
+{
+       struct tcp_ao_getsockopt opt_in, opt_out;
+       struct tcp_ao_key *key, *current_key;
+       bool do_address_matching = true;
+       union tcp_ao_addr *addr = NULL;
+       unsigned int max_keys;  /* maximum number of keys to copy to user */
+       size_t out_offset = 0;
+       size_t bytes_to_write;  /* number of bytes to write to user level */
+       int err, user_len;
+       u32 matched_keys;       /* keys from ao_info matched so far */
+       int optlen_out;
+       __be16 port = 0;
+
+       if (copy_from_sockptr(&user_len, optlen, sizeof(int)))
+               return -EFAULT;
+
+       if (user_len <= 0)
+               return -EINVAL;
+
+       memset(&opt_in, 0, sizeof(struct tcp_ao_getsockopt));
+       err = copy_struct_from_sockptr(&opt_in, sizeof(opt_in),
+                                      optval, user_len);
+       if (err < 0)
+               return err;
+
+       if (opt_in.pkt_good || opt_in.pkt_bad)
+               return -EINVAL;
+
+       if (opt_in.reserved != 0)
+               return -EINVAL;
+
+       max_keys = opt_in.nkeys;
+
+       if (opt_in.get_all || opt_in.is_current || opt_in.is_rnext) {
+               if (opt_in.get_all && (opt_in.is_current || opt_in.is_rnext))
+                       return -EINVAL;
+               do_address_matching = false;
+       }
+
+       switch (opt_in.addr.ss_family) {
+       case AF_INET: {
+               struct sockaddr_in *sin;
+               __be32 mask;
+
+               sin = (struct sockaddr_in *)&opt_in.addr;
+               port = sin->sin_port;
+               addr = (union tcp_ao_addr *)&sin->sin_addr;
+
+               if (opt_in.prefix > 32)
+                       return -EINVAL;
+
+               if (ntohl(sin->sin_addr.s_addr) == INADDR_ANY &&
+                   opt_in.prefix != 0)
+                       return -EINVAL;
+
+               mask = inet_make_mask(opt_in.prefix);
+               if (sin->sin_addr.s_addr & ~mask)
+                       return -EINVAL;
+
+               break;
+       }
+       case AF_INET6: {
+               struct sockaddr_in6 *sin6;
+               struct in6_addr *addr6;
+
+               sin6 = (struct sockaddr_in6 *)&opt_in.addr;
+               addr = (union tcp_ao_addr *)&sin6->sin6_addr;
+               addr6 = &sin6->sin6_addr;
+               port = sin6->sin6_port;
+
+               /* We don't have to change family and @addr here if
+                * ipv6_addr_v4mapped() like in key adding:
+                * tcp_ao_key_cmp() does it. Do the sanity checks though.
+                */
+               if (opt_in.prefix != 0) {
+                       if (ipv6_addr_v4mapped(addr6)) {
+                               __be32 mask, addr4 = addr6->s6_addr32[3];
+
+                               if (opt_in.prefix > 32 ||
+                                   ntohl(addr4) == INADDR_ANY)
+                                       return -EINVAL;
+                               mask = inet_make_mask(opt_in.prefix);
+                               if (addr4 & ~mask)
+                                       return -EINVAL;
+                       } else {
+                               struct in6_addr pfx;
+
+                               if (ipv6_addr_any(addr6) ||
+                                   opt_in.prefix > 128)
+                                       return -EINVAL;
+
+                               ipv6_addr_prefix(&pfx, addr6, opt_in.prefix);
+                               if (ipv6_addr_cmp(&pfx, addr6))
+                                       return -EINVAL;
+                       }
+               } else if (!ipv6_addr_any(addr6)) {
+                       return -EINVAL;
+               }
+               break;
+       }
+       case 0:
+               if (!do_address_matching)
+                       break;
+               fallthrough;
+       default:
+               return -EAFNOSUPPORT;
+       }
+
+       if (!do_address_matching) {
+               /* We could just ignore those, but let's do stricter checks */
+               if (addr || port)
+                       return -EINVAL;
+               if (opt_in.prefix || opt_in.sndid || opt_in.rcvid)
+                       return -EINVAL;
+       }
+
+       bytes_to_write = min_t(int, user_len, sizeof(struct tcp_ao_getsockopt));
+       matched_keys = 0;
+       /* May change in RX, while we're dumping, pre-fetch it */
+       current_key = READ_ONCE(ao_info->current_key);
+
+       hlist_for_each_entry_rcu(key, &ao_info->head, node) {
+               if (opt_in.get_all)
+                       goto match;
+
+               if (opt_in.is_current || opt_in.is_rnext) {
+                       if (opt_in.is_current && key == current_key)
+                               goto match;
+                       if (opt_in.is_rnext && key == ao_info->rnext_key)
+                               goto match;
+                       continue;
+               }
+
+               if (tcp_ao_key_cmp(key, addr, opt_in.prefix,
+                                  opt_in.addr.ss_family,
+                                  opt_in.sndid, opt_in.rcvid) != 0)
+                       continue;
+match:
+               matched_keys++;
+               if (matched_keys > max_keys)
+                       continue;
+
+               memset(&opt_out, 0, sizeof(struct tcp_ao_getsockopt));
+
+               if (key->family == AF_INET) {
+                       struct sockaddr_in *sin_out = (struct sockaddr_in *)&opt_out.addr;
+
+                       sin_out->sin_family = key->family;
+                       sin_out->sin_port = 0;
+                       memcpy(&sin_out->sin_addr, &key->addr, sizeof(struct in_addr));
+               } else {
+                       struct sockaddr_in6 *sin6_out = (struct sockaddr_in6 *)&opt_out.addr;
+
+                       sin6_out->sin6_family = key->family;
+                       sin6_out->sin6_port = 0;
+                       memcpy(&sin6_out->sin6_addr, &key->addr, sizeof(struct in6_addr));
+               }
+               opt_out.sndid = key->sndid;
+               opt_out.rcvid = key->rcvid;
+               opt_out.prefix = key->prefixlen;
+               opt_out.keyflags = key->keyflags;
+               opt_out.is_current = (key == current_key);
+               opt_out.is_rnext = (key == ao_info->rnext_key);
+               opt_out.nkeys = 0;
+               opt_out.maclen = key->maclen;
+               opt_out.keylen = key->keylen;
+               opt_out.pkt_good = atomic64_read(&key->pkt_good);
+               opt_out.pkt_bad = atomic64_read(&key->pkt_bad);
+               memcpy(&opt_out.key, key->key, key->keylen);
+               tcp_sigpool_algo(key->tcp_sigpool_id, opt_out.alg_name, 64);
+
+               /* Copy key to user */
+               if (copy_to_sockptr_offset(optval, out_offset,
+                                          &opt_out, bytes_to_write))
+                       return -EFAULT;
+               out_offset += user_len;
+       }
+
+       optlen_out = (int)sizeof(struct tcp_ao_getsockopt);
+       if (copy_to_sockptr(optlen, &optlen_out, sizeof(int)))
+               return -EFAULT;
+
+       out_offset = offsetof(struct tcp_ao_getsockopt, nkeys);
+       if (copy_to_sockptr_offset(optval, out_offset,
+                                  &matched_keys, sizeof(u32)))
+               return -EFAULT;
+
+       return 0;
+}
+
+int tcp_ao_get_mkts(struct sock *sk, sockptr_t optval, sockptr_t optlen)
+{
+       struct tcp_ao_info *ao_info;
+
+       ao_info = setsockopt_ao_info(sk);
+       if (IS_ERR(ao_info))
+               return PTR_ERR(ao_info);
+       if (!ao_info)
+               return -ENOENT;
+
+       return tcp_ao_copy_mkts_to_user(ao_info, optval, optlen);
+}
+
+int tcp_ao_get_sock_info(struct sock *sk, sockptr_t optval, sockptr_t optlen)
+{
+       struct tcp_ao_info_opt out, in = {};
+       struct tcp_ao_key *current_key;
+       struct tcp_ao_info *ao;
+       int err, len;
+
+       if (copy_from_sockptr(&len, optlen, sizeof(int)))
+               return -EFAULT;
+
+       if (len <= 0)
+               return -EINVAL;
+
+       /* Copying this "in" only to check ::reserved, ::reserved2,
+        * that may be needed to extend (struct tcp_ao_info_opt) and
+        * what getsockopt() provides in future.
+        */
+       err = copy_struct_from_sockptr(&in, sizeof(in), optval, len);
+       if (err)
+               return err;
+
+       if (in.reserved != 0 || in.reserved2 != 0)
+               return -EINVAL;
+
+       ao = setsockopt_ao_info(sk);
+       if (IS_ERR(ao))
+               return PTR_ERR(ao);
+       if (!ao)
+               return -ENOENT;
+
+       memset(&out, 0, sizeof(out));
+       out.ao_required         = ao->ao_required;
+       out.accept_icmps        = ao->accept_icmps;
+       out.pkt_good            = atomic64_read(&ao->counters.pkt_good);
+       out.pkt_bad             = atomic64_read(&ao->counters.pkt_bad);
+       out.pkt_key_not_found   = atomic64_read(&ao->counters.key_not_found);
+       out.pkt_ao_required     = atomic64_read(&ao->counters.ao_required);
+       out.pkt_dropped_icmp    = atomic64_read(&ao->counters.dropped_icmp);
+
+       current_key = READ_ONCE(ao->current_key);
+       if (current_key) {
+               out.set_current = 1;
+               out.current_key = current_key->sndid;
+       }
+       if (ao->rnext_key) {
+               out.set_rnext = 1;
+               out.rnext = ao->rnext_key->rcvid;
+       }
+
+       if (copy_to_sockptr(optval, &out, min_t(int, len, sizeof(out))))
+               return -EFAULT;
+
+       return 0;
+}
+