summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/networking/ip-sysctl.txt6
-rw-r--r--include/linux/sysctl.h1
-rw-r--r--include/net/tcp.h3
-rw-r--r--net/ipv4/sysctl_net_ipv4.c52
-rw-r--r--net/ipv4/tcp_cong.c63
5 files changed, 125 insertions, 0 deletions
diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt
index db4280856588..bbcc8deda172 100644
--- a/Documentation/networking/ip-sysctl.txt
+++ b/Documentation/networking/ip-sysctl.txt
@@ -351,6 +351,12 @@ tcp_frto - BOOLEAN
where packet loss is typically due to random radio interference
rather than intermediate router congestion.
+tcp_allowed_congestion_control - STRING
+ Show/set the congestion control choices available to non-privileged
+ processes. The list is a subset of those listed in
+ tcp_available_congestion_control.
+ Default is "reno" and the default setting (tcp_congestion_control).
+
tcp_available_congestion_control - STRING
Shows the available congestion control choices that are registered.
More congestion control algorithms may be available as modules,
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 28a48279654d..0725441621d0 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -427,6 +427,7 @@ enum
NET_CIPSOV4_RBM_OPTFMT=120,
NET_CIPSOV4_RBM_STRICTVALID=121,
NET_TCP_AVAIL_CONG_CONTROL=122,
+ NET_TCP_ALLOWED_CONG_CONTROL=123,
};
enum {
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 6af4baf5b769..e1a5d29d0a1f 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -625,6 +625,7 @@ enum tcp_ca_event {
struct tcp_congestion_ops {
struct list_head list;
+ int non_restricted;
/* initialize private data (optional) */
void (*init)(struct sock *sk);
@@ -663,6 +664,8 @@ extern void tcp_cleanup_congestion_control(struct sock *sk);
extern int tcp_set_default_congestion_control(const char *name);
extern void tcp_get_default_congestion_control(char *name);
extern void tcp_get_available_congestion_control(char *buf, size_t len);
+extern void tcp_get_allowed_congestion_control(char *buf, size_t len);
+extern int tcp_set_allowed_congestion_control(char *allowed);
extern int tcp_set_congestion_control(struct sock *sk, const char *name);
extern void tcp_slow_start(struct tcp_sock *tp);
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index 2e770f45d829..dfcf47f10f88 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -146,6 +146,50 @@ static int proc_tcp_available_congestion_control(ctl_table *ctl,
return ret;
}
+static int proc_allowed_congestion_control(ctl_table *ctl,
+ int write, struct file * filp,
+ void __user *buffer, size_t *lenp,
+ loff_t *ppos)
+{
+ ctl_table tbl = { .maxlen = TCP_CA_BUF_MAX };
+ int ret;
+
+ tbl.data = kmalloc(tbl.maxlen, GFP_USER);
+ if (!tbl.data)
+ return -ENOMEM;
+
+ tcp_get_allowed_congestion_control(tbl.data, tbl.maxlen);
+ ret = proc_dostring(&tbl, write, filp, buffer, lenp, ppos);
+ if (write && ret == 0)
+ ret = tcp_set_allowed_congestion_control(tbl.data);
+ kfree(tbl.data);
+ return ret;
+}
+
+static int strategy_allowed_congestion_control(ctl_table *table, int __user *name,
+ int nlen, void __user *oldval,
+ size_t __user *oldlenp,
+ void __user *newval, size_t newlen,
+ void **context)
+{
+ ctl_table tbl = { .maxlen = TCP_CA_BUF_MAX };
+ int ret;
+
+ tbl.data = kmalloc(tbl.maxlen, GFP_USER);
+ if (!tbl.data)
+ return -ENOMEM;
+
+ tcp_get_available_congestion_control(tbl.data, tbl.maxlen);
+ ret = sysctl_string(&tbl, name, nlen, oldval, oldlenp, newval, newlen,
+ context);
+ if (ret == 0 && newval && newlen)
+ ret = tcp_set_allowed_congestion_control(tbl.data);
+ kfree(tbl.data);
+
+ return ret;
+
+}
+
ctl_table ipv4_table[] = {
{
.ctl_name = NET_IPV4_TCP_TIMESTAMPS,
@@ -755,6 +799,14 @@ ctl_table ipv4_table[] = {
.mode = 0444,
.proc_handler = &proc_tcp_available_congestion_control,
},
+ {
+ .ctl_name = NET_TCP_ALLOWED_CONG_CONTROL,
+ .procname = "tcp_allowed_congestion_control",
+ .maxlen = TCP_CA_BUF_MAX,
+ .mode = 0644,
+ .proc_handler = &proc_allowed_congestion_control,
+ .strategy = &strategy_allowed_congestion_control,
+ },
{ .ctl_name = 0 }
};
diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
index d846d7b95e1f..343d6197c92e 100644
--- a/net/ipv4/tcp_cong.c
+++ b/net/ipv4/tcp_cong.c
@@ -123,6 +123,7 @@ int tcp_set_default_congestion_control(const char *name)
#endif
if (ca) {
+ ca->non_restricted = 1; /* default is always allowed */
list_move(&ca->list, &tcp_cong_list);
ret = 0;
}
@@ -168,6 +169,64 @@ void tcp_get_default_congestion_control(char *name)
rcu_read_unlock();
}
+/* Built list of non-restricted congestion control values */
+void tcp_get_allowed_congestion_control(char *buf, size_t maxlen)
+{
+ struct tcp_congestion_ops *ca;
+ size_t offs = 0;
+
+ *buf = '\0';
+ rcu_read_lock();
+ list_for_each_entry_rcu(ca, &tcp_cong_list, list) {
+ if (!ca->non_restricted)
+ continue;
+ offs += snprintf(buf + offs, maxlen - offs,
+ "%s%s",
+ offs == 0 ? "" : " ", ca->name);
+
+ }
+ rcu_read_unlock();
+}
+
+/* Change list of non-restricted congestion control */
+int tcp_set_allowed_congestion_control(char *val)
+{
+ struct tcp_congestion_ops *ca;
+ char *clone, *name;
+ int ret = 0;
+
+ clone = kstrdup(val, GFP_USER);
+ if (!clone)
+ return -ENOMEM;
+
+ spin_lock(&tcp_cong_list_lock);
+ /* pass 1 check for bad entries */
+ while ((name = strsep(&clone, " ")) && *name) {
+ ca = tcp_ca_find(name);
+ if (!ca) {
+ ret = -ENOENT;
+ goto out;
+ }
+ }
+
+ /* pass 2 clear */
+ list_for_each_entry_rcu(ca, &tcp_cong_list, list)
+ ca->non_restricted = 0;
+
+ /* pass 3 mark as allowed */
+ while ((name = strsep(&val, " ")) && *name) {
+ ca = tcp_ca_find(name);
+ WARN_ON(!ca);
+ if (ca)
+ ca->non_restricted = 1;
+ }
+out:
+ spin_unlock(&tcp_cong_list_lock);
+
+ return ret;
+}
+
+
/* Change congestion control for socket */
int tcp_set_congestion_control(struct sock *sk, const char *name)
{
@@ -183,6 +242,9 @@ int tcp_set_congestion_control(struct sock *sk, const char *name)
if (!ca)
err = -ENOENT;
+ else if (!(ca->non_restricted || capable(CAP_NET_ADMIN)))
+ err = -EPERM;
+
else if (!try_module_get(ca->owner))
err = -EBUSY;
@@ -284,6 +346,7 @@ EXPORT_SYMBOL_GPL(tcp_reno_min_cwnd);
struct tcp_congestion_ops tcp_reno = {
.name = "reno",
+ .non_restricted = 1,
.owner = THIS_MODULE,
.ssthresh = tcp_reno_ssthresh,
.cong_avoid = tcp_reno_cong_avoid,