br_ioctl.c 9.0 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 *	Ioctl handler
 *	Linux ethernet bridge
 *
 *	Authors:
 *	Lennert Buytenhek		<buytenh@gnu.org>
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 */

14
#include <linux/capability.h>
L
Linus Torvalds 已提交
15 16 17
#include <linux/kernel.h>
#include <linux/if_bridge.h>
#include <linux/netdevice.h>
18
#include <linux/slab.h>
L
Linus Torvalds 已提交
19
#include <linux/times.h>
20
#include <net/net_namespace.h>
21
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
22 23
#include "br_private.h"

24
static int get_bridge_ifindices(struct net *net, int *indices, int num)
L
Linus Torvalds 已提交
25 26 27 28
{
	struct net_device *dev;
	int i = 0;

29 30
	rcu_read_lock();
	for_each_netdev_rcu(net, dev) {
31 32
		if (i >= num)
			break;
33
		if (dev->priv_flags & IFF_EBRIDGE)
L
Linus Torvalds 已提交
34 35
			indices[i++] = dev->ifindex;
	}
36
	rcu_read_unlock();
L
Linus Torvalds 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

	return i;
}

/* called with RTNL */
static void get_port_ifindices(struct net_bridge *br, int *ifindices, int num)
{
	struct net_bridge_port *p;

	list_for_each_entry(p, &br->port_list, list) {
		if (p->port_no < num)
			ifindices[p->port_no] = p->dev->ifindex;
	}
}

/*
 * Format up to a page worth of forwarding table entries
 * userbuf -- where to copy result
 * maxnum  -- maximum number of entries desired
 *            (limited to a page for sanity)
 * offset  -- number of records to skip
 */
59
static int get_fdb_entries(struct net_bridge *br, void __user *userbuf,
L
Linus Torvalds 已提交
60 61 62 63
			   unsigned long maxnum, unsigned long offset)
{
	int num;
	void *buf;
64
	size_t size;
L
Linus Torvalds 已提交
65

66 67
	/* Clamp size to PAGE_SIZE, test maxnum to avoid overflow */
	if (maxnum > PAGE_SIZE/sizeof(struct __fdb_entry))
L
Linus Torvalds 已提交
68
		maxnum = PAGE_SIZE/sizeof(struct __fdb_entry);
69 70

	size = maxnum * sizeof(struct __fdb_entry);
L
Linus Torvalds 已提交
71 72 73 74

	buf = kmalloc(size, GFP_USER);
	if (!buf)
		return -ENOMEM;
75

L
Linus Torvalds 已提交
76 77 78 79 80 81 82 83 84 85
	num = br_fdb_fillbuf(br, buf, maxnum, offset);
	if (num > 0) {
		if (copy_to_user(userbuf, buf, num*sizeof(struct __fdb_entry)))
			num = -EFAULT;
	}
	kfree(buf);

	return num;
}

86
/* called with RTNL */
L
Linus Torvalds 已提交
87 88
static int add_del_if(struct net_bridge *br, int ifindex, int isadd)
{
89
	struct net *net = dev_net(br->dev);
L
Linus Torvalds 已提交
90 91 92
	struct net_device *dev;
	int ret;

93
	if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
94 95
		return -EPERM;

96
	dev = __dev_get_by_index(net, ifindex);
L
Linus Torvalds 已提交
97 98
	if (dev == NULL)
		return -EINVAL;
99

L
Linus Torvalds 已提交
100
	if (isadd)
101
		ret = br_add_if(br, dev, NULL);
102
	else
L
Linus Torvalds 已提交
103 104 105 106 107 108 109 110
		ret = br_del_if(br, dev);

	return ret;
}

/*
 * Legacy ioctl's through SIOCDEVPRIVATE
 * This interface is deprecated because it was too difficult to
L
Lucas De Marchi 已提交
111
 * to do the translation for 32/64bit ioctl compatibility.
L
Linus Torvalds 已提交
112 113 114 115
 */
static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
	struct net_bridge *br = netdev_priv(dev);
116
	struct net_bridge_port *p = NULL;
L
Linus Torvalds 已提交
117
	unsigned long args[4];
118
	int ret = -EOPNOTSUPP;
119

L
Linus Torvalds 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
	if (copy_from_user(args, rq->ifr_data, sizeof(args)))
		return -EFAULT;

	switch (args[0]) {
	case BRCTL_ADD_IF:
	case BRCTL_DEL_IF:
		return add_del_if(br, args[1], args[0] == BRCTL_ADD_IF);

	case BRCTL_GET_BRIDGE_INFO:
	{
		struct __bridge_info b;

		memset(&b, 0, sizeof(struct __bridge_info));
		rcu_read_lock();
		memcpy(&b.designated_root, &br->designated_root, 8);
		memcpy(&b.bridge_id, &br->bridge_id, 8);
		b.root_path_cost = br->root_path_cost;
		b.max_age = jiffies_to_clock_t(br->max_age);
		b.hello_time = jiffies_to_clock_t(br->hello_time);
		b.forward_delay = br->forward_delay;
		b.bridge_max_age = br->bridge_max_age;
		b.bridge_hello_time = br->bridge_hello_time;
		b.bridge_forward_delay = jiffies_to_clock_t(br->bridge_forward_delay);
		b.topology_change = br->topology_change;
		b.topology_change_detected = br->topology_change_detected;
		b.root_port = br->root_port;
146 147

		b.stp_enabled = (br->stp_enabled != BR_NO_STP);
L
Linus Torvalds 已提交
148 149 150 151
		b.ageing_time = jiffies_to_clock_t(br->ageing_time);
		b.hello_timer_value = br_timer_value(&br->hello_timer);
		b.tcn_timer_value = br_timer_value(&br->tcn_timer);
		b.topology_change_timer_value = br_timer_value(&br->topology_change_timer);
152
		b.gc_timer_value = br_timer_value(&br->gc_work.timer);
153
		rcu_read_unlock();
L
Linus Torvalds 已提交
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

		if (copy_to_user((void __user *)args[1], &b, sizeof(b)))
			return -EFAULT;

		return 0;
	}

	case BRCTL_GET_PORT_LIST:
	{
		int num, *indices;

		num = args[2];
		if (num < 0)
			return -EINVAL;
		if (num == 0)
			num = 256;
		if (num > BR_MAX_PORTS)
			num = BR_MAX_PORTS;

173
		indices = kcalloc(num, sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
174 175 176 177 178 179 180 181 182 183 184
		if (indices == NULL)
			return -ENOMEM;

		get_port_ifindices(br, indices, num);
		if (copy_to_user((void __user *)args[1], indices, num*sizeof(int)))
			num =  -EFAULT;
		kfree(indices);
		return num;
	}

	case BRCTL_SET_BRIDGE_FORWARD_DELAY:
185
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
186 187
			return -EPERM;

188 189
		ret = br_set_forward_delay(br, args[1]);
		break;
L
Linus Torvalds 已提交
190 191

	case BRCTL_SET_BRIDGE_HELLO_TIME:
192
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
193 194
			return -EPERM;

195 196
		ret = br_set_hello_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
197 198

	case BRCTL_SET_BRIDGE_MAX_AGE:
199
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
200 201
			return -EPERM;

202 203
		ret = br_set_max_age(br, args[1]);
		break;
L
Linus Torvalds 已提交
204 205

	case BRCTL_SET_AGEING_TIME:
206
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
207 208
			return -EPERM;

209 210
		ret = br_set_ageing_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

	case BRCTL_GET_PORT_INFO:
	{
		struct __port_info p;
		struct net_bridge_port *pt;

		rcu_read_lock();
		if ((pt = br_get_port(br, args[2])) == NULL) {
			rcu_read_unlock();
			return -EINVAL;
		}

		memset(&p, 0, sizeof(struct __port_info));
		memcpy(&p.designated_root, &pt->designated_root, 8);
		memcpy(&p.designated_bridge, &pt->designated_bridge, 8);
		p.port_id = pt->port_id;
		p.designated_port = pt->designated_port;
		p.path_cost = pt->path_cost;
		p.designated_cost = pt->designated_cost;
		p.state = pt->state;
		p.top_change_ack = pt->topology_change_ack;
		p.config_pending = pt->config_pending;
		p.message_age_timer_value = br_timer_value(&pt->message_age_timer);
		p.forward_delay_timer_value = br_timer_value(&pt->forward_delay_timer);
		p.hold_timer_value = br_timer_value(&pt->hold_timer);

		rcu_read_unlock();

		if (copy_to_user((void __user *)args[1], &p, sizeof(p)))
			return -EFAULT;

		return 0;
	}

	case BRCTL_SET_BRIDGE_STP_STATE:
246
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
247 248
			return -EPERM;

249
		br_stp_set_enabled(br, args[1]);
250 251
		ret = 0;
		break;
L
Linus Torvalds 已提交
252 253

	case BRCTL_SET_BRIDGE_PRIORITY:
254
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
255 256 257
			return -EPERM;

		br_stp_set_bridge_priority(br, args[1]);
258 259
		ret = 0;
		break;
L
Linus Torvalds 已提交
260 261 262

	case BRCTL_SET_PORT_PRIORITY:
	{
263
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
264 265 266
			return -EPERM;

		spin_lock_bh(&br->lock);
267
		if ((p = br_get_port(br, args[1])) == NULL)
L
Linus Torvalds 已提交
268 269
			ret = -EINVAL;
		else
270
			ret = br_stp_set_port_priority(p, args[2]);
L
Linus Torvalds 已提交
271
		spin_unlock_bh(&br->lock);
272
		break;
L
Linus Torvalds 已提交
273 274 275 276
	}

	case BRCTL_SET_PATH_COST:
	{
277
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
278 279
			return -EPERM;

280
		spin_lock_bh(&br->lock);
L
Linus Torvalds 已提交
281 282 283
		if ((p = br_get_port(br, args[1])) == NULL)
			ret = -EINVAL;
		else
284 285
			ret = br_stp_set_path_cost(p, args[2]);
		spin_unlock_bh(&br->lock);
286
		break;
L
Linus Torvalds 已提交
287 288 289
	}

	case BRCTL_GET_FDB_ENTRIES:
290
		return get_fdb_entries(br, (void __user *)args[1],
L
Linus Torvalds 已提交
291 292 293
				       args[2], args[3]);
	}

294 295 296 297 298 299 300 301
	if (!ret) {
		if (p)
			br_ifinfo_notify(RTM_NEWLINK, p);
		else
			netdev_state_change(br->dev);
	}

	return ret;
L
Linus Torvalds 已提交
302 303
}

304
static int old_deviceless(struct net *net, void __user *uarg)
L
Linus Torvalds 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
{
	unsigned long args[3];

	if (copy_from_user(args, uarg, sizeof(args)))
		return -EFAULT;

	switch (args[0]) {
	case BRCTL_GET_VERSION:
		return BRCTL_VERSION;

	case BRCTL_GET_BRIDGES:
	{
		int *indices;
		int ret = 0;

		if (args[2] >= 2048)
			return -ENOMEM;
322
		indices = kcalloc(args[2], sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
323 324 325
		if (indices == NULL)
			return -ENOMEM;

326
		args[2] = get_bridge_ifindices(net, indices, args[2]);
L
Linus Torvalds 已提交
327 328 329 330 331 332 333 334 335 336 337 338 339

		ret = copy_to_user((void __user *)args[1], indices, args[2]*sizeof(int))
			? -EFAULT : args[2];

		kfree(indices);
		return ret;
	}

	case BRCTL_ADD_BRIDGE:
	case BRCTL_DEL_BRIDGE:
	{
		char buf[IFNAMSIZ];

340
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
341 342 343 344 345 346 347 348
			return -EPERM;

		if (copy_from_user(buf, (void __user *)args[1], IFNAMSIZ))
			return -EFAULT;

		buf[IFNAMSIZ-1] = 0;

		if (args[0] == BRCTL_ADD_BRIDGE)
349
			return br_add_bridge(net, buf);
L
Linus Torvalds 已提交
350

351
		return br_del_bridge(net, buf);
L
Linus Torvalds 已提交
352 353 354 355 356 357
	}
	}

	return -EOPNOTSUPP;
}

358
int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd, void __user *uarg)
L
Linus Torvalds 已提交
359 360 361 362
{
	switch (cmd) {
	case SIOCGIFBR:
	case SIOCSIFBR:
363
		return old_deviceless(net, uarg);
364

L
Linus Torvalds 已提交
365 366 367 368 369
	case SIOCBRADDBR:
	case SIOCBRDELBR:
	{
		char buf[IFNAMSIZ];

370
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
371 372 373 374 375 376 377
			return -EPERM;

		if (copy_from_user(buf, uarg, IFNAMSIZ))
			return -EFAULT;

		buf[IFNAMSIZ-1] = 0;
		if (cmd == SIOCBRADDBR)
378
			return br_add_bridge(net, buf);
L
Linus Torvalds 已提交
379

380
		return br_del_bridge(net, buf);
L
Linus Torvalds 已提交
381 382 383 384 385 386 387 388 389
	}
	}
	return -EOPNOTSUPP;
}

int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
	struct net_bridge *br = netdev_priv(dev);

390
	switch (cmd) {
L
Linus Torvalds 已提交
391 392 393 394 395 396 397 398 399
	case SIOCDEVPRIVATE:
		return old_dev_ioctl(dev, rq, cmd);

	case SIOCBRADDIF:
	case SIOCBRDELIF:
		return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF);

	}

400
	br_debug(br, "Bridge does not support ioctl 0x%x\n", cmd);
L
Linus Torvalds 已提交
401 402
	return -EOPNOTSUPP;
}