1 /* vi: set sw=4 ts=4: */
2 /* route
3  *
4  * Similar to the standard Unix route, but with only the necessary
5  * parts for AF_INET and AF_INET6
6  *
7  * Bjorn Wesen, Axis Communications AB
8  *
9  * Author of the original route:
10  *              Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
11  *              (derived from FvK's 'route.c     1.70    01/04/94')
12  *
13  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
14  *
15  *
16  * displayroute() code added by Vladimir N. Oleynik <dzo@simtreas.ru>
17  * adjustments by Larry Doolittle  <LRDoolittle@lbl.gov>
18  *
19  * IPV6 support added by Bart Visscher <magick@linux-fan.com>
20  */
21 
22 /* 2004/03/09  Manuel Novoa III <mjn3@codepoet.org>
23  *
24  * Rewritten to fix several bugs, add additional error checking, and
25  * remove ridiculous amounts of bloat.
26  */
27 //config:config ROUTE
28 //config:	bool "route"
29 //config:	default y
30 //config:	select PLATFORM_LINUX
31 //config:	help
32 //config:	  Route displays or manipulates the kernel's IP routing tables.
33 
34 //applet:IF_ROUTE(APPLET(route, BB_DIR_SBIN, BB_SUID_DROP))
35 
36 //kbuild:lib-$(CONFIG_ROUTE) += route.o
37 
38 //usage:#define route_trivial_usage
39 //usage:       "[{add|del|delete}]"
40 //usage:#define route_full_usage "\n\n"
41 //usage:       "Edit kernel routing tables\n"
42 //usage:     "\n	-n	Don't resolve names"
43 //usage:     "\n	-e	Display other/more information"
44 //usage:     "\n	-A inet" IF_FEATURE_IPV6("{6}") "	Select address family"
45 
46 #include <net/route.h>
47 #include <net/if.h>
48 
49 #include "libbb.h"
50 #include "inet_common.h"
51 
52 
53 #ifndef RTF_UP
54 /* Keep this in sync with /usr/src/linux/include/linux/route.h */
55 #define RTF_UP          0x0001	/* route usable                 */
56 #define RTF_GATEWAY     0x0002	/* destination is a gateway     */
57 #define RTF_HOST        0x0004	/* host entry (net otherwise)   */
58 #define RTF_REINSTATE   0x0008	/* reinstate route after tmout  */
59 #define RTF_DYNAMIC     0x0010	/* created dyn. (by redirect)   */
60 #define RTF_MODIFIED    0x0020	/* modified dyn. (by redirect)  */
61 #define RTF_MTU         0x0040	/* specific MTU for this route  */
62 #ifndef RTF_MSS
63 #define RTF_MSS         RTF_MTU	/* Compatibility :-(            */
64 #endif
65 #define RTF_WINDOW      0x0080	/* per route window clamping    */
66 #define RTF_IRTT        0x0100	/* Initial round trip time      */
67 #define RTF_REJECT      0x0200	/* Reject route                 */
68 #define RTF_NONEXTHOP   0x00200000 /* route with no nexthop	*/
69 #endif
70 
71 #if defined(SIOCADDRTOLD) || defined(RTF_IRTT)	/* route */
72 #define HAVE_NEW_ADDRT 1
73 #endif
74 
75 #if HAVE_NEW_ADDRT
76 #define mask_in_addr(x) (((struct sockaddr_in *)&((x).rt_genmask))->sin_addr.s_addr)
77 #define full_mask(x) (x)
78 #else
79 #define mask_in_addr(x) ((x).rt_genmask)
80 #define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr)
81 #endif
82 
83 /* The RTACTION entries must agree with tbl_verb[] below! */
84 #define RTACTION_ADD 1
85 #define RTACTION_DEL 2
86 
87 /* For the various tbl_*[] arrays, the 1st byte is the offset to
88  * the next entry and the 2nd byte is return value. */
89 
90 #define NET_FLAG  1
91 #define HOST_FLAG 2
92 
93 /* We remap '-' to '#' to avoid problems with getopt. */
94 static const char tbl_hash_net_host[] ALIGN1 =
95 	"\007\001#net\0"
96 /*	"\010\002#host\0" */
97 	"\007\002#host"				/* Since last, we can save a byte. */
98 ;
99 
100 #define KW_TAKES_ARG            020
101 #define KW_SETS_FLAG            040
102 
103 #define KW_IPVx_METRIC          020
104 #define KW_IPVx_NETMASK         021
105 #define KW_IPVx_GATEWAY         022
106 #define KW_IPVx_MSS             023
107 #define KW_IPVx_WINDOW          024
108 #define KW_IPVx_IRTT            025
109 #define KW_IPVx_DEVICE          026
110 
111 #define KW_IPVx_FLAG_ONLY       040
112 #define KW_IPVx_REJECT          040
113 #define KW_IPVx_MOD             041
114 #define KW_IPVx_DYN             042
115 #define KW_IPVx_REINSTATE       043
116 
117 static const char tbl_ipvx[] ALIGN1 =
118 	/* 020 is the "takes an arg" bit */
119 #if HAVE_NEW_ADDRT
120 	"\011\020metric\0"
121 #endif
122 	"\012\021netmask\0"
123 	"\005\022gw\0"
124 	"\012\022gateway\0"
125 	"\006\023mss\0"
126 	"\011\024window\0"
127 #ifdef RTF_IRTT
128 	"\007\025irtt\0"
129 #endif
130 	"\006\026dev\0"
131 	"\011\026device\0"
132 	/* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */
133 #ifdef RTF_REJECT
134 	"\011\040reject\0"
135 #endif
136 	"\006\041mod\0"
137 	"\006\042dyn\0"
138 /*	"\014\043reinstate\0" */
139 	"\013\043reinstate"			/* Since last, we can save a byte. */
140 ;
141 
142 static const uint16_t flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */
143 #ifdef RTF_REJECT
144 	RTF_REJECT,
145 #endif
146 	RTF_MODIFIED,
147 	RTF_DYNAMIC,
148 	RTF_REINSTATE
149 };
150 
kw_lookup(const char * kwtbl,char *** pargs)151 static int kw_lookup(const char *kwtbl, char ***pargs)
152 {
153 	if (**pargs) {
154 		do {
155 			if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */
156 				*pargs += 1;
157 				if (kwtbl[1] & KW_TAKES_ARG) {
158 					if (!**pargs) {	/* No more args! */
159 						bb_show_usage();
160 					}
161 					*pargs += 1; /* Calling routine will use args[-1]. */
162 				}
163 				return kwtbl[1];
164 			}
165 			kwtbl += *kwtbl;
166 		} while (*kwtbl);
167 	}
168 	return 0;
169 }
170 
171 /* Add or delete a route, depending on action. */
172 
INET_setroute(int action,char ** args)173 static NOINLINE void INET_setroute(int action, char **args)
174 {
175 	/* char buffer instead of bona-fide struct avoids aliasing warning */
176 	char rt_buf[sizeof(struct rtentry)];
177 	struct rtentry *const rt = (void *)rt_buf;
178 
179 	const char *netmask = NULL;
180 	int skfd, isnet, xflag;
181 
182 	/* Grab the -net or -host options.  Remember they were transformed. */
183 	xflag = kw_lookup(tbl_hash_net_host, &args);
184 
185 	/* If we did grab -net or -host, make sure we still have an arg left. */
186 	if (*args == NULL) {
187 		bb_show_usage();
188 	}
189 
190 	/* Clean out the RTREQ structure. */
191 	memset(rt, 0, sizeof(*rt));
192 
193 	{
194 		const char *target = *args++;
195 		char *prefix;
196 
197 		/* recognize x.x.x.x/mask format. */
198 		prefix = strchr(target, '/');
199 		if (prefix) {
200 			int prefix_len;
201 
202 			prefix_len = xatoul_range(prefix+1, 0, 32);
203 			mask_in_addr(*rt) = htonl( ~(0xffffffffUL >> prefix_len));
204 			*prefix = '\0';
205 #if HAVE_NEW_ADDRT
206 			rt->rt_genmask.sa_family = AF_INET;
207 #endif
208 		} else {
209 			/* Default netmask. */
210 			netmask = "default";
211 		}
212 		/* Prefer hostname lookup is -host flag (xflag==1) was given. */
213 		isnet = INET_resolve(target, (struct sockaddr_in *) &rt->rt_dst,
214 							 (xflag & HOST_FLAG));
215 		if (isnet < 0) {
216 			bb_error_msg_and_die("resolving %s", target);
217 		}
218 		if (prefix) {
219 			/* do not destroy prefix for process args */
220 			*prefix = '/';
221 		}
222 	}
223 
224 	if (xflag) {		/* Reinit isnet if -net or -host was specified. */
225 		isnet = (xflag & NET_FLAG);
226 	}
227 
228 	/* Fill in the other fields. */
229 	rt->rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST));
230 
231 	while (*args) {
232 		int k = kw_lookup(tbl_ipvx, &args);
233 		const char *args_m1 = args[-1];
234 
235 		if (k & KW_IPVx_FLAG_ONLY) {
236 			rt->rt_flags |= flags_ipvx[k & 3];
237 			continue;
238 		}
239 
240 #if HAVE_NEW_ADDRT
241 		if (k == KW_IPVx_METRIC) {
242 			rt->rt_metric = xatoul(args_m1) + 1;
243 			continue;
244 		}
245 #endif
246 
247 		if (k == KW_IPVx_NETMASK) {
248 			struct sockaddr mask;
249 
250 			if (mask_in_addr(*rt)) {
251 				bb_show_usage();
252 			}
253 
254 			netmask = args_m1;
255 			isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0);
256 			if (isnet < 0) {
257 				bb_error_msg_and_die("resolving %s", netmask);
258 			}
259 			rt->rt_genmask = full_mask(mask);
260 			continue;
261 		}
262 
263 		if (k == KW_IPVx_GATEWAY) {
264 			if (rt->rt_flags & RTF_GATEWAY) {
265 				bb_show_usage();
266 			}
267 
268 			isnet = INET_resolve(args_m1,
269 						(struct sockaddr_in *) &rt->rt_gateway, 1);
270 			rt->rt_flags |= RTF_GATEWAY;
271 
272 			if (isnet) {
273 				if (isnet < 0) {
274 					bb_error_msg_and_die("resolving %s", args_m1);
275 				}
276 				bb_error_msg_and_die("gateway %s is a NETWORK", args_m1);
277 			}
278 			continue;
279 		}
280 
281 		if (k == KW_IPVx_MSS) {	/* Check valid MSS bounds. */
282 			rt->rt_flags |= RTF_MSS;
283 			rt->rt_mss = xatoul_range(args_m1, 64, 32768);
284 			continue;
285 		}
286 
287 		if (k == KW_IPVx_WINDOW) {	/* Check valid window bounds. */
288 			rt->rt_flags |= RTF_WINDOW;
289 			rt->rt_window = xatoul_range(args_m1, 128, INT_MAX);
290 			continue;
291 		}
292 
293 #ifdef RTF_IRTT
294 		if (k == KW_IPVx_IRTT) {
295 			rt->rt_flags |= RTF_IRTT;
296 			rt->rt_irtt = xatoul(args_m1);
297 			rt->rt_irtt *= (bb_clk_tck() / 100);	/* FIXME */
298 #if 0					/* FIXME: do we need to check anything of this? */
299 			if (rt->rt_irtt < 1 || rt->rt_irtt > (120 * HZ)) {
300 				bb_error_msg_and_die("bad irtt");
301 			}
302 #endif
303 			continue;
304 		}
305 #endif
306 
307 		/* Device is special in that it can be the last arg specified
308 		 * and doesn't require the dev/device keyword in that case. */
309 		if (!rt->rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) {
310 			/* Don't use args_m1 here since args may have changed! */
311 			rt->rt_dev = args[-1];
312 			continue;
313 		}
314 
315 		/* Nothing matched. */
316 		bb_show_usage();
317 	}
318 
319 #ifdef RTF_REJECT
320 	if ((rt->rt_flags & RTF_REJECT) && !rt->rt_dev) {
321 		rt->rt_dev = (char*)"lo";
322 	}
323 #endif
324 
325 	/* sanity checks.. */
326 	if (mask_in_addr(*rt)) {
327 		uint32_t mask = mask_in_addr(*rt);
328 
329 		mask = ~ntohl(mask);
330 		if ((rt->rt_flags & RTF_HOST) && mask != 0xffffffff) {
331 			bb_error_msg_and_die("netmask %.8x and host route conflict",
332 								 (unsigned int) mask);
333 		}
334 		if (mask & (mask + 1)) {
335 			bb_error_msg_and_die("bogus netmask %s", netmask);
336 		}
337 		mask = ((struct sockaddr_in *) &rt->rt_dst)->sin_addr.s_addr;
338 		if (mask & ~(uint32_t)mask_in_addr(*rt)) {
339 			bb_error_msg_and_die("netmask and route address conflict");
340 		}
341 	}
342 
343 	/* Fill out netmask if still unset */
344 	if ((action == RTACTION_ADD) && (rt->rt_flags & RTF_HOST)) {
345 		mask_in_addr(*rt) = 0xffffffff;
346 	}
347 
348 	/* Create a socket to the INET kernel. */
349 	skfd = xsocket(AF_INET, SOCK_DGRAM, 0);
350 
351 	if (action == RTACTION_ADD)
352 		xioctl(skfd, SIOCADDRT, rt);
353 	else
354 		xioctl(skfd, SIOCDELRT, rt);
355 
356 	if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
357 }
358 
359 #if ENABLE_FEATURE_IPV6
360 
INET6_setroute(int action,char ** args)361 static NOINLINE void INET6_setroute(int action, char **args)
362 {
363 	struct sockaddr_in6 sa6;
364 	struct in6_rtmsg rt;
365 	int prefix_len, skfd;
366 	const char *devname;
367 
368 		/* We know args isn't NULL from the check in route_main. */
369 		const char *target = *args++;
370 
371 		if (strcmp(target, "default") == 0) {
372 			prefix_len = 0;
373 			memset(&sa6, 0, sizeof(sa6));
374 		} else {
375 			char *cp;
376 			cp = strchr(target, '/'); /* Yes... const to non is ok. */
377 			if (cp) {
378 				*cp = '\0';
379 				prefix_len = xatoul_range(cp + 1, 0, 128);
380 			} else {
381 				prefix_len = 128;
382 			}
383 			if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) {
384 				bb_error_msg_and_die("resolving %s", target);
385 			}
386 		}
387 
388 	/* Clean out the RTREQ structure. */
389 	memset(&rt, 0, sizeof(rt));
390 
391 	memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr));
392 
393 	/* Fill in the other fields. */
394 	rt.rtmsg_dst_len = prefix_len;
395 	rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP);
396 	rt.rtmsg_metric = 1;
397 
398 	devname = NULL;
399 
400 	while (*args) {
401 		int k = kw_lookup(tbl_ipvx, &args);
402 		const char *args_m1 = args[-1];
403 
404 		if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) {
405 			rt.rtmsg_flags |= flags_ipvx[k & 3];
406 			continue;
407 		}
408 
409 		if (k == KW_IPVx_METRIC) {
410 			rt.rtmsg_metric = xatoul(args_m1);
411 			continue;
412 		}
413 
414 		if (k == KW_IPVx_GATEWAY) {
415 			if (rt.rtmsg_flags & RTF_GATEWAY) {
416 				bb_show_usage();
417 			}
418 
419 			if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) {
420 				bb_error_msg_and_die("resolving %s", args_m1);
421 			}
422 			memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr,
423 					sizeof(struct in6_addr));
424 			rt.rtmsg_flags |= RTF_GATEWAY;
425 			continue;
426 		}
427 
428 		/* Device is special in that it can be the last arg specified
429 		 * and doesn't require the dev/device keyword in that case. */
430 		if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) {
431 			/* Don't use args_m1 here since args may have changed! */
432 			devname = args[-1];
433 			continue;
434 		}
435 
436 		/* Nothing matched. */
437 		bb_show_usage();
438 	}
439 
440 	/* Create a socket to the INET6 kernel. */
441 	skfd = xsocket(AF_INET6, SOCK_DGRAM, 0);
442 
443 	rt.rtmsg_ifindex = 0;
444 
445 	if (devname) {
446 		struct ifreq ifr;
447 		memset(&ifr, 0, sizeof(ifr));
448 		strncpy_IFNAMSIZ(ifr.ifr_name, devname);
449 		xioctl(skfd, SIOCGIFINDEX, &ifr);
450 		rt.rtmsg_ifindex = ifr.ifr_ifindex;
451 	}
452 
453 	/* Tell the kernel to accept this route. */
454 	if (action == RTACTION_ADD)
455 		xioctl(skfd, SIOCADDRT, &rt);
456 	else
457 		xioctl(skfd, SIOCDELRT, &rt);
458 
459 	if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
460 }
461 #endif
462 
463 static const
464 IF_NOT_FEATURE_IPV6(uint16_t)
465 IF_FEATURE_IPV6(unsigned)
466 flagvals[] = { /* Must agree with flagchars[]. */
467 	RTF_UP,
468 	RTF_GATEWAY,
469 	RTF_HOST,
470 	RTF_REINSTATE,
471 	RTF_DYNAMIC,
472 	RTF_MODIFIED,
473 #if ENABLE_FEATURE_IPV6
474 	RTF_DEFAULT,
475 	RTF_ADDRCONF,
476 	RTF_CACHE,
477 	RTF_REJECT,
478 	RTF_NONEXTHOP, /* this one doesn't fit into 16 bits */
479 #endif
480 };
481 /* Must agree with flagvals[]. */
482 static const char flagchars[] ALIGN1 =
483 	"UGHRDM"
484 #if ENABLE_FEATURE_IPV6
485 	"DAC!n"
486 #endif
487 ;
488 #define IPV4_MASK (RTF_UP|RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED)
489 #define IPV6_MASK (RTF_UP|RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE|RTF_REJECT|RTF_NONEXTHOP)
490 
set_flags(char * flagstr,int flags)491 static void set_flags(char *flagstr, int flags)
492 {
493 	int i;
494 
495 	for (i = 0; (*flagstr = flagchars[i]) != 0; i++) {
496 		if (flags & flagvals[i]) {
497 			++flagstr;
498 		}
499 	}
500 }
501 
502 /* also used in netstat */
bb_displayroutes(int noresolve,int netstatfmt)503 void FAST_FUNC bb_displayroutes(int noresolve, int netstatfmt)
504 {
505 	char devname[64], flags[16], *sdest, *sgw;
506 	unsigned long d, g, m;
507 	int r;
508 	int flgs, ref, use, metric, mtu, win, ir;
509 	struct sockaddr_in s_addr;
510 	struct in_addr mask;
511 
512 	FILE *fp = xfopen_for_read("/proc/net/route");
513 
514 	printf("Kernel IP routing table\n"
515 		"Destination     Gateway         Genmask         Flags %s Iface\n",
516 			netstatfmt ? "  MSS Window  irtt" : "Metric Ref    Use");
517 
518 	/* Skip the first line. */
519 	r = fscanf(fp, "%*[^\n]\n");
520 	if (r < 0) {
521 		/* Empty line, read error, or EOF. Yes, if routing table
522 		 * is completely empty, /proc/net/route has no header.
523 		 */
524 		goto ERROR;
525 	}
526 	while (1) {
527 		r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n",
528 				devname, &d, &g, &flgs, &ref, &use, &metric, &m,
529 				&mtu, &win, &ir);
530 		if (r != 11) {
531  ERROR:
532 			if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
533 				break;
534 			}
535 			bb_perror_msg_and_die(bb_msg_read_error);
536 		}
537 
538 		if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */
539 			continue;
540 		}
541 
542 		set_flags(flags, (flgs & IPV4_MASK));
543 #ifdef RTF_REJECT
544 		if (flgs & RTF_REJECT) {
545 			flags[0] = '!';
546 		}
547 #endif
548 
549 		memset(&s_addr, 0, sizeof(struct sockaddr_in));
550 		s_addr.sin_family = AF_INET;
551 		s_addr.sin_addr.s_addr = d;
552 		sdest = INET_rresolve(&s_addr, (noresolve | 0x8000), m); /* 'default' instead of '*' */
553 		s_addr.sin_addr.s_addr = g;
554 		sgw = INET_rresolve(&s_addr, (noresolve | 0x4000), m); /* Host instead of net */
555 		mask.s_addr = m;
556 		/* "%15.15s" truncates hostnames, do we really want that? */
557 		printf("%-15.15s %-15.15s %-16s%-6s", sdest, sgw, inet_ntoa(mask), flags);
558 		free(sdest);
559 		free(sgw);
560 		if (netstatfmt) {
561 			printf("%5d %-5d %6d %s\n", mtu, win, ir, devname);
562 		} else {
563 			printf("%-6d %-2d %7d %s\n", metric, ref, use, devname);
564 		}
565 	}
566 	fclose(fp);
567 }
568 
569 #if ENABLE_FEATURE_IPV6
570 
INET6_displayroutes(void)571 static void INET6_displayroutes(void)
572 {
573 	char addr6[128], *naddr6;
574 	/* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses.
575 	 * We read the non-delimited strings into the tail of the buffer
576 	 * using fscanf and then modify the buffer by shifting forward
577 	 * while inserting ':'s and the nul terminator for the first string.
578 	 * Hence the strings are at addr6x and addr6x+40.  This generates
579 	 * _much_ less code than the previous (upstream) approach. */
580 	char addr6x[80];
581 	char iface[16], flags[16];
582 	int iflags, metric, refcnt, use, prefix_len, slen;
583 	struct sockaddr_in6 snaddr6;
584 
585 	FILE *fp = xfopen_for_read("/proc/net/ipv6_route");
586 
587 	printf("Kernel IPv6 routing table\n%-44s%-40s"
588 			"Flags Metric Ref    Use Iface\n",
589 			"Destination", "Next Hop");
590 
591 	while (1) {
592 		int r;
593 		r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n",
594 				addr6x+14, &prefix_len, &slen, addr6x+40+7,
595 				&metric, &refcnt, &use, &iflags, iface);
596 		if (r != 9) {
597 			if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
598 				break;
599 			}
600  ERROR:
601 			bb_perror_msg_and_die(bb_msg_read_error);
602 		}
603 
604 		/* Do the addr6x shift-and-insert changes to ':'-delimit addresses.
605 		 * For now, always do this to validate the proc route format, even
606 		 * if the interface is down. */
607 		{
608 			int i = 0;
609 			char *p = addr6x+14;
610 
611 			do {
612 				if (!*p) {
613 					if (i == 40) { /* nul terminator for 1st address? */
614 						addr6x[39] = 0;	/* Fixup... need 0 instead of ':'. */
615 						++p;	/* Skip and continue. */
616 						continue;
617 					}
618 					goto ERROR;
619 				}
620 				addr6x[i++] = *p++;
621 				if (!((i+1) % 5)) {
622 					addr6x[i++] = ':';
623 				}
624 			} while (i < 40+28+7);
625 		}
626 
627 		set_flags(flags, (iflags & IPV6_MASK));
628 
629 		r = 0;
630 		while (1) {
631 			inet_pton(AF_INET6, addr6x + r,
632 					  (struct sockaddr *) &snaddr6.sin6_addr);
633 			snaddr6.sin6_family = AF_INET6;
634 			naddr6 = INET6_rresolve((struct sockaddr_in6 *) &snaddr6,
635 						0x0fff /* Apparently, upstream never resolves. */
636 						);
637 
638 			if (!r) {			/* 1st pass */
639 				snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len);
640 				r += 40;
641 				free(naddr6);
642 			} else {			/* 2nd pass */
643 				/* Print the info. */
644 				printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n",
645 						addr6, naddr6, flags, metric, refcnt, use, iface);
646 				free(naddr6);
647 				break;
648 			}
649 		}
650 	}
651 	fclose(fp);
652 }
653 
654 #endif
655 
656 #define ROUTE_OPT_A     0x01
657 #define ROUTE_OPT_n     0x02
658 #define ROUTE_OPT_e     0x04
659 #define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */
660 
661 /* 1st byte is offset to next entry offset.  2nd byte is return value. */
662 /* 2nd byte matches RTACTION_* code */
663 static const char tbl_verb[] ALIGN1 =
664 	"\006\001add\0"
665 	"\006\002del\0"
666 /*	"\011\002delete\0" */
667 	"\010\002delete"  /* Since it's last, we can save a byte. */
668 ;
669 
670 int route_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
route_main(int argc UNUSED_PARAM,char ** argv)671 int route_main(int argc UNUSED_PARAM, char **argv)
672 {
673 	unsigned opt;
674 	int what;
675 	char *family;
676 	char **p;
677 
678 	/* First, remap '-net' and '-host' to avoid getopt problems. */
679 	p = argv;
680 	while (*++p) {
681 		if (strcmp(*p, "-net") == 0 || strcmp(*p, "-host") == 0) {
682 			p[0][0] = '#';
683 		}
684 	}
685 
686 	opt = getopt32(argv, "A:ne", &family);
687 
688 	if ((opt & ROUTE_OPT_A) && strcmp(family, "inet") != 0) {
689 #if ENABLE_FEATURE_IPV6
690 		if (strcmp(family, "inet6") == 0) {
691 			opt |= ROUTE_OPT_INET6;	/* Set flag for ipv6. */
692 		} else
693 #endif
694 		bb_show_usage();
695 	}
696 
697 	argv += optind;
698 
699 	/* No more args means display the routing table. */
700 	if (!*argv) {
701 		int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0;
702 #if ENABLE_FEATURE_IPV6
703 		if (opt & ROUTE_OPT_INET6)
704 			INET6_displayroutes();
705 		else
706 #endif
707 			bb_displayroutes(noresolve, opt & ROUTE_OPT_e);
708 
709 		fflush_stdout_and_exit(EXIT_SUCCESS);
710 	}
711 
712 	/* Check verb.  At the moment, must be add, del, or delete. */
713 	what = kw_lookup(tbl_verb, &argv);
714 	if (!what || !*argv) {		/* Unknown verb or no more args. */
715 		bb_show_usage();
716 	}
717 
718 #if ENABLE_FEATURE_IPV6
719 	if (opt & ROUTE_OPT_INET6)
720 		INET6_setroute(what, argv);
721 	else
722 #endif
723 		INET_setroute(what, argv);
724 
725 	return EXIT_SUCCESS;
726 }
727