|
@@ -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)
|
|
|
{
|
|
@@ -253,25 +326,17 @@ 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) ||
|