net/tcp: Add TCP-AO getsockopt()s

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>
This commit is contained in:
Dmitry Safonov 2023-10-23 20:22:10 +01:00 committed by David S. Miller
parent 7753c2f0a8
commit ef84703a91
4 changed files with 369 additions and 14 deletions

View 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)

View 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, ...) */

View 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;
}

View 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;
}