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