net/tcp: Add TCP_AO_REPAIR
authorDmitry Safonov <dima@arista.com>
Mon, 23 Oct 2023 19:22:14 +0000 (20:22 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 27 Oct 2023 09:35:46 +0000 (10:35 +0100)
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>
include/net/tcp_ao.h
include/uapi/linux/tcp.h
net/ipv4/tcp.c
net/ipv4/tcp_ao.c

index edd6748b2cfa4efd6add9fe6fe9b9cb1d9356a00..a375a171ef3cb37ab1d8246c72c6a3e83f5c9184 100644 (file)
@@ -199,6 +199,8 @@ 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);
+int tcp_ao_get_repair(struct sock *sk, sockptr_t optval, sockptr_t optlen);
+int tcp_ao_set_repair(struct sock *sk, sockptr_t optval, unsigned int 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, int l3index,
@@ -330,6 +332,18 @@ static inline int tcp_ao_get_sock_info(struct sock *sk, sockptr_t optval, sockpt
 {
        return -ENOPROTOOPT;
 }
+
+static inline int tcp_ao_get_repair(struct sock *sk,
+                                   sockptr_t optval, sockptr_t optlen)
+{
+       return -ENOPROTOOPT;
+}
+
+static inline int tcp_ao_set_repair(struct sock *sk,
+                                   sockptr_t optval, unsigned int optlen)
+{
+       return -ENOPROTOOPT;
+}
 #endif
 
 #if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
index be34d7c5c531d8801f02b7f9904e95867888abd2..c07e9f90c0843caf8c5daaacac8296181f0c3860 100644 (file)
@@ -133,6 +133,7 @@ enum {
 #define TCP_AO_DEL_KEY         39      /* Delete MKT */
 #define TCP_AO_INFO            40      /* Set/list TCP-AO per-socket options */
 #define TCP_AO_GET_KEYS                41      /* List MKT(s) */
+#define TCP_AO_REPAIR          42      /* Get/Set SNEs and ISNs */
 
 #define TCP_REPAIR_ON          1
 #define TCP_REPAIR_OFF         0
@@ -458,6 +459,13 @@ struct tcp_ao_getsockopt { /* getsockopt(TCP_AO_GET_KEYS) */
        __u64   pkt_bad;                /* out: segments that failed verification */
 } __attribute__((aligned(8)));
 
+struct tcp_ao_repair { /* {s,g}etsockopt(TCP_AO_REPAIR) */
+       __be32                  snt_isn;
+       __be32                  rcv_isn;
+       __u32                   snd_sne;
+       __u32                   rcv_sne;
+} __attribute__((aligned(8)));
+
 /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
 
 #define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1
index ce33eee9d0f27195151397031c87d47d19bc349c..53bcc17c91e4c4fc0799f3f5d51b7ad5c8f32b26 100644 (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;
index b5ac3e73e1dabb926e5dbf6c92ae61fa917f0df6..6a845e906a1d8578511fc7135e637224d3c5cb99 100644 (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;
+}