mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-09-21 20:22:13 +08:00
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:
parent
7753c2f0a8
commit
ef84703a91
@ -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)
|
||||
|
@ -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, ...) */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user