summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/mpls/af_mpls.c146
1 files changed, 146 insertions, 0 deletions
diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c
index 924377736b2a..b097125dfa33 100644
--- a/net/mpls/af_mpls.c
+++ b/net/mpls/af_mpls.c
@@ -1,6 +1,7 @@
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
+#include <linux/sysctl.h>
#include <linux/net.h>
#include <linux/module.h>
#include <linux/if_arp.h>
@@ -31,6 +32,9 @@ struct mpls_route { /* next hop label forwarding entry */
u8 rt_via[0];
};
+static int zero = 0;
+static int label_limit = (1 << 20) - 1;
+
static struct mpls_route *mpls_route_input_rcu(struct net *net, unsigned index)
{
struct mpls_route *rt = NULL;
@@ -273,18 +277,160 @@ static struct notifier_block mpls_dev_notifier = {
.notifier_call = mpls_dev_notify,
};
+static int resize_platform_label_table(struct net *net, size_t limit)
+{
+ size_t size = sizeof(struct mpls_route *) * limit;
+ size_t old_limit;
+ size_t cp_size;
+ struct mpls_route __rcu **labels = NULL, **old;
+ struct mpls_route *rt0 = NULL, *rt2 = NULL;
+ unsigned index;
+
+ if (size) {
+ labels = kzalloc(size, GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY);
+ if (!labels)
+ labels = vzalloc(size);
+
+ if (!labels)
+ goto nolabels;
+ }
+
+ /* In case the predefined labels need to be populated */
+ if (limit > LABEL_IPV4_EXPLICIT_NULL) {
+ struct net_device *lo = net->loopback_dev;
+ rt0 = mpls_rt_alloc(lo->addr_len);
+ if (!rt0)
+ goto nort0;
+ rt0->rt_dev = lo;
+ rt0->rt_protocol = RTPROT_KERNEL;
+ rt0->rt_via_family = AF_PACKET;
+ memcpy(rt0->rt_via, lo->dev_addr, lo->addr_len);
+ }
+ if (limit > LABEL_IPV6_EXPLICIT_NULL) {
+ struct net_device *lo = net->loopback_dev;
+ rt2 = mpls_rt_alloc(lo->addr_len);
+ if (!rt2)
+ goto nort2;
+ rt2->rt_dev = lo;
+ rt2->rt_protocol = RTPROT_KERNEL;
+ rt2->rt_via_family = AF_PACKET;
+ memcpy(rt2->rt_via, lo->dev_addr, lo->addr_len);
+ }
+
+ rtnl_lock();
+ /* Remember the original table */
+ old = net->mpls.platform_label;
+ old_limit = net->mpls.platform_labels;
+
+ /* Free any labels beyond the new table */
+ for (index = limit; index < old_limit; index++)
+ mpls_route_update(net, index, NULL, NULL, NULL);
+
+ /* Copy over the old labels */
+ cp_size = size;
+ if (old_limit < limit)
+ cp_size = old_limit * sizeof(struct mpls_route *);
+
+ memcpy(labels, old, cp_size);
+
+ /* If needed set the predefined labels */
+ if ((old_limit <= LABEL_IPV6_EXPLICIT_NULL) &&
+ (limit > LABEL_IPV6_EXPLICIT_NULL)) {
+ labels[LABEL_IPV6_EXPLICIT_NULL] = rt2;
+ rt2 = NULL;
+ }
+
+ if ((old_limit <= LABEL_IPV4_EXPLICIT_NULL) &&
+ (limit > LABEL_IPV4_EXPLICIT_NULL)) {
+ labels[LABEL_IPV4_EXPLICIT_NULL] = rt0;
+ rt0 = NULL;
+ }
+
+ /* Update the global pointers */
+ net->mpls.platform_labels = limit;
+ net->mpls.platform_label = labels;
+
+ rtnl_unlock();
+
+ mpls_rt_free(rt2);
+ mpls_rt_free(rt0);
+
+ if (old) {
+ synchronize_rcu();
+ kvfree(old);
+ }
+ return 0;
+
+nort2:
+ mpls_rt_free(rt0);
+nort0:
+ kvfree(labels);
+nolabels:
+ return -ENOMEM;
+}
+
+static int mpls_platform_labels(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+ struct net *net = table->data;
+ int platform_labels = net->mpls.platform_labels;
+ int ret;
+ struct ctl_table tmp = {
+ .procname = table->procname,
+ .data = &platform_labels,
+ .maxlen = sizeof(int),
+ .mode = table->mode,
+ .extra1 = &zero,
+ .extra2 = &label_limit,
+ };
+
+ ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+
+ if (write && ret == 0)
+ ret = resize_platform_label_table(net, platform_labels);
+
+ return ret;
+}
+
+static struct ctl_table mpls_table[] = {
+ {
+ .procname = "platform_labels",
+ .data = NULL,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = mpls_platform_labels,
+ },
+ { }
+};
+
static int mpls_net_init(struct net *net)
{
+ struct ctl_table *table;
+
net->mpls.platform_labels = 0;
net->mpls.platform_label = NULL;
+ table = kmemdup(mpls_table, sizeof(mpls_table), GFP_KERNEL);
+ if (table == NULL)
+ return -ENOMEM;
+
+ table[0].data = net;
+ net->mpls.ctl = register_net_sysctl(net, "net/mpls", table);
+ if (net->mpls.ctl == NULL)
+ return -ENOMEM;
+
return 0;
}
static void mpls_net_exit(struct net *net)
{
+ struct ctl_table *table;
unsigned int index;
+ table = net->mpls.ctl->ctl_table_arg;
+ unregister_net_sysctl_table(net->mpls.ctl);
+ kfree(table);
+
/* An rcu grace period haselapsed since there was a device in
* the network namespace (and thus the last in fqlight packet)
* left this network namespace. This is because