]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/ipv4/tcp_cong.c
fib_trie: Fix RCU bug and merge similar bits of inflate/halve
[karo-tx-linux.git] / net / ipv4 / tcp_cong.c
index 27ead0dd16bc7e444e96781ff01b10c444678396..63c29dba68a8f220d1b788d86ca11853590121ec 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/types.h>
 #include <linux/list.h>
 #include <linux/gfp.h>
+#include <linux/jhash.h>
 #include <net/tcp.h>
 
 static DEFINE_SPINLOCK(tcp_cong_list_lock);
@@ -31,6 +32,34 @@ static struct tcp_congestion_ops *tcp_ca_find(const char *name)
        return NULL;
 }
 
+/* Must be called with rcu lock held */
+static const struct tcp_congestion_ops *__tcp_ca_find_autoload(const char *name)
+{
+       const struct tcp_congestion_ops *ca = tcp_ca_find(name);
+#ifdef CONFIG_MODULES
+       if (!ca && capable(CAP_NET_ADMIN)) {
+               rcu_read_unlock();
+               request_module("tcp_%s", name);
+               rcu_read_lock();
+               ca = tcp_ca_find(name);
+       }
+#endif
+       return ca;
+}
+
+/* Simple linear search, not much in here. */
+struct tcp_congestion_ops *tcp_ca_find_key(u32 key)
+{
+       struct tcp_congestion_ops *e;
+
+       list_for_each_entry_rcu(e, &tcp_cong_list, list) {
+               if (e->key == key)
+                       return e;
+       }
+
+       return NULL;
+}
+
 /*
  * Attach new congestion control algorithm to the list
  * of available options.
@@ -45,9 +74,12 @@ int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
                return -EINVAL;
        }
 
+       ca->key = jhash(ca->name, sizeof(ca->name), strlen(ca->name));
+
        spin_lock(&tcp_cong_list_lock);
-       if (tcp_ca_find(ca->name)) {
-               pr_notice("%s already registered\n", ca->name);
+       if (ca->key == TCP_CA_UNSPEC || tcp_ca_find_key(ca->key)) {
+               pr_notice("%s already registered or non-unique key\n",
+                         ca->name);
                ret = -EEXIST;
        } else {
                list_add_tail_rcu(&ca->list, &tcp_cong_list);
@@ -70,9 +102,50 @@ void tcp_unregister_congestion_control(struct tcp_congestion_ops *ca)
        spin_lock(&tcp_cong_list_lock);
        list_del_rcu(&ca->list);
        spin_unlock(&tcp_cong_list_lock);
+
+       /* Wait for outstanding readers to complete before the
+        * module gets removed entirely.
+        *
+        * A try_module_get() should fail by now as our module is
+        * in "going" state since no refs are held anymore and
+        * module_exit() handler being called.
+        */
+       synchronize_rcu();
 }
 EXPORT_SYMBOL_GPL(tcp_unregister_congestion_control);
 
+u32 tcp_ca_get_key_by_name(const char *name)
+{
+       const struct tcp_congestion_ops *ca;
+       u32 key;
+
+       might_sleep();
+
+       rcu_read_lock();
+       ca = __tcp_ca_find_autoload(name);
+       key = ca ? ca->key : TCP_CA_UNSPEC;
+       rcu_read_unlock();
+
+       return key;
+}
+EXPORT_SYMBOL_GPL(tcp_ca_get_key_by_name);
+
+char *tcp_ca_get_name_by_key(u32 key, char *buffer)
+{
+       const struct tcp_congestion_ops *ca;
+       char *ret = NULL;
+
+       rcu_read_lock();
+       ca = tcp_ca_find_key(key);
+       if (ca)
+               ret = strncpy(buffer, ca->name,
+                             TCP_CA_NAME_MAX);
+       rcu_read_unlock();
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(tcp_ca_get_name_by_key);
+
 /* Assign choice of congestion control. */
 void tcp_assign_congestion_control(struct sock *sk)
 {
@@ -107,6 +180,18 @@ void tcp_init_congestion_control(struct sock *sk)
                icsk->icsk_ca_ops->init(sk);
 }
 
+static void tcp_reinit_congestion_control(struct sock *sk,
+                                         const struct tcp_congestion_ops *ca)
+{
+       struct inet_connection_sock *icsk = inet_csk(sk);
+
+       tcp_cleanup_congestion_control(sk);
+       icsk->icsk_ca_ops = ca;
+
+       if (sk->sk_state != TCP_CLOSE && icsk->icsk_ca_ops->init)
+               icsk->icsk_ca_ops->init(sk);
+}
+
 /* Manage refcounts on socket close. */
 void tcp_cleanup_congestion_control(struct sock *sk)
 {
@@ -241,42 +326,26 @@ out:
 int tcp_set_congestion_control(struct sock *sk, const char *name)
 {
        struct inet_connection_sock *icsk = inet_csk(sk);
-       struct tcp_congestion_ops *ca;
+       const struct tcp_congestion_ops *ca;
        int err = 0;
 
-       rcu_read_lock();
-       ca = tcp_ca_find(name);
+       if (icsk->icsk_ca_dst_locked)
+               return -EPERM;
 
-       /* no change asking for existing value */
+       rcu_read_lock();
+       ca = __tcp_ca_find_autoload(name);
+       /* No change asking for existing value */
        if (ca == icsk->icsk_ca_ops)
                goto out;
-
-#ifdef CONFIG_MODULES
-       /* not found attempt to autoload module */
-       if (!ca && capable(CAP_NET_ADMIN)) {
-               rcu_read_unlock();
-               request_module("tcp_%s", name);
-               rcu_read_lock();
-               ca = tcp_ca_find(name);
-       }
-#endif
        if (!ca)
                err = -ENOENT;
-
        else if (!((ca->flags & TCP_CONG_NON_RESTRICTED) ||
                   ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)))
                err = -EPERM;
-
        else if (!try_module_get(ca->owner))
                err = -EBUSY;
-
-       else {
-               tcp_cleanup_congestion_control(sk);
-               icsk->icsk_ca_ops = ca;
-
-               if (sk->sk_state != TCP_CLOSE && icsk->icsk_ca_ops->init)
-                       icsk->icsk_ca_ops->init(sk);
-       }
+       else
+               tcp_reinit_congestion_control(sk, ca);
  out:
        rcu_read_unlock();
        return err;