diff options
Diffstat (limited to 'net/ipv4/tcp_cong.c')
| -rw-r--r-- | net/ipv4/tcp_cong.c | 153 | 
1 files changed, 115 insertions, 38 deletions
| diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c index 27ead0dd16bc..d694088214cd 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; @@ -291,26 +360,32 @@ int tcp_set_congestion_control(struct sock *sk, const char *name)   * ABC caps N to 2. Slow start exits when cwnd grows over ssthresh and   * returns the leftover acks to adjust cwnd in congestion avoidance mode.   */ -void tcp_slow_start(struct tcp_sock *tp, u32 acked) +u32 tcp_slow_start(struct tcp_sock *tp, u32 acked)  {  	u32 cwnd = tp->snd_cwnd + acked;  	if (cwnd > tp->snd_ssthresh)  		cwnd = tp->snd_ssthresh + 1; +	acked -= cwnd - tp->snd_cwnd;  	tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp); + +	return acked;  }  EXPORT_SYMBOL_GPL(tcp_slow_start); -/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd (or alternative w) */ -void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w) +/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd (or alternative w), + * for every packet that was ACKed. + */ +void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked)  { +	tp->snd_cwnd_cnt += acked;  	if (tp->snd_cwnd_cnt >= w) { -		if (tp->snd_cwnd < tp->snd_cwnd_clamp) -			tp->snd_cwnd++; -		tp->snd_cwnd_cnt = 0; -	} else { -		tp->snd_cwnd_cnt++; +		u32 delta = tp->snd_cwnd_cnt / w; + +		tp->snd_cwnd_cnt -= delta * w; +		tp->snd_cwnd += delta;  	} +	tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_cwnd_clamp);  }  EXPORT_SYMBOL_GPL(tcp_cong_avoid_ai); @@ -329,11 +404,13 @@ void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 acked)  		return;  	/* In "safe" area, increase. */ -	if (tp->snd_cwnd <= tp->snd_ssthresh) -		tcp_slow_start(tp, acked); +	if (tp->snd_cwnd <= tp->snd_ssthresh) { +		acked = tcp_slow_start(tp, acked); +		if (!acked) +			return; +	}  	/* In dangerous area, increase slowly. */ -	else -		tcp_cong_avoid_ai(tp, tp->snd_cwnd); +	tcp_cong_avoid_ai(tp, tp->snd_cwnd, acked);  }  EXPORT_SYMBOL_GPL(tcp_reno_cong_avoid); |