net/tcp: Add TCP_AO_REPAIR

Add TCP_AO_REPAIR setsockopt(), getsockopt(). They let a user to repair
TCP-AO ISNs/SNEs. Also let the user hack around when (tp->repair) is on
and add ao_info on a socket in any supported state.
As SNEs now can be read/written at any moment, use
WRITE_ONCE()/READ_ONCE() to set/read them.

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:14 +01:00
committed by David S. Miller
parent 248411b8cb
commit faadfaba5e
4 changed files with 125 additions and 11 deletions

View File

@ -3593,20 +3593,28 @@ int do_tcp_setsockopt(struct sock *sk, int level, int optname,
__tcp_sock_set_quickack(sk, val);
break;
case TCP_AO_REPAIR:
err = tcp_ao_set_repair(sk, optval, optlen);
break;
#ifdef CONFIG_TCP_AO
case TCP_AO_ADD_KEY:
case TCP_AO_DEL_KEY:
case TCP_AO_INFO: {
/* If this is the first TCP-AO setsockopt() on the socket,
* sk_state has to be LISTEN or CLOSE
* sk_state has to be LISTEN or CLOSE. Allow TCP_REPAIR
* in any state.
*/
if (((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE)) ||
rcu_dereference_protected(tcp_sk(sk)->ao_info,
if ((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE))
goto ao_parse;
if (rcu_dereference_protected(tcp_sk(sk)->ao_info,
lockdep_sock_is_held(sk)))
err = tp->af_specific->ao_parse(sk, optname, optval,
optlen);
else
err = -EISCONN;
goto ao_parse;
if (tp->repair)
goto ao_parse;
err = -EISCONN;
break;
ao_parse:
err = tp->af_specific->ao_parse(sk, optname, optval, optlen);
break;
}
#endif
@ -4284,6 +4292,8 @@ zerocopy_rcv_out:
return err;
}
#endif
case TCP_AO_REPAIR:
return tcp_ao_get_repair(sk, optval, optlen);
case TCP_AO_GET_KEYS:
case TCP_AO_INFO: {
int err;

View File

@ -1490,6 +1490,16 @@ static struct tcp_ao_info *setsockopt_ao_info(struct sock *sk)
return ERR_PTR(-ESOCKTNOSUPPORT);
}
static struct tcp_ao_info *getsockopt_ao_info(struct sock *sk)
{
if (sk_fullsock(sk))
return rcu_dereference(tcp_sk(sk)->ao_info);
else if (sk->sk_state == TCP_TIME_WAIT)
return rcu_dereference(tcp_twsk(sk)->ao_info);
return ERR_PTR(-ESOCKTNOSUPPORT);
}
#define TCP_AO_KEYF_ALL (TCP_AO_KEYF_IFINDEX | TCP_AO_KEYF_EXCLUDE_OPT)
#define TCP_AO_GET_KEYF_VALID (TCP_AO_KEYF_IFINDEX)
@ -1671,11 +1681,13 @@ static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family,
if (ret < 0)
goto err_free_sock;
/* Change this condition if we allow adding keys in states
* like close_wait, syn_sent or fin_wait...
*/
if (sk->sk_state == TCP_ESTABLISHED)
if (!((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE))) {
tcp_ao_cache_traffic_keys(sk, ao_info, key);
if (first) {
ao_info->current_key = key;
ao_info->rnext_key = key;
}
}
tcp_ao_link_mkt(ao_info, key);
if (first) {
@ -1926,6 +1938,8 @@ static int tcp_ao_info_cmd(struct sock *sk, unsigned short int family,
if (IS_ERR(ao_info))
return PTR_ERR(ao_info);
if (!ao_info) {
if (!((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE)))
return -EINVAL;
ao_info = tcp_ao_alloc_info(GFP_KERNEL);
if (!ao_info)
return -ENOMEM;
@ -2308,3 +2322,71 @@ int tcp_ao_get_sock_info(struct sock *sk, sockptr_t optval, sockptr_t optlen)
return 0;
}
int tcp_ao_set_repair(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_ao_repair cmd;
struct tcp_ao_key *key;
struct tcp_ao_info *ao;
int err;
if (optlen < sizeof(cmd))
return -EINVAL;
err = copy_struct_from_sockptr(&cmd, sizeof(cmd), optval, optlen);
if (err)
return err;
if (!tp->repair)
return -EPERM;
ao = setsockopt_ao_info(sk);
if (IS_ERR(ao))
return PTR_ERR(ao);
if (!ao)
return -ENOENT;
WRITE_ONCE(ao->lisn, cmd.snt_isn);
WRITE_ONCE(ao->risn, cmd.rcv_isn);
WRITE_ONCE(ao->snd_sne, cmd.snd_sne);
WRITE_ONCE(ao->rcv_sne, cmd.rcv_sne);
hlist_for_each_entry_rcu(key, &ao->head, node)
tcp_ao_cache_traffic_keys(sk, ao, key);
return 0;
}
int tcp_ao_get_repair(struct sock *sk, sockptr_t optval, sockptr_t optlen)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_ao_repair opt;
struct tcp_ao_info *ao;
int len;
if (copy_from_sockptr(&len, optlen, sizeof(int)))
return -EFAULT;
if (len <= 0)
return -EINVAL;
if (!tp->repair)
return -EPERM;
rcu_read_lock();
ao = getsockopt_ao_info(sk);
if (IS_ERR_OR_NULL(ao)) {
rcu_read_unlock();
return ao ? PTR_ERR(ao) : -ENOENT;
}
opt.snt_isn = ao->lisn;
opt.rcv_isn = ao->risn;
opt.snd_sne = READ_ONCE(ao->snd_sne);
opt.rcv_sne = READ_ONCE(ao->rcv_sne);
rcu_read_unlock();
if (copy_to_sockptr(optval, &opt, min_t(int, len, sizeof(opt))))
return -EFAULT;
return 0;
}