diff options
| author | Chris Zankel <[email protected]> | 2015-04-14 03:51:35 +0000 | 
|---|---|---|
| committer | Chris Zankel <[email protected]> | 2015-04-14 03:51:35 +0000 | 
| commit | 7ead5b7e4a3cf4a16579a8f164022345b93fe972 (patch) | |
| tree | 0a9b9497f53d1593c9e2ac197b2e686ea74a9975 /net/ipv4/tcp_cong.c | |
| parent | 834a316eeebcb75316c0a7d9088fa638c52dc584 (diff) | |
| parent | 39a8804455fb23f09157341d3ba7db6d7ae6ee76 (diff) | |
Merge tag 'v4.0' into for_next
Linux 4.0
Diffstat (limited to 'net/ipv4/tcp_cong.c')
| -rw-r--r-- | net/ipv4/tcp_cong.c | 127 | 
1 files changed, 101 insertions, 26 deletions
| diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c index 8670e68e2ce6..62856e185a93 100644 --- a/net/ipv4/tcp_cong.c +++ b/net/ipv4/tcp_cong.c @@ -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; @@ -309,6 +378,12 @@ EXPORT_SYMBOL_GPL(tcp_slow_start);   */  void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked)  { +	/* If credits accumulated at a higher w, apply them gently now. */ +	if (tp->snd_cwnd_cnt >= w) { +		tp->snd_cwnd_cnt = 0; +		tp->snd_cwnd++; +	} +  	tp->snd_cwnd_cnt += acked;  	if (tp->snd_cwnd_cnt >= w) {  		u32 delta = tp->snd_cwnd_cnt / w; |