net: Update an existing TCP congestion control algorithm.
authorKui-Feng Lee <kuifeng@meta.com>
Thu, 23 Mar 2023 03:23:59 +0000 (20:23 -0700)
committerMartin KaFai Lau <martin.lau@kernel.org>
Thu, 23 Mar 2023 05:53:00 +0000 (22:53 -0700)
This feature lets you immediately transition to another congestion
control algorithm or implementation with the same name.  Once a name
is updated, new connections will apply this new algorithm.

The purpose is to update a customized algorithm implemented in BPF
struct_ops with a new version on the flight.  The following is an
example of using the userspace API implemented in later BPF patches.

   link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
   .......
   err = bpf_link__update_map(link, skel->maps.ca_update_2);

We first load and register an algorithm implemented in BPF struct_ops,
then swap it out with a new one using the same name. After that, newly
created connections will apply the updated algorithm, while older ones
retain the previous version already applied.

This patch also takes this chance to refactor the ca validation into
the new tcp_validate_congestion_control() function.

Cc: netdev@vger.kernel.org, Eric Dumazet <edumazet@google.com>
Signed-off-by: Kui-Feng Lee <kuifeng@meta.com>
Link: https://lore.kernel.org/r/20230323032405.3735486-3-kuifeng@meta.com
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
include/net/tcp.h
net/ipv4/tcp_cong.c

index db9f828..2abb755 100644 (file)
@@ -1117,6 +1117,9 @@ struct tcp_congestion_ops {
 
 int tcp_register_congestion_control(struct tcp_congestion_ops *type);
 void tcp_unregister_congestion_control(struct tcp_congestion_ops *type);
+int tcp_update_congestion_control(struct tcp_congestion_ops *type,
+                                 struct tcp_congestion_ops *old_type);
+int tcp_validate_congestion_control(struct tcp_congestion_ops *ca);
 
 void tcp_assign_congestion_control(struct sock *sk);
 void tcp_init_congestion_control(struct sock *sk);
index db8b4b4..1b34050 100644 (file)
@@ -75,14 +75,8 @@ struct tcp_congestion_ops *tcp_ca_find_key(u32 key)
        return NULL;
 }
 
-/*
- * Attach new congestion control algorithm to the list
- * of available options.
- */
-int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
+int tcp_validate_congestion_control(struct tcp_congestion_ops *ca)
 {
-       int ret = 0;
-
        /* all algorithms must implement these */
        if (!ca->ssthresh || !ca->undo_cwnd ||
            !(ca->cong_avoid || ca->cong_control)) {
@@ -90,6 +84,20 @@ int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
                return -EINVAL;
        }
 
+       return 0;
+}
+
+/* Attach new congestion control algorithm to the list
+ * of available options.
+ */
+int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
+{
+       int ret;
+
+       ret = tcp_validate_congestion_control(ca);
+       if (ret)
+               return ret;
+
        ca->key = jhash(ca->name, sizeof(ca->name), strlen(ca->name));
 
        spin_lock(&tcp_cong_list_lock);
@@ -130,6 +138,50 @@ void tcp_unregister_congestion_control(struct tcp_congestion_ops *ca)
 }
 EXPORT_SYMBOL_GPL(tcp_unregister_congestion_control);
 
+/* Replace a registered old ca with a new one.
+ *
+ * The new ca must have the same name as the old one, that has been
+ * registered.
+ */
+int tcp_update_congestion_control(struct tcp_congestion_ops *ca, struct tcp_congestion_ops *old_ca)
+{
+       struct tcp_congestion_ops *existing;
+       int ret;
+
+       ret = tcp_validate_congestion_control(ca);
+       if (ret)
+               return ret;
+
+       ca->key = jhash(ca->name, sizeof(ca->name), strlen(ca->name));
+
+       spin_lock(&tcp_cong_list_lock);
+       existing = tcp_ca_find_key(old_ca->key);
+       if (ca->key == TCP_CA_UNSPEC || !existing || strcmp(existing->name, ca->name)) {
+               pr_notice("%s not registered or non-unique key\n",
+                         ca->name);
+               ret = -EINVAL;
+       } else if (existing != old_ca) {
+               pr_notice("invalid old congestion control algorithm to replace\n");
+               ret = -EINVAL;
+       } else {
+               /* Add the new one before removing the old one to keep
+                * one implementation available all the time.
+                */
+               list_add_tail_rcu(&ca->list, &tcp_cong_list);
+               list_del_rcu(&existing->list);
+               pr_debug("%s updated\n", ca->name);
+       }
+       spin_unlock(&tcp_cong_list_lock);
+
+       /* Wait for outstanding readers to complete before the
+        * module or struct_ops gets removed entirely.
+        */
+       if (!ret)
+               synchronize_rcu();
+
+       return ret;
+}
+
 u32 tcp_ca_get_key_by_name(struct net *net, const char *name, bool *ecn_ca)
 {
        const struct tcp_congestion_ops *ca;