1 /* vi: set sw=4 ts=4: */
2 /*
3  * Small implementation of brctl for busybox.
4  *
5  * Copyright (C) 2008 by Bernhard Reutner-Fischer
6  *
7  * Some helper functions from bridge-utils are
8  * Copyright (C) 2000 Lennert Buytenhek
9  *
10  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11  */
12 //config:config BRCTL
13 //config:	bool "brctl (4.7 kb)"
14 //config:	default y
15 //config:	help
16 //config:	Manage ethernet bridges.
17 //config:	Supports addbr/delbr and addif/delif.
18 //config:
19 //config:config FEATURE_BRCTL_FANCY
20 //config:	bool "Fancy options"
21 //config:	default y
22 //config:	depends on BRCTL
23 //config:	help
24 //config:	Add support for extended option like:
25 //config:		setageing, setfd, sethello, setmaxage,
26 //config:		setpathcost, setportprio, setbridgeprio,
27 //config:		stp
28 //config:	This adds about 600 bytes.
29 //config:
30 //config:config FEATURE_BRCTL_SHOW
31 //config:	bool "Support show"
32 //config:	default y
33 //config:	depends on BRCTL && FEATURE_BRCTL_FANCY
34 //config:	help
35 //config:	Add support for option which prints the current config:
36 //config:		show
37 
38 //applet:IF_BRCTL(APPLET_NOEXEC(brctl, brctl, BB_DIR_USR_SBIN, BB_SUID_DROP, brctl))
39 
40 //kbuild:lib-$(CONFIG_BRCTL) += brctl.o
41 
42 //usage:#define brctl_trivial_usage
43 //usage:       "COMMAND [BRIDGE [ARGS]]"
44 //usage:#define brctl_full_usage "\n\n"
45 //usage:       "Manage ethernet bridges"
46 //usage:     "\nCommands:"
47 //usage:	IF_FEATURE_BRCTL_SHOW(
48 //usage:     "\n	show [BRIDGE]...	Show bridges"
49 //usage:	)
50 //usage:     "\n	addbr BRIDGE		Create BRIDGE"
51 //usage:     "\n	delbr BRIDGE		Delete BRIDGE"
52 //usage:     "\n	addif BRIDGE IFACE	Add IFACE to BRIDGE"
53 //usage:     "\n	delif BRIDGE IFACE	Delete IFACE from BRIDGE"
54 //usage:	IF_FEATURE_BRCTL_FANCY(
55 //usage:     "\n	showmacs BRIDGE			List MAC addresses"
56 //usage:     "\n	showstp	BRIDGE			Show STP info"
57 //usage:     "\n	stp BRIDGE 1/yes/on|0/no/off	Set STP on/off"
58 //usage:     "\n	setageing BRIDGE SECONDS	Set ageing time"
59 //usage:     "\n	setfd BRIDGE SECONDS		Set bridge forward delay"
60 //usage:     "\n	sethello BRIDGE SECONDS		Set hello time"
61 //usage:     "\n	setmaxage BRIDGE SECONDS	Set max message age"
62 //usage:     "\n	setbridgeprio BRIDGE PRIO	Set bridge priority"
63 //usage:     "\n	setportprio BRIDGE IFACE PRIO	Set port priority"
64 //usage:     "\n	setpathcost BRIDGE IFACE COST	Set path cost"
65 //usage:	)
66 // Not yet implemented:
67 //			hairpin BRIDGE IFACE on|off	Set hairpin on/off
68 
69 #include "libbb.h"
70 #include "common_bufsiz.h"
71 #include <linux/sockios.h>
72 #include <net/if.h>
73 
74 #ifndef SIOCBRADDBR
75 # define SIOCBRADDBR BRCTL_ADD_BRIDGE
76 #endif
77 #ifndef SIOCBRDELBR
78 # define SIOCBRDELBR BRCTL_DEL_BRIDGE
79 #endif
80 #ifndef SIOCBRADDIF
81 # define SIOCBRADDIF BRCTL_ADD_IF
82 #endif
83 #ifndef SIOCBRDELIF
84 # define SIOCBRDELIF BRCTL_DEL_IF
85 #endif
86 
87 #if ENABLE_FEATURE_BRCTL_FANCY
str_to_jiffies(const char * time_str)88 static unsigned str_to_jiffies(const char *time_str)
89 {
90 	double dd;
91 	char *endptr;
92 //TODO: needs setlocale(LC_NUMERIC, "C")?
93 	dd = /*bb_*/strtod(time_str, &endptr);
94 	if (endptr == time_str || dd < 0)
95 		bb_error_msg_and_die(bb_msg_invalid_arg_to, time_str, "timespec");
96 
97 	dd *= 100;
98 	/* For purposes of brctl,
99 	 * capping SECONDS by ~20 million seconds is quite enough:
100 	 */
101 	if (dd > INT_MAX)
102 		dd = INT_MAX;
103 
104 	return dd;
105 }
106 #endif
107 
108 #define filedata bb_common_bufsiz1
109 
110 #if ENABLE_FEATURE_BRCTL_SHOW || ENABLE_FEATURE_BRCTL_FANCY
read_file(const char * name)111 static int read_file(const char *name)
112 {
113 	int n = open_read_close(name, filedata, COMMON_BUFSIZE - 1);
114 	if (n < 0) {
115 		filedata[0] = '\0';
116 	} else {
117 		filedata[n] = '\0';
118 		if (n != 0 && filedata[n - 1] == '\n')
119 			filedata[--n] = '\0';
120 	}
121 	return n;
122 }
123 #endif
124 
125 #if ENABLE_FEATURE_BRCTL_SHOW
126 /* NB: we are in /sys/class/net
127  */
show_bridge(const char * name,int need_hdr)128 static int show_bridge(const char *name, int need_hdr)
129 {
130 /* Output:
131  *bridge name	bridge id		STP enabled	interfaces
132  *br0		8000.000000000000	no		eth0
133  */
134 	char pathbuf[IFNAMSIZ + sizeof("/bridge/bridge_id") + 8];
135 	int tabs;
136 	DIR *ifaces;
137 	struct dirent *ent;
138 	char *sfx;
139 
140 #if IFNAMSIZ == 16
141 	sfx = pathbuf + sprintf(pathbuf, "%.16s/bridge/", name);
142 #else
143 	sfx = pathbuf + sprintf(pathbuf, "%.*s/bridge/", (int)IFNAMSIZ, name);
144 #endif
145 	strcpy(sfx, "bridge_id");
146 	if (read_file(pathbuf) < 0)
147 		return -1; /* this iface is not a bridge */
148 
149 	if (need_hdr)
150 		puts("bridge name\tbridge id\t\tSTP enabled\tinterfaces");
151 	printf("%s\t\t%s\t", name, filedata);
152 
153 	strcpy(sfx, "stp_state");
154 	read_file(pathbuf);
155 	if (LONE_CHAR(filedata, '0'))
156 		strcpy(filedata, "no");
157 	else
158 	if (LONE_CHAR(filedata, '1'))
159 		strcpy(filedata, "yes");
160 	fputs_stdout(filedata);
161 
162 	/* sfx points past "BR/bridge/", turn it into "BR/brif": */
163 	sfx[-4] = 'f'; sfx[-3] = '\0';
164 	tabs = 0;
165 	ifaces = opendir(pathbuf);
166 	if (ifaces) {
167 		while ((ent = readdir(ifaces)) != NULL) {
168 			if (DOT_OR_DOTDOT(ent->d_name))
169 				continue; /* . or .. */
170 			if (tabs)
171 				printf("\t\t\t\t\t");
172 			else
173 				tabs = 1;
174 			printf("\t\t%s\n", ent->d_name);
175 		}
176 		closedir(ifaces);
177 	}
178 	if (!tabs)  /* bridge has no interfaces */
179 		bb_putchar('\n');
180 	return 0;
181 }
182 #endif
183 
184 #if ENABLE_FEATURE_BRCTL_FANCY
write_uint(const char * name,const char * leaf,unsigned val)185 static void write_uint(const char *name, const char *leaf, unsigned val)
186 {
187 	char pathbuf[IFNAMSIZ + sizeof("/bridge/bridge_id") + 32];
188 	int fd, n;
189 
190 #if IFNAMSIZ == 16
191 	sprintf(pathbuf, "%.16s/%s", name, leaf);
192 #else
193 	sprintf(pathbuf, "%.*s/%s", (int)IFNAMSIZ, name, leaf);
194 #endif
195 	fd = xopen(pathbuf, O_WRONLY);
196 	n = sprintf(filedata, "%u\n", val);
197 	if (write(fd, filedata, n) < 0)
198 		bb_simple_perror_msg_and_die(name);
199 	/* So far all callers exit very soon after calling us.
200 	 * Do not bother closing fd (unless debugging):
201 	 */
202 	if (ENABLE_FEATURE_CLEAN_UP)
203 		close(fd);
204 }
205 
206 struct fdb_entry {
207 	uint8_t mac_addr[6];
208 	uint8_t port_no;
209 	uint8_t is_local;
210 	uint32_t ageing_timer_value;
211 	uint8_t port_hi;
212 	uint8_t pad0;
213 	uint16_t unused;
214 };
215 
compare_fdbs(const void * _f0,const void * _f1)216 static int compare_fdbs(const void *_f0, const void *_f1)
217 {
218 	const struct fdb_entry *f0 = _f0;
219 	const struct fdb_entry *f1 = _f1;
220 
221 	return memcmp(f0->mac_addr, f1->mac_addr, 6);
222 }
223 
read_bridge_forward_db(const char * name,struct fdb_entry ** _fdb)224 static size_t read_bridge_forward_db(const char *name, struct fdb_entry **_fdb)
225 {
226 	char pathbuf[IFNAMSIZ + sizeof("/brforward") + 8];
227 	struct fdb_entry *fdb;
228 	size_t nentries;
229 	int fd;
230 	ssize_t cc;
231 
232 #if IFNAMSIZ == 16
233 	sprintf(pathbuf, "%.16s/brforward", name);
234 #else
235 	sprintf(pathbuf, "%.*s/brforward", (int)IFNAMSIZ, name);
236 #endif
237 	fd = open(pathbuf, O_RDONLY);
238 	if (fd < 0)
239 		bb_error_msg_and_die("bridge %s does not exist", name);
240 
241 	fdb = NULL;
242 	nentries = 0;
243 	for (;;) {
244 		fdb = xrealloc_vector(fdb, 4, nentries);
245 		cc = full_read(fd, &fdb[nentries], sizeof(*fdb));
246 		if (cc == 0) {
247 			break;
248 		}
249 		if (cc != sizeof(*fdb)) {
250 			bb_perror_msg_and_die("can't read bridge %s forward db", name);
251 		}
252 		++nentries;
253 	}
254 
255 	if (ENABLE_FEATURE_CLEAN_UP)
256 		close(fd);
257 
258 	qsort(fdb, nentries, sizeof(*fdb), compare_fdbs);
259 
260 	*_fdb = fdb;
261 	return nentries;
262 }
263 
show_bridge_macs(const char * name)264 static void show_bridge_macs(const char *name)
265 {
266 	struct fdb_entry *fdb;
267 	size_t nentries;
268 	size_t i;
269 
270 	nentries = read_bridge_forward_db(name, &fdb);
271 
272 	printf("port no\tmac addr\t\tis local?\tageing timer\n");
273 	for (i = 0; i < nentries; ++i) {
274 		const struct fdb_entry *f = &fdb[i];
275 		unsigned tv_sec = f->ageing_timer_value / 100;
276 		unsigned tv_csec = f->ageing_timer_value % 100;
277 		printf("%3u\t"
278 			"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\t"
279 			"%s\t\t"
280 			"%4u.%.2u\n",
281 			f->port_no,
282 			f->mac_addr[0], f->mac_addr[1], f->mac_addr[2],
283 			f->mac_addr[3], f->mac_addr[4], f->mac_addr[5],
284 			(f->is_local ? "yes" : "no"),
285 			tv_sec, tv_csec
286 		);
287 	}
288 
289 	if (ENABLE_FEATURE_CLEAN_UP)
290 		free(fdb);
291 }
292 
show_bridge_timer(const char * msg)293 static void show_bridge_timer(const char *msg)
294 {
295 	unsigned long long centisec = xstrtoull(filedata, 0);
296 	unsigned tv_sec = centisec / 100;
297 	unsigned tv_csec = centisec % 100;
298 	printf("%s%4u.%.2u", msg, tv_sec, tv_csec);
299 }
300 
show_bridge_state(unsigned state)301 static const char *show_bridge_state(unsigned state)
302 {
303 	/* See linux/if_bridge.h, BR_STATE_ constants */
304 	static const char state_names[] ALIGN1 =
305 		"disabled\0"	//BR_STATE_DISABLED   0
306 		"listening\0"   //BR_STATE_LISTENING  1
307 		"learning\0"    //BR_STATE_LEARNING   2
308 		"forwarding\0"  //BR_STATE_FORWARDING 3
309 		"blocking"      //BR_STATE_BLOCKING   4
310 	;
311 	if (state < 5)
312 		return nth_string(state_names, state);
313 	return utoa(state);
314 }
315 
printf_xstrtou(const char * fmt)316 static void printf_xstrtou(const char *fmt)
317 {
318 	printf(fmt, xstrtou(filedata, 0));
319 }
320 
show_bridge_port(const char * name)321 static NOINLINE void show_bridge_port(const char *name)
322 {
323 	char pathbuf[IFNAMSIZ + sizeof("/brport/forward_delay_timer") + 8];
324 	char *sfx;
325 
326 #if IFNAMSIZ == 16
327 	sfx = pathbuf + sprintf(pathbuf, "%.16s/brport/", name);
328 #else
329 	sfx = pathbuf + sprintf(pathbuf, "%.*s/brport/", (int)IFNAMSIZ, name);
330 #endif
331 
332 	strcpy(sfx, "port_no");
333 	read_file(pathbuf);
334 	printf("%s (%u)\n", name, xstrtou(filedata, 0));
335 
336 	strcpy(sfx + 5, "id"); // "port_id"
337 	read_file(pathbuf);
338 	printf_xstrtou(" port id\t\t%.4x");
339 
340 	strcpy(sfx, "state");
341 	read_file(pathbuf);
342 	printf("\t\t\tstate\t\t%15s\n", show_bridge_state(xstrtou(filedata, 0)));
343 
344 	strcpy(sfx, "designated_root");
345 	read_file(pathbuf);
346 	printf(" designated root\t%s", filedata);
347 
348 	strcpy(sfx, "path_cost");
349 	read_file(pathbuf);
350 	printf_xstrtou("\tpath cost\t\t%4u\n");
351 
352 	strcpy(sfx, "designated_bridge");
353 	read_file(pathbuf);
354 	printf(" designated bridge\t%s", filedata);
355 
356 	strcpy(sfx, "message_age_timer");
357 	read_file(pathbuf);
358 	show_bridge_timer("\tmessage age timer\t");
359 
360 	strcpy(sfx, "designated_port");
361 	read_file(pathbuf);
362 	printf_xstrtou("\n designated port\t%.4x");
363 
364 	strcpy(sfx, "forward_delay_timer");
365 	read_file(pathbuf);
366 	show_bridge_timer("\t\t\tforward delay timer\t");
367 
368 	strcpy(sfx, "designated_cost");
369 	read_file(pathbuf);
370 	printf_xstrtou("\n designated cost\t%4u");
371 
372 	strcpy(sfx, "hold_timer");
373 	read_file(pathbuf);
374 	show_bridge_timer("\t\t\thold timer\t\t");
375 
376 	printf("\n flags\t\t\t");
377 
378 	strcpy(sfx, "config_pending");
379 	read_file(pathbuf);
380 	if (!LONE_CHAR(filedata, '0'))
381 		printf("CONFIG_PENDING ");
382 
383 	strcpy(sfx, "change_ack");
384 	read_file(pathbuf);
385 	if (!LONE_CHAR(filedata, '0'))
386 		printf("TOPOLOGY_CHANGE_ACK ");
387 
388 	strcpy(sfx, "hairpin_mode");
389 	read_file(pathbuf);
390 	if (!LONE_CHAR(filedata, '0'))
391 		printf_xstrtou("\n hairpin mode\t\t%4u");
392 
393 	printf("\n\n");
394 }
395 
show_bridge_stp(const char * name)396 static void show_bridge_stp(const char *name)
397 {
398 	char pathbuf[IFNAMSIZ + sizeof("/bridge/topology_change_timer") + 8];
399 	char *sfx;
400 
401 #if IFNAMSIZ == 16
402 	sfx = pathbuf + sprintf(pathbuf, "%.16s/bridge/", name);
403 #else
404 	sfx = pathbuf + sprintf(pathbuf, "%.*s/bridge/", (int)IFNAMSIZ, name);
405 #endif
406 
407 	strcpy(sfx, "bridge_id");
408 	if (read_file(pathbuf) < 0)
409 		bb_error_msg_and_die("bridge %s does not exist", name);
410 
411 	printf("%s\n"
412 		" bridge id\t\t%s", name, filedata);
413 
414 	strcpy(sfx, "root_id");
415 	read_file(pathbuf);
416 	printf("\n designated root\t%s", filedata);
417 
418 	strcpy(sfx + 5, "port"); // "root_port"
419 	read_file(pathbuf);
420 	printf_xstrtou("\n root port\t\t%4u\t\t\t");
421 
422 	strcpy(sfx + 6, "ath_cost"); // "root_path_cost"
423 	read_file(pathbuf);
424 	printf_xstrtou("path cost\t\t%4u\n");
425 
426 	strcpy(sfx, "max_age");
427 	read_file(pathbuf);
428 	show_bridge_timer(" max age\t\t");
429 	show_bridge_timer("\t\t\tbridge max age\t\t");
430 
431 	strcpy(sfx, "hello_time");
432 	read_file(pathbuf);
433 	show_bridge_timer("\n hello time\t\t");
434 	show_bridge_timer("\t\t\tbridge hello time\t");
435 
436 	strcpy(sfx, "forward_delay");
437 	read_file(pathbuf);
438 	show_bridge_timer("\n forward delay\t\t");
439 	show_bridge_timer("\t\t\tbridge forward delay\t");
440 
441 	strcpy(sfx, "ageing_time");
442 	read_file(pathbuf);
443 	show_bridge_timer("\n ageing time\t\t");
444 
445 	strcpy(sfx, "hello_timer");
446 	read_file(pathbuf);
447 	show_bridge_timer("\n hello timer\t\t");
448 
449 	strcpy(sfx, "tcn_timer");
450 	read_file(pathbuf);
451 	show_bridge_timer("\t\t\ttcn timer\t\t");
452 
453 	strcpy(sfx, "topology_change_timer");
454 	read_file(pathbuf);
455 	show_bridge_timer("\n topology change timer\t");
456 
457 	strcpy(sfx, "gc_timer");
458 	read_file(pathbuf);
459 	show_bridge_timer("\t\t\tgc timer\t\t");
460 
461 	printf("\n flags\t\t\t");
462 
463 	strcpy(sfx, "topology_change");
464 	read_file(pathbuf);
465 	if (!LONE_CHAR(filedata, '0'))
466 		printf("TOPOLOGY_CHANGE ");
467 
468 	strcpy(sfx, "topology_change_detected");
469 	read_file(pathbuf);
470 	if (!LONE_CHAR(filedata, '0'))
471 		printf("TOPOLOGY_CHANGE_DETECTED ");
472 	printf("\n\n\n");
473 
474 	/* Show bridge ports */
475 	{
476 		DIR *ifaces;
477 
478 		/* sfx points past "BR/bridge/", turn it into "BR/brif": */
479 		sfx[-4] = 'f'; sfx[-3] = '\0';
480 		ifaces = opendir(pathbuf);
481 		if (ifaces) {
482 			struct dirent *ent;
483 			while ((ent = readdir(ifaces)) != NULL) {
484 				if (DOT_OR_DOTDOT(ent->d_name))
485 					continue; /* . or .. */
486 				show_bridge_port(ent->d_name);
487 			}
488 			if (ENABLE_FEATURE_CLEAN_UP)
489 				closedir(ifaces);
490 		}
491 	}
492 }
493 #endif
494 
495 int brctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
brctl_main(int argc UNUSED_PARAM,char ** argv)496 int brctl_main(int argc UNUSED_PARAM, char **argv)
497 {
498 	static const char keywords[] ALIGN1 =
499 		"addbr\0" "delbr\0" "addif\0" "delif\0"
500 	IF_FEATURE_BRCTL_FANCY(
501 		"stp\0"
502 		"showstp\0"
503 		"setageing\0" "setfd\0" "sethello\0" "setmaxage\0"
504 		"setpathcost\0" "setportprio\0"
505 		"setbridgeprio\0"
506 		"showmacs\0"
507 	)
508 	IF_FEATURE_BRCTL_SHOW("show\0");
509 	enum { ARG_addbr = 0, ARG_delbr, ARG_addif, ARG_delif
510 		IF_FEATURE_BRCTL_FANCY(,
511 			ARG_stp,
512 			ARG_showstp,
513 			ARG_setageing, ARG_setfd, ARG_sethello, ARG_setmaxage,
514 			ARG_setpathcost, ARG_setportprio,
515 			ARG_setbridgeprio,
516 			ARG_showmacs
517 		)
518 		IF_FEATURE_BRCTL_SHOW(, ARG_show)
519 	};
520 	int key;
521 	char *br;
522 
523 	argv++;
524 	if (!*argv) {
525 		/* bare "brctl" shows --help */
526 		bb_show_usage();
527 	}
528 
529 	xchdir("/sys/class/net");
530 
531 	key = index_in_strings(keywords, *argv);
532 	if (key == -1) /* no match found in keywords array, bail out. */
533 		bb_error_msg_and_die(bb_msg_invalid_arg_to, *argv, applet_name);
534 	argv++;
535 
536 #if ENABLE_FEATURE_BRCTL_SHOW
537 	if (key == ARG_show) { /* show [BR]... */
538 		DIR *net;
539 		struct dirent *ent;
540 		int need_hdr = 1;
541 		int exitcode = EXIT_SUCCESS;
542 
543 		if (*argv) {
544 			/* "show BR1 BR2 BR3" */
545 			do {
546 				if (show_bridge(*argv, need_hdr) >= 0) {
547 					need_hdr = 0;
548 				} else {
549 					bb_error_msg("bridge %s does not exist", *argv);
550 //TODO: if device exists, but is not a BR, brctl from bridge-utils 1.6
551 //says this instead: "device eth0 is not a bridge"
552 					exitcode = EXIT_FAILURE;
553 				}
554 			} while (*++argv != NULL);
555 			return exitcode;
556 		}
557 
558 		/* "show" (if no ifaces, shows nothing, not even header) */
559 		net = xopendir(".");
560 		while ((ent = readdir(net)) != NULL) {
561 			if (DOT_OR_DOTDOT(ent->d_name))
562 				continue; /* . or .. */
563 			if (show_bridge(ent->d_name, need_hdr) >= 0)
564 				need_hdr = 0;
565 		}
566 		if (ENABLE_FEATURE_CLEAN_UP)
567 			closedir(net);
568 		return exitcode;
569 	}
570 #endif
571 
572 	if (!*argv) /* All of the below need at least one argument */
573 		bb_show_usage();
574 
575 	br = *argv++;
576 
577 	if (key == ARG_addbr || key == ARG_delbr) {
578 		/* brctl from bridge-utils 1.6 still uses ioctl
579 		 * for SIOCBRADDBR / SIOCBRDELBR, not /sys accesses
580 		 */
581 		int fd = xsocket(AF_INET, SOCK_STREAM, 0);
582 		ioctl_or_perror_and_die(fd,
583 			key == ARG_addbr ? SIOCBRADDBR : SIOCBRDELBR,
584 			br, "bridge %s", br
585 		);
586 		//close(fd);
587 		//goto done;
588 		/* bridge-utils 1.6 simply ignores trailing args:
589 		 * "brctl addbr BR1 ARGS" ignores ARGS
590 		 */
591 		if (ENABLE_FEATURE_CLEAN_UP)
592 			close(fd);
593 		return EXIT_SUCCESS;
594 	}
595 
596 #if ENABLE_FEATURE_BRCTL_FANCY
597 	if (key == ARG_showmacs) {
598 		show_bridge_macs(br);
599 		return EXIT_SUCCESS;
600 	}
601 	if (key == ARG_showstp) {
602 		show_bridge_stp(br);
603 		return EXIT_SUCCESS;
604 	}
605 #endif
606 
607 	if (!*argv) /* All of the below need at least two arguments */
608 		bb_show_usage();
609 
610 #if ENABLE_FEATURE_BRCTL_FANCY
611 	if (key == ARG_stp) {
612 		static const char no_yes[] ALIGN1 =
613 			"0\0" "off\0" "n\0" "no\0"   /* 0 .. 3 */
614 			"1\0" "on\0"  "y\0" "yes\0"; /* 4 .. 7 */
615 		int onoff = index_in_strings(no_yes, *argv);
616 		if (onoff < 0)
617 			bb_error_msg_and_die(bb_msg_invalid_arg_to, *argv, applet_name);
618 		onoff = (unsigned)onoff / 4;
619 		write_uint(br, "bridge/stp_state", onoff);
620 		return EXIT_SUCCESS;
621 	}
622 
623 	if ((unsigned)(key - ARG_setageing) < 4) { /* time related ops */
624 		/* setageing BR N: "N*100\n" to /sys/class/net/BR/bridge/ageing_time
625 		 * setfd BR N:     "N*100\n" to /sys/class/net/BR/bridge/forward_delay
626 		 * sethello BR N:  "N*100\n" to /sys/class/net/BR/bridge/hello_time
627 		 * setmaxage BR N: "N*100\n" to /sys/class/net/BR/bridge/max_age
628 		 */
629 		write_uint(br,
630 			nth_string(
631 				"bridge/ageing_time"  "\0" /* ARG_setageing */
632 				"bridge/forward_delay""\0" /* ARG_setfd     */
633 				"bridge/hello_time"   "\0" /* ARG_sethello  */
634 				"bridge/max_age",          /* ARG_setmaxage */
635 				key - ARG_setageing
636 			),
637 			str_to_jiffies(*argv)
638 		);
639 		return EXIT_SUCCESS;
640 	}
641 
642 	if (key == ARG_setbridgeprio) {
643 		write_uint(br, "bridge/priority", xatoi_positive(*argv));
644 		return EXIT_SUCCESS;
645 	}
646 
647 	if (key == ARG_setpathcost
648 	 || key == ARG_setportprio
649 	) {
650 		if (!argv[1])
651 			bb_show_usage();
652 		/* BR is not used (and ignored!) for these commands:
653 		 * "setpathcost BR PORT N" writes "N\n" to
654 		 * /sys/class/net/PORT/brport/path_cost
655 		 * "setportprio BR PORT N" writes "N\n" to
656 		 * /sys/class/net/PORT/brport/priority
657 		 */
658 		write_uint(argv[0],
659 			nth_string(
660 				"brport/path_cost" "\0" /* ARG_setpathcost */
661 				"brport/priority",      /* ARG_setportprio */
662 				key - ARG_setpathcost
663 			),
664 			xatoi_positive(argv[1])
665 		);
666 		return EXIT_SUCCESS;
667 	}
668 #endif
669 	/* always true: if (key == ARG_addif || key == ARG_delif) */ {
670 		struct ifreq ifr;
671 		int fd = xsocket(AF_INET, SOCK_STREAM, 0);
672 
673 		strncpy_IFNAMSIZ(ifr.ifr_name, br);
674 		ifr.ifr_ifindex = if_nametoindex(*argv);
675 		if (ifr.ifr_ifindex == 0) {
676 			bb_perror_msg_and_die("iface %s", *argv);
677 		}
678 		ioctl_or_perror_and_die(fd,
679 			key == ARG_addif ? SIOCBRADDIF : SIOCBRDELIF,
680 			&ifr, "bridge %s", br
681 		);
682 		if (ENABLE_FEATURE_CLEAN_UP)
683 			close(fd);
684 	}
685 
686 	return EXIT_SUCCESS;
687 }
688