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_destroy_sock(struct sock *sk, bool twsk);
|
||||||
void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw, struct tcp_sock *tp);
|
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);
|
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,
|
enum skb_drop_reason tcp_inbound_ao_hash(struct sock *sk,
|
||||||
const struct sk_buff *skb, unsigned short int family,
|
const struct sk_buff *skb, unsigned short int family,
|
||||||
const struct request_sock *req,
|
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 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
|
#endif
|
||||||
|
|
||||||
#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
|
#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_ADD_KEY 38 /* Add/Set MKT */
|
||||||
#define TCP_AO_DEL_KEY 39 /* Delete 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_ON 1
|
||||||
#define TCP_REPAIR_OFF 0
|
#define TCP_REPAIR_OFF 0
|
||||||
@ -405,21 +406,55 @@ struct tcp_ao_del { /* setsockopt(TCP_AO_DEL_KEY) */
|
|||||||
__u8 keyflags; /* see TCP_AO_KEYF_ */
|
__u8 keyflags; /* see TCP_AO_KEYF_ */
|
||||||
} __attribute__((aligned(8)));
|
} __attribute__((aligned(8)));
|
||||||
|
|
||||||
struct tcp_ao_info_opt { /* setsockopt(TCP_AO_INFO) */
|
struct tcp_ao_info_opt { /* setsockopt(TCP_AO_INFO), getsockopt(TCP_AO_INFO) */
|
||||||
__u32 set_current :1, /* corresponding ::current_key */
|
/* Here 'in' is for setsockopt(), 'out' is for getsockopt() */
|
||||||
set_rnext :1, /* corresponding ::rnext */
|
__u32 set_current :1, /* in/out: corresponding ::current_key */
|
||||||
ao_required :1, /* don't accept non-AO connects */
|
set_rnext :1, /* in/out: corresponding ::rnext */
|
||||||
set_counters :1, /* set/clear ::pkt_* counters */
|
ao_required :1, /* in/out: don't accept non-AO connects */
|
||||||
accept_icmps :1, /* accept incoming ICMPs */
|
set_counters :1, /* in: set/clear ::pkt_* counters */
|
||||||
|
accept_icmps :1, /* in/out: accept incoming ICMPs */
|
||||||
reserved :27; /* must be 0 */
|
reserved :27; /* must be 0 */
|
||||||
__u16 reserved2; /* padding, must be 0 */
|
__u16 reserved2; /* padding, must be 0 */
|
||||||
__u8 current_key; /* KeyID to set as Current_key */
|
__u8 current_key; /* in/out: KeyID of Current_key */
|
||||||
__u8 rnext; /* KeyID to set as Rnext_key */
|
__u8 rnext; /* in/out: keyid of RNext_key */
|
||||||
__u64 pkt_good; /* verified segments */
|
__u64 pkt_good; /* in/out: verified segments */
|
||||||
__u64 pkt_bad; /* failed verification */
|
__u64 pkt_bad; /* in/out: failed verification */
|
||||||
__u64 pkt_key_not_found; /* could not find a key to verify */
|
__u64 pkt_key_not_found; /* in/out: could not find a key to verify */
|
||||||
__u64 pkt_ao_required; /* segments missing TCP-AO sign */
|
__u64 pkt_ao_required; /* in/out: segments missing TCP-AO sign */
|
||||||
__u64 pkt_dropped_icmp; /* ICMPs that were ignored */
|
__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)));
|
} __attribute__((aligned(8)));
|
||||||
|
|
||||||
/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
|
/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
|
||||||
|
@ -4284,6 +4284,19 @@ zerocopy_rcv_out:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
#endif
|
#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:
|
default:
|
||||||
return -ENOPROTOOPT;
|
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);
|
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