1/*
2 * Copyright (C) 2000 Lennert Buytenhek
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <unistd.h>
23#include <errno.h>
24#include <string.h>
25#include <sys/fcntl.h>
26
27#include "libbridge.h"
28#include "libbridge_private.h"
29
30#ifdef HAVE_LIBSYSFS
31/* Given two two character "0a" convert it to a byte */
32static unsigned char getoctet(const char *cp)
33{
34	char t[3] = { cp[0], cp[1], 0 };
35	return strtoul(t, NULL, 16);
36}
37
38static struct sysfs_directory *bridge_sysfs_directory(const char *devname,
39						      const char *subname)
40{
41	struct sysfs_directory *sdir;
42	struct sysfs_class_device *dev;
43	char path[SYSFS_PATH_MAX];
44
45	if (!devname)
46		return NULL;
47
48	if (!br_class_net) {
49		dprintf("can't find class_net\n");
50		return NULL;
51	}
52
53	dev = sysfs_get_class_device(br_class_net, (char *) devname);
54	if (!dev) {
55		dprintf("can't find device %s in %s\n", devname, br_class_net->path);
56		return NULL;
57	}
58
59	snprintf(path, SYSFS_PATH_MAX, "%s/%s", dev->path, subname);
60	sdir = sysfs_open_directory(path);
61	if (!sdir)
62		dprintf("can't open directory: %s\n", path);
63	return sdir;
64}
65
66static void fetch_id(struct sysfs_directory *sdir, const char *name,
67		     struct bridge_id *id)
68{
69	struct sysfs_attribute *attr;
70
71	memset(id, 0, sizeof(id));
72	attr = sysfs_get_directory_attribute(sdir, (char *) name);
73	if (!attr) {
74		dprintf("Can't find attribute %s/%s\n", sdir->path, name);
75		return;
76	}
77
78	if (strlen(attr->value) < 17)
79		dprintf("Bad format for %s: '%s'\n", name, attr->value);
80	else {
81		const char *cp = attr->value;
82		id->prio[0] = getoctet(cp); cp += 2;
83		id->prio[1] = getoctet(cp); cp += 3;
84		id->addr[0] = getoctet(cp); cp += 2;
85		id->addr[1] = getoctet(cp); cp += 2;
86		id->addr[2] = getoctet(cp); cp += 2;
87		id->addr[3] = getoctet(cp); cp += 2;
88		id->addr[4] = getoctet(cp); cp += 2;
89		id->addr[5] = getoctet(cp);
90	}
91}
92
93/* Get a time value out of sysfs */
94static void fetch_tv(struct sysfs_directory *sdir, const char *name,
95		     struct timeval *tv)
96{
97	struct sysfs_attribute *attr
98		= sysfs_get_directory_attribute(sdir, (char *) name);
99
100	if (!attr) {
101		dprintf("Can't find attribute %s/%s\n", sdir->path, name);
102		memset(tv, 0, sizeof(tv));
103		return;
104	}
105
106	__jiffies_to_tv(tv, strtoul(attr->value, NULL, 0));
107}
108
109/* Fetch an integer attribute out of sysfs. */
110static int fetch_int(struct sysfs_directory *sdir, const char *name)
111{
112	struct sysfs_attribute *attr
113		= sysfs_get_directory_attribute(sdir, (char *) name);
114	int val = 0;
115
116	if (!attr)
117		dprintf("Can't find attribute %s/%s\n", sdir->path, name);
118	else
119		val = strtol(attr->value, NULL, 0);
120	return val;
121}
122#endif
123
124/*
125 * Convert device name to an index in the list of ports in bridge.
126 *
127 * Old API does bridge operations as if ports were an array
128 * inside bridge structure.
129 */
130static int get_portno(const char *brname, const char *ifname)
131{
132	int i;
133	int ifindex = if_nametoindex(ifname);
134	int ifindices[MAX_PORTS];
135	unsigned long args[4] = { BRCTL_GET_PORT_LIST,
136				  (unsigned long)ifindices, MAX_PORTS, 0 };
137	struct ifreq ifr;
138
139	if (ifindex <= 0)
140		goto error;
141
142	memset(ifindices, 0, sizeof(ifindices));
143	strncpy(ifr.ifr_name, brname, IFNAMSIZ);
144	ifr.ifr_data = (char *) &args;
145
146	if (ioctl(br_socket_fd, SIOCDEVPRIVATE, &ifr) < 0) {
147		dprintf("get_portno: get ports of %s failed: %s\n",
148			brname, strerror(errno));
149		goto error;
150	}
151
152	for (i = 0; i < MAX_PORTS; i++) {
153		if (ifindices[i] == ifindex)
154			return i;
155	}
156
157	dprintf("%s is not a in bridge %s\n", ifname, brname);
158 error:
159	return -1;
160}
161
162/* get information via ioctl */
163static int old_get_bridge_info(const char *bridge, struct bridge_info *info)
164{
165	struct ifreq ifr;
166	struct __bridge_info i;
167	unsigned long args[4] = { BRCTL_GET_BRIDGE_INFO,
168				  (unsigned long) &i, 0, 0 };
169
170	memset(info, 0, sizeof(*info));
171	strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
172	ifr.ifr_data = (char *) &args;
173
174	if (ioctl(br_socket_fd, SIOCDEVPRIVATE, &ifr) < 0) {
175		dprintf("%s: can't get info %s\n",
176			bridge, strerror(errno));
177		return errno;
178	}
179
180	memcpy(&info->designated_root, &i.designated_root, 8);
181	memcpy(&info->bridge_id, &i.bridge_id, 8);
182	info->root_path_cost = i.root_path_cost;
183	info->root_port = i.root_port;
184	info->topology_change = i.topology_change;
185	info->topology_change_detected = i.topology_change_detected;
186	info->stp_enabled = i.stp_enabled;
187	__jiffies_to_tv(&info->max_age, i.max_age);
188	__jiffies_to_tv(&info->hello_time, i.hello_time);
189	__jiffies_to_tv(&info->forward_delay, i.forward_delay);
190	__jiffies_to_tv(&info->bridge_max_age, i.bridge_max_age);
191	__jiffies_to_tv(&info->bridge_hello_time, i.bridge_hello_time);
192	__jiffies_to_tv(&info->bridge_forward_delay, i.bridge_forward_delay);
193	__jiffies_to_tv(&info->ageing_time, i.ageing_time);
194	__jiffies_to_tv(&info->hello_timer_value, i.hello_timer_value);
195	__jiffies_to_tv(&info->tcn_timer_value, i.tcn_timer_value);
196	__jiffies_to_tv(&info->topology_change_timer_value,
197			i.topology_change_timer_value);
198	__jiffies_to_tv(&info->gc_timer_value, i.gc_timer_value);
199
200	return 0;
201}
202
203/*
204 * Get bridge parameters using either sysfs or old
205 * ioctl.
206 */
207int br_get_bridge_info(const char *bridge, struct bridge_info *info)
208{
209#ifndef HAVE_LIBSYSFS
210	return old_get_bridge_info(bridge, info);
211#else
212	struct sysfs_directory *sdir;
213
214	sdir = bridge_sysfs_directory(bridge, SYSFS_BRIDGE_ATTR);
215	if (!sdir)
216		return old_get_bridge_info(bridge,info);
217
218	memset(info, 0, sizeof(*info));
219	fetch_id(sdir, "root_id", &info->designated_root);
220	fetch_id(sdir, "bridge_id", &info->bridge_id);
221	info->root_path_cost = fetch_int(sdir, "root_path_cost");
222	fetch_tv(sdir, "max_age", &info->max_age);
223	fetch_tv(sdir, "hello_time", &info->hello_time);
224	fetch_tv(sdir, "forward_delay", &info->forward_delay);
225	fetch_tv(sdir, "max_age", &info->bridge_max_age);
226	fetch_tv(sdir, "hello_time", &info->bridge_hello_time);
227	fetch_tv(sdir, "forward_delay", &info->bridge_forward_delay);
228	fetch_tv(sdir, "ageing_time", &info->ageing_time);
229	fetch_tv(sdir, "hello_timer", &info->hello_timer_value);
230	fetch_tv(sdir, "tcn_timer", &info->tcn_timer_value);
231	fetch_tv(sdir, "topology_change_timer",
232		 &info->topology_change_timer_value);;
233	fetch_tv(sdir, "gc_timer", &info->gc_timer_value);
234
235	info->root_port = fetch_int(sdir, "root_port");
236	info->stp_enabled = fetch_int(sdir, "stp_state");
237	info->topology_change = fetch_int(sdir, "topology_change");
238	info->topology_change_detected = fetch_int(sdir, "topology_change_detected");
239	sysfs_close_directory(sdir);
240
241	return 0;
242#endif
243}
244
245static int old_get_port_info(const char *brname, const char *port,
246			     struct port_info *info)
247{
248	struct __port_info i;
249	int index;
250
251	memset(info, 0, sizeof(*info));
252
253	index = get_portno(brname, port);
254	if (index < 0)
255		return errno;
256
257	else {
258		struct ifreq ifr;
259		unsigned long args[4] = { BRCTL_GET_PORT_INFO,
260					   (unsigned long) &i, index, 0 };
261
262		strncpy(ifr.ifr_name, brname, IFNAMSIZ);
263		ifr.ifr_data = (char *) &args;
264
265		if (ioctl(br_socket_fd, SIOCDEVPRIVATE, &ifr) < 0) {
266			dprintf("old can't get port %s(%d) info %s\n",
267				brname, index, strerror(errno));
268			return errno;
269		}
270	}
271
272	info->port_no = index;
273	memcpy(&info->designated_root, &i.designated_root, 8);
274	memcpy(&info->designated_bridge, &i.designated_bridge, 8);
275	info->port_id = i.port_id;
276	info->designated_port = i.designated_port;
277	info->path_cost = i.path_cost;
278	info->designated_cost = i.designated_cost;
279	info->state = i.state;
280	info->top_change_ack = i.top_change_ack;
281	info->config_pending = i.config_pending;
282	__jiffies_to_tv(&info->message_age_timer_value,
283			i.message_age_timer_value);
284	__jiffies_to_tv(&info->forward_delay_timer_value,
285			i.forward_delay_timer_value);
286	__jiffies_to_tv(&info->hold_timer_value, i.hold_timer_value);
287	return 0;
288}
289
290/*
291 * Get information about port on bridge.
292 */
293int br_get_port_info(const char *brname, const char *port,
294		     struct port_info *info)
295{
296#ifndef HAVE_LIBSYSFS
297	return old_get_port_info(brname, port, info);
298#else
299	struct sysfs_directory *sdir
300		= bridge_sysfs_directory(port, SYSFS_BRIDGE_PORT_ATTR);
301
302	if (!sdir)
303		return old_get_port_info(brname, port, info);
304
305	memset(info, 0, sizeof(*info));
306	fetch_id(sdir, "designated_root", &info->designated_root);
307	fetch_id(sdir, "designated_bridge", &info->designated_bridge);
308	info->port_no = fetch_int(sdir, "port_no");
309	info->port_id = fetch_int(sdir, "port_id");
310	info->designated_port = fetch_int(sdir, "designated_port");
311	info->path_cost = fetch_int(sdir, "path_cost");
312	info->designated_cost = fetch_int(sdir, "designated_cost");
313	info->state = fetch_int(sdir, "state");
314	info->top_change_ack = fetch_int(sdir, "change_ack");
315	info->config_pending = fetch_int(sdir, "config_pending");
316	fetch_tv(sdir, "message_age_timer",
317		 &info->message_age_timer_value);
318	fetch_tv(sdir, "forward_delay_timer",
319		 &info->forward_delay_timer_value);
320	fetch_tv(sdir, "hold_timer",
321		 &info->hold_timer_value);
322	sysfs_close_directory(sdir);
323
324	return 0;
325#endif
326}
327
328
329static int br_set(const char *bridge, const char *name,
330		  unsigned long value, unsigned long oldcode)
331{
332	int ret;
333#ifdef HAVE_LIBSYSFS
334	struct sysfs_directory *sdir;
335
336	sdir = bridge_sysfs_directory(bridge, SYSFS_BRIDGE_ATTR);
337	if (sdir) {
338		struct sysfs_attribute *attr;
339		char buf[32];
340		sprintf(buf, "%ld", value);
341
342		attr = sysfs_get_directory_attribute(sdir, (char *) name);
343		if (attr)
344			ret = sysfs_write_attribute(attr, buf, strlen(buf));
345		else {
346			ret = -1;
347			errno = EINVAL;
348		}
349		sysfs_close_directory(sdir);
350	} else
351#endif
352	{
353		struct ifreq ifr;
354		unsigned long args[4] = { oldcode, value, 0, 0 };
355
356		strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
357		ifr.ifr_data = (char *) &args;
358		ret = ioctl(br_socket_fd, SIOCDEVPRIVATE, &ifr);
359	}
360
361	return ret < 0 ? errno : 0;
362}
363
364int br_set_bridge_forward_delay(const char *br, struct timeval *tv)
365{
366	return br_set(br, "forward_delay", __tv_to_jiffies(tv),
367		      BRCTL_SET_BRIDGE_FORWARD_DELAY);
368}
369
370int br_set_bridge_hello_time(const char *br, struct timeval *tv)
371{
372	return br_set(br, "hello_time", __tv_to_jiffies(tv),
373		      BRCTL_SET_BRIDGE_HELLO_TIME);
374}
375
376int br_set_bridge_max_age(const char *br, struct timeval *tv)
377{
378	return br_set(br, "max_age", __tv_to_jiffies(tv),
379		      BRCTL_SET_BRIDGE_MAX_AGE);
380}
381
382int br_set_ageing_time(const char *br, struct timeval *tv)
383{
384	return br_set(br, "ageing_time", __tv_to_jiffies(tv),
385		      BRCTL_SET_AGEING_TIME);
386}
387
388int br_set_stp_state(const char *br, int stp_state)
389{
390	return br_set(br, "stp_state", stp_state, BRCTL_SET_BRIDGE_STP_STATE);
391}
392
393int br_set_bridge_priority(const char *br, int bridge_priority)
394{
395	return br_set(br, "priority", bridge_priority,
396		      BRCTL_SET_BRIDGE_PRIORITY);
397}
398
399static int port_set(const char *bridge, const char *ifname,
400		    const char *name, unsigned long value,
401		    unsigned long oldcode)
402{
403	int ret, index;
404#ifdef HAVE_LIBSYSFS
405	struct sysfs_directory *sdir;
406
407	sdir = bridge_sysfs_directory(ifname, SYSFS_BRIDGE_PORT_ATTR);
408	if (sdir) {
409		struct sysfs_attribute *attr;
410		char buf[32];
411
412		sprintf(buf, "%ld", value);
413
414		attr = sysfs_get_directory_attribute(sdir, (char *) name);
415		if (attr)
416			ret = sysfs_write_attribute(attr, buf, strlen(buf));
417		else {
418			ret = -1;
419			errno = EINVAL;
420		}
421		sysfs_close_directory(sdir);
422	} else
423#endif
424	if ( (index = get_portno(bridge, ifname)) < 0)
425		ret = index;
426
427	else {
428		struct ifreq ifr;
429		unsigned long args[4] = { oldcode, index, value, 0 };
430
431		strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
432		ifr.ifr_data = (char *) &args;
433		ret = ioctl(br_socket_fd, SIOCDEVPRIVATE, &ifr);
434	}
435
436	return ret < 0 ? errno : 0;
437}
438
439int br_set_port_priority(const char *bridge, const char *port, int priority)
440{
441	return port_set(bridge, port, "priority", priority, BRCTL_SET_PORT_PRIORITY);
442}
443
444int br_set_path_cost(const char *bridge, const char *port, int cost)
445{
446	return port_set(bridge, port, "path_cost", cost, BRCTL_SET_PATH_COST);
447}
448
449static inline void __copy_fdb(struct fdb_entry *ent,
450			      const struct __fdb_entry *f)
451{
452	memcpy(ent->mac_addr, f->mac_addr, 6);
453	ent->port_no = f->port_no;
454	ent->is_local = f->is_local;
455	__jiffies_to_tv(&ent->ageing_timer_value, f->ageing_timer_value);
456}
457
458int br_read_fdb(const char *bridge, struct fdb_entry *fdbs,
459		unsigned long offset, int num)
460{
461	int i, fd = -1, n;
462	struct __fdb_entry fe[num];
463#ifdef HAVE_LIBSYSFS
464	struct sysfs_class_device *dev;
465
466	/* open /sys/class/net/brXXX/brforward */
467	if (br_class_net &&
468	    (dev = sysfs_get_class_device(br_class_net, (char *) bridge))) {
469		char path[SYSFS_PATH_MAX];
470
471		snprintf(path, SYSFS_PATH_MAX, "%s/%s", dev->path,
472			 SYSFS_BRIDGE_FDB);
473		fd = open(path, O_RDONLY, 0);
474	}
475
476	if (fd != -1) {
477		/* read records from file */
478		lseek(fd, offset*sizeof(struct __fdb_entry), SEEK_SET);
479		n = read(fd, fe, num*sizeof(struct __fdb_entry));
480		if (n > 0)
481			n /= sizeof(struct __fdb_entry);
482	} else
483#endif
484	{
485		/* old kernel, use ioctl */
486		unsigned long args[4] = { BRCTL_GET_FDB_ENTRIES,
487					  (unsigned long) fe,
488					  num, offset };
489		struct ifreq ifr;
490		int retries = 0;
491
492		strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
493		ifr.ifr_data = (char *) args;
494
495	retry:
496		n = ioctl(br_socket_fd, SIOCDEVPRIVATE, &ifr);
497
498		/* table can change during ioctl processing */
499		if (n < 0 && errno == EAGAIN && ++retries < 10) {
500			sleep(0);
501			goto retry;
502		}
503	}
504
505	for (i = 0; i < n; i++)
506		__copy_fdb(fdbs+i, fe+i);
507
508	if (fd > 0)
509		close(fd);
510
511	return n;
512}
513