+/* vi: set sw=4 ts=4: */
/* route
*
* Similar to the standard Unix route, but with only the necessary
- * parts for AF_INET
+ * parts for AF_INET and AF_INET6
*
* Bjorn Wesen, Axis Communications AB
*
- * Author of the original route:
+ * Author of the original route:
* Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
* (derived from FvK's 'route.c 1.70 01/04/94')
*
- * 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.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
*
- * $Id: route.c,v 1.11 2001/08/23 22:05:33 andersen Exp $
*
* displayroute() code added by Vladimir N. Oleynik <dzo@simtreas.ru>
* adjustments by Larry Doolittle <LRDoolittle@lbl.gov>
+ *
+ * IPV6 support added by Bart Visscher <magick@linux-fan.com>
*/
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <net/route.h>
-#include <linux/param.h> // HZ
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <stdio.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <getopt.h>
-#include <unistd.h>
-#include <ctype.h>
-#include "busybox.h"
-
-#define _(x) x
-
-#define RTACTION_ADD 1
-#define RTACTION_DEL 2
-#define RTACTION_HELP 3
-#define RTACTION_FLUSH 4
-#define RTACTION_SHOW 5
-
-#define E_NOTFOUND 8
-#define E_SOCK 7
-#define E_LOOKUP 6
-#define E_VERSION 5
-#define E_USAGE 4
-#define E_OPTERR 3
-#define E_INTERN 2
-#define E_NOSUPP 1
-
-/* resolve XXX.YYY.ZZZ.QQQ -> binary */
-
-static int
-INET_resolve(char *name, struct sockaddr *sa)
-{
- struct sockaddr_in *s_in = (struct sockaddr_in *)sa;
-
- s_in->sin_family = AF_INET;
- s_in->sin_port = 0;
-
- /* Default is special, meaning 0.0.0.0. */
- if (strcmp(name, "default")==0) {
- s_in->sin_addr.s_addr = INADDR_ANY;
- return 1;
- }
- /* Look to see if it's a dotted quad. */
- if (inet_aton(name, &s_in->sin_addr)) {
- return 0;
- }
- /* guess not.. */
- return -1;
-}
+/* 2004/03/09 Manuel Novoa III <mjn3@codepoet.org>
+ *
+ * Rewritten to fix several bugs, add additional error checking, and
+ * remove ridiculous amounts of bloat.
+ */
-#if defined (SIOCADDRTOLD) || defined (RTF_IRTT) /* route */
-#define HAVE_NEW_ADDRT 1
+//usage:#define route_trivial_usage
+//usage: "[{add|del|delete}]"
+//usage:#define route_full_usage "\n\n"
+//usage: "Edit kernel routing tables\n"
+//usage: "\n -n Don't resolve names"
+//usage: "\n -e Display other/more information"
+//usage: "\n -A inet" IF_FEATURE_IPV6("{6}") " Select address family"
+
+#include <net/route.h>
+#include <net/if.h>
+
+#include "libbb.h"
+#include "inet_common.h"
+
+
+#ifndef RTF_UP
+/* Keep this in sync with /usr/src/linux/include/linux/route.h */
+#define RTF_UP 0x0001 /* route usable */
+#define RTF_GATEWAY 0x0002 /* destination is a gateway */
+#define RTF_HOST 0x0004 /* host entry (net otherwise) */
+#define RTF_REINSTATE 0x0008 /* reinstate route after tmout */
+#define RTF_DYNAMIC 0x0010 /* created dyn. (by redirect) */
+#define RTF_MODIFIED 0x0020 /* modified dyn. (by redirect) */
+#define RTF_MTU 0x0040 /* specific MTU for this route */
+#ifndef RTF_MSS
+#define RTF_MSS RTF_MTU /* Compatibility :-( */
#endif
-#ifdef RTF_IRTT /* route */
-#define HAVE_RTF_IRTT 1
+#define RTF_WINDOW 0x0080 /* per route window clamping */
+#define RTF_IRTT 0x0100 /* Initial round trip time */
+#define RTF_REJECT 0x0200 /* Reject route */
#endif
-#ifdef RTF_REJECT /* route */
-#define HAVE_RTF_REJECT 1
+
+#if defined(SIOCADDRTOLD) || defined(RTF_IRTT) /* route */
+#define HAVE_NEW_ADDRT 1
#endif
#if HAVE_NEW_ADDRT
#define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr)
#endif
-/* add or delete a route depending on action */
+/* The RTACTION entries must agree with tbl_verb[] below! */
+#define RTACTION_ADD 1
+#define RTACTION_DEL 2
+
+/* For the various tbl_*[] arrays, the 1st byte is the offset to
+ * the next entry and the 2nd byte is return value. */
+
+#define NET_FLAG 1
+#define HOST_FLAG 2
+
+/* We remap '-' to '#' to avoid problems with getopt. */
+static const char tbl_hash_net_host[] ALIGN1 =
+ "\007\001#net\0"
+/* "\010\002#host\0" */
+ "\007\002#host" /* Since last, we can save a byte. */
+;
+
+#define KW_TAKES_ARG 020
+#define KW_SETS_FLAG 040
+
+#define KW_IPVx_METRIC 020
+#define KW_IPVx_NETMASK 021
+#define KW_IPVx_GATEWAY 022
+#define KW_IPVx_MSS 023
+#define KW_IPVx_WINDOW 024
+#define KW_IPVx_IRTT 025
+#define KW_IPVx_DEVICE 026
+
+#define KW_IPVx_FLAG_ONLY 040
+#define KW_IPVx_REJECT 040
+#define KW_IPVx_MOD 041
+#define KW_IPVx_DYN 042
+#define KW_IPVx_REINSTATE 043
+
+static const char tbl_ipvx[] ALIGN1 =
+ /* 020 is the "takes an arg" bit */
+#if HAVE_NEW_ADDRT
+ "\011\020metric\0"
+#endif
+ "\012\021netmask\0"
+ "\005\022gw\0"
+ "\012\022gateway\0"
+ "\006\023mss\0"
+ "\011\024window\0"
+#ifdef RTF_IRTT
+ "\007\025irtt\0"
+#endif
+ "\006\026dev\0"
+ "\011\026device\0"
+ /* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */
+#ifdef RTF_REJECT
+ "\011\040reject\0"
+#endif
+ "\006\041mod\0"
+ "\006\042dyn\0"
+/* "\014\043reinstate\0" */
+ "\013\043reinstate" /* Since last, we can save a byte. */
+;
+
+static const int flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */
+#ifdef RTF_REJECT
+ RTF_REJECT,
+#endif
+ RTF_MODIFIED,
+ RTF_DYNAMIC,
+ RTF_REINSTATE
+};
+
+static int kw_lookup(const char *kwtbl, char ***pargs)
+{
+ if (**pargs) {
+ do {
+ if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */
+ *pargs += 1;
+ if (kwtbl[1] & KW_TAKES_ARG) {
+ if (!**pargs) { /* No more args! */
+ bb_show_usage();
+ }
+ *pargs += 1; /* Calling routine will use args[-1]. */
+ }
+ return kwtbl[1];
+ }
+ kwtbl += *kwtbl;
+ } while (*kwtbl);
+ }
+ return 0;
+}
+
+/* Add or delete a route, depending on action. */
-static int
-INET_setroute(int action, int options, char **args)
+static NOINLINE void INET_setroute(int action, char **args)
{
- struct rtentry rt;
- char target[128], gateway[128] = "NONE", netmask[128] = "default";
- int xflag, isnet;
- int skfd;
-
- xflag = 0;
-
- if (*args == NULL)
- show_usage();
- if (strcmp(*args, "-net")==0) {
- xflag = 1;
- args++;
- } else if (strcmp(*args, "-host")==0) {
- xflag = 2;
- args++;
+ /* char buffer instead of bona-fide struct avoids aliasing warning */
+ char rt_buf[sizeof(struct rtentry)];
+ struct rtentry *const rt = (void *)rt_buf;
+
+ const char *netmask = NULL;
+ int skfd, isnet, xflag;
+
+ /* Grab the -net or -host options. Remember they were transformed. */
+ xflag = kw_lookup(tbl_hash_net_host, &args);
+
+ /* If we did grab -net or -host, make sure we still have an arg left. */
+ if (*args == NULL) {
+ bb_show_usage();
}
- safe_strncpy(target, *args++, (sizeof target));
/* Clean out the RTREQ structure. */
- memset((char *) &rt, 0, sizeof(struct rtentry));
+ memset(rt, 0, sizeof(*rt));
+ {
+ const char *target = *args++;
+ char *prefix;
- if ((isnet = INET_resolve(target, &rt.rt_dst)) < 0) {
- error_msg(_("can't resolve %s"), target);
- return EXIT_FAILURE; /* XXX change to E_something */
+ /* recognize x.x.x.x/mask format. */
+ prefix = strchr(target, '/');
+ if (prefix) {
+ int prefix_len;
+
+ prefix_len = xatoul_range(prefix+1, 0, 32);
+ mask_in_addr(*rt) = htonl( ~(0xffffffffUL >> prefix_len));
+ *prefix = '\0';
+#if HAVE_NEW_ADDRT
+ rt->rt_genmask.sa_family = AF_INET;
+#endif
+ } else {
+ /* Default netmask. */
+ netmask = "default";
+ }
+ /* Prefer hostname lookup is -host flag (xflag==1) was given. */
+ isnet = INET_resolve(target, (struct sockaddr_in *) &rt->rt_dst,
+ (xflag & HOST_FLAG));
+ if (isnet < 0) {
+ bb_error_msg_and_die("resolving %s", target);
+ }
+ if (prefix) {
+ /* do not destroy prefix for process args */
+ *prefix = '/';
+ }
}
- switch (xflag) {
- case 1:
- isnet = 1;
- break;
-
- case 2:
- isnet = 0;
- break;
-
- default:
- break;
+ if (xflag) { /* Reinit isnet if -net or -host was specified. */
+ isnet = (xflag & NET_FLAG);
}
-
+
/* Fill in the other fields. */
- rt.rt_flags = (RTF_UP | RTF_HOST);
- if (isnet)
- rt.rt_flags &= ~RTF_HOST;
+ rt->rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST));
while (*args) {
- if (strcmp(*args, "metric")==0) {
- int metric;
-
- args++;
- if (!*args || !isdigit(**args))
- show_usage();
- metric = atoi(*args);
+ int k = kw_lookup(tbl_ipvx, &args);
+ const char *args_m1 = args[-1];
+
+ if (k & KW_IPVx_FLAG_ONLY) {
+ rt->rt_flags |= flags_ipvx[k & 3];
+ continue;
+ }
+
#if HAVE_NEW_ADDRT
- rt.rt_metric = metric + 1;
-#else
- ENOSUPP("inet_setroute", "NEW_ADDRT (metric)"); /* XXX Fixme */
-#endif
- args++;
+ if (k == KW_IPVx_METRIC) {
+ rt->rt_metric = xatoul(args_m1) + 1;
continue;
}
+#endif
- if (strcmp(*args, "netmask")==0) {
+ if (k == KW_IPVx_NETMASK) {
struct sockaddr mask;
-
- args++;
- if (!*args || mask_in_addr(rt))
- show_usage();
- safe_strncpy(netmask, *args, (sizeof netmask));
- if ((isnet = INET_resolve(netmask, &mask)) < 0) {
- error_msg(_("can't resolve netmask %s"), netmask);
- return E_LOOKUP;
+
+ if (mask_in_addr(*rt)) {
+ bb_show_usage();
+ }
+
+ netmask = args_m1;
+ isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0);
+ if (isnet < 0) {
+ bb_error_msg_and_die("resolving %s", netmask);
}
- rt.rt_genmask = full_mask(mask);
- args++;
+ rt->rt_genmask = full_mask(mask);
continue;
}
- if (strcmp(*args, "gw")==0 || strcmp(*args, "gateway")==0) {
- args++;
- if (!*args)
- show_usage();
- if (rt.rt_flags & RTF_GATEWAY)
- show_usage();
- safe_strncpy(gateway, *args, (sizeof gateway));
- if ((isnet = INET_resolve(gateway, &rt.rt_gateway)) < 0) {
- error_msg(_("can't resolve gw %s"), gateway);
- return E_LOOKUP;
+ if (k == KW_IPVx_GATEWAY) {
+ if (rt->rt_flags & RTF_GATEWAY) {
+ bb_show_usage();
}
+
+ isnet = INET_resolve(args_m1,
+ (struct sockaddr_in *) &rt->rt_gateway, 1);
+ rt->rt_flags |= RTF_GATEWAY;
+
if (isnet) {
- error_msg(
- _("%s: cannot use a NETWORK as gateway!"),
- gateway);
- return E_OPTERR;
+ if (isnet < 0) {
+ bb_error_msg_and_die("resolving %s", args_m1);
+ }
+ bb_error_msg_and_die("gateway %s is a NETWORK", args_m1);
}
- rt.rt_flags |= RTF_GATEWAY;
- args++;
continue;
}
- if (strcmp(*args, "mss")==0) {
- args++;
- rt.rt_flags |= RTF_MSS;
- if (!*args)
- show_usage();
- rt.rt_mss = atoi(*args);
- args++;
- if (rt.rt_mss < 64 || rt.rt_mss > 32768) {
- error_msg(_("Invalid MSS."));
- return E_OPTERR;
- }
+ if (k == KW_IPVx_MSS) { /* Check valid MSS bounds. */
+ rt->rt_flags |= RTF_MSS;
+ rt->rt_mss = xatoul_range(args_m1, 64, 32768);
continue;
}
- if (strcmp(*args, "window")==0) {
- args++;
- if (!*args)
- show_usage();
- rt.rt_flags |= RTF_WINDOW;
- rt.rt_window = atoi(*args);
- args++;
- if (rt.rt_window < 128) {
- error_msg(_("Invalid window."));
- return E_OPTERR;
- }
+ if (k == KW_IPVx_WINDOW) { /* Check valid window bounds. */
+ rt->rt_flags |= RTF_WINDOW;
+ rt->rt_window = xatoul_range(args_m1, 128, INT_MAX);
continue;
}
- if (strcmp(*args, "irtt")==0) {
- args++;
- if (!*args)
- show_usage();
- args++;
-#if HAVE_RTF_IRTT
- rt.rt_flags |= RTF_IRTT;
- rt.rt_irtt = atoi(*(args - 1));
- rt.rt_irtt *= (HZ / 100); /* FIXME */
-#if 0 /* FIXME: do we need to check anything of this? */
- if (rt.rt_irtt < 1 || rt.rt_irtt > (120 * HZ)) {
- error_msg(_("Invalid initial rtt."));
- return E_OPTERR;
+#ifdef RTF_IRTT
+ if (k == KW_IPVx_IRTT) {
+ rt->rt_flags |= RTF_IRTT;
+ rt->rt_irtt = xatoul(args_m1);
+ rt->rt_irtt *= (sysconf(_SC_CLK_TCK) / 100); /* FIXME */
+#if 0 /* FIXME: do we need to check anything of this? */
+ if (rt->rt_irtt < 1 || rt->rt_irtt > (120 * HZ)) {
+ bb_error_msg_and_die("bad irtt");
}
-#endif
-#else
- ENOSUPP("inet_setroute", "RTF_IRTT"); /* XXX Fixme */
#endif
continue;
}
-
- if (strcmp(*args, "reject")==0) {
- args++;
-#if HAVE_RTF_REJECT
- rt.rt_flags |= RTF_REJECT;
-#else
- ENOSUPP("inet_setroute", "RTF_REJECT"); /* XXX Fixme */
#endif
+
+ /* Device is special in that it can be the last arg specified
+ * and doesn't requre the dev/device keyword in that case. */
+ if (!rt->rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) {
+ /* Don't use args_m1 here since args may have changed! */
+ rt->rt_dev = args[-1];
continue;
}
- if (strcmp(*args, "mod")==0) {
- args++;
- rt.rt_flags |= RTF_MODIFIED;
- continue;
- }
- if (strcmp(*args, "dyn")==0) {
- args++;
- rt.rt_flags |= RTF_DYNAMIC;
- continue;
- }
- if (strcmp(*args, "reinstate")==0) {
- args++;
- rt.rt_flags |= RTF_REINSTATE;
- continue;
- }
- if (strcmp(*args, "device")==0 || strcmp(*args, "dev")==0) {
- args++;
- if (rt.rt_dev || *args == NULL)
- show_usage();
- rt.rt_dev = *args++;
- continue;
- }
- /* nothing matches */
- if (!rt.rt_dev) {
- rt.rt_dev = *args++;
- if (*args)
- show_usage(); /* must be last to catch typos */
- } else {
- show_usage();
- }
+
+ /* Nothing matched. */
+ bb_show_usage();
}
-#if HAVE_RTF_REJECT
- if ((rt.rt_flags & RTF_REJECT) && !rt.rt_dev)
- rt.rt_dev = "lo";
+#ifdef RTF_REJECT
+ if ((rt->rt_flags & RTF_REJECT) && !rt->rt_dev) {
+ rt->rt_dev = (char*)"lo";
+ }
#endif
/* sanity checks.. */
- if (mask_in_addr(rt)) {
- unsigned long mask = mask_in_addr(rt);
+ if (mask_in_addr(*rt)) {
+ uint32_t mask = mask_in_addr(*rt);
+
mask = ~ntohl(mask);
- if ((rt.rt_flags & RTF_HOST) && mask != 0xffffffff) {
- error_msg(
- _("netmask %.8x doesn't make sense with host route"),
- (unsigned int)mask);
- return E_OPTERR;
+ if ((rt->rt_flags & RTF_HOST) && mask != 0xffffffff) {
+ bb_error_msg_and_die("netmask %.8x and host route conflict",
+ (unsigned int) mask);
}
if (mask & (mask + 1)) {
- error_msg(_("bogus netmask %s"), netmask);
- return E_OPTERR;
+ bb_error_msg_and_die("bogus netmask %s", netmask);
}
- mask = ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr;
- if (mask & ~mask_in_addr(rt)) {
- error_msg(_("netmask doesn't match route address"));
- return E_OPTERR;
+ mask = ((struct sockaddr_in *) &rt->rt_dst)->sin_addr.s_addr;
+ if (mask & ~(uint32_t)mask_in_addr(*rt)) {
+ bb_error_msg_and_die("netmask and route address conflict");
}
}
+
/* Fill out netmask if still unset */
- if ((action == RTACTION_ADD) && rt.rt_flags & RTF_HOST)
- mask_in_addr(rt) = 0xffffffff;
+ if ((action == RTACTION_ADD) && (rt->rt_flags & RTF_HOST)) {
+ mask_in_addr(*rt) = 0xffffffff;
+ }
/* Create a socket to the INET kernel. */
- if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
- perror("socket");
- return E_SOCK;
+ skfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ if (action == RTACTION_ADD)
+ xioctl(skfd, SIOCADDRT, rt);
+ else
+ xioctl(skfd, SIOCDELRT, rt);
+
+ if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
+}
+
+#if ENABLE_FEATURE_IPV6
+
+static NOINLINE void INET6_setroute(int action, char **args)
+{
+ struct sockaddr_in6 sa6;
+ struct in6_rtmsg rt;
+ int prefix_len, skfd;
+ const char *devname;
+
+ /* We know args isn't NULL from the check in route_main. */
+ const char *target = *args++;
+
+ if (strcmp(target, "default") == 0) {
+ prefix_len = 0;
+ memset(&sa6, 0, sizeof(sa6));
+ } else {
+ char *cp;
+ cp = strchr(target, '/'); /* Yes... const to non is ok. */
+ if (cp) {
+ *cp = '\0';
+ prefix_len = xatoul_range(cp + 1, 0, 128);
+ } else {
+ prefix_len = 128;
+ }
+ if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) {
+ bb_error_msg_and_die("resolving %s", target);
+ }
+ }
+
+ /* Clean out the RTREQ structure. */
+ memset(&rt, 0, sizeof(rt));
+
+ memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr));
+
+ /* Fill in the other fields. */
+ rt.rtmsg_dst_len = prefix_len;
+ rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP);
+ rt.rtmsg_metric = 1;
+
+ devname = NULL;
+
+ while (*args) {
+ int k = kw_lookup(tbl_ipvx, &args);
+ const char *args_m1 = args[-1];
+
+ if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) {
+ rt.rtmsg_flags |= flags_ipvx[k & 3];
+ continue;
+ }
+
+ if (k == KW_IPVx_METRIC) {
+ rt.rtmsg_metric = xatoul(args_m1);
+ continue;
+ }
+
+ if (k == KW_IPVx_GATEWAY) {
+ if (rt.rtmsg_flags & RTF_GATEWAY) {
+ bb_show_usage();
+ }
+
+ if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) {
+ bb_error_msg_and_die("resolving %s", args_m1);
+ }
+ memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ rt.rtmsg_flags |= RTF_GATEWAY;
+ continue;
+ }
+
+ /* Device is special in that it can be the last arg specified
+ * and doesn't requre the dev/device keyword in that case. */
+ if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) {
+ /* Don't use args_m1 here since args may have changed! */
+ devname = args[-1];
+ continue;
+ }
+
+ /* Nothing matched. */
+ bb_show_usage();
+ }
+
+ /* Create a socket to the INET6 kernel. */
+ skfd = xsocket(AF_INET6, SOCK_DGRAM, 0);
+
+ rt.rtmsg_ifindex = 0;
+
+ if (devname) {
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy_IFNAMSIZ(ifr.ifr_name, devname);
+ xioctl(skfd, SIOCGIFINDEX, &ifr);
+ rt.rtmsg_ifindex = ifr.ifr_ifindex;
}
+
/* Tell the kernel to accept this route. */
- if (action == RTACTION_DEL) {
- if (ioctl(skfd, SIOCDELRT, &rt) < 0) {
- perror("SIOCDELRT");
- close(skfd);
- return E_SOCK;
- }
- } else {
- if (ioctl(skfd, SIOCADDRT, &rt) < 0) {
- perror("SIOCADDRT");
- close(skfd);
- return E_SOCK;
+ if (action == RTACTION_ADD)
+ xioctl(skfd, SIOCADDRT, &rt);
+ else
+ xioctl(skfd, SIOCDELRT, &rt);
+
+ if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
+}
+#endif
+
+static const unsigned flagvals[] = { /* Must agree with flagchars[]. */
+ RTF_GATEWAY,
+ RTF_HOST,
+ RTF_REINSTATE,
+ RTF_DYNAMIC,
+ RTF_MODIFIED,
+#if ENABLE_FEATURE_IPV6
+ RTF_DEFAULT,
+ RTF_ADDRCONF,
+ RTF_CACHE
+#endif
+};
+
+#define IPV4_MASK (RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED)
+#define IPV6_MASK (RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE)
+
+/* Must agree with flagvals[]. */
+static const char flagchars[] ALIGN1 =
+ "GHRDM"
+#if ENABLE_FEATURE_IPV6
+ "DAC"
+#endif
+;
+
+static void set_flags(char *flagstr, int flags)
+{
+ int i;
+
+ *flagstr++ = 'U';
+
+ for (i = 0; (*flagstr = flagchars[i]) != 0; i++) {
+ if (flags & flagvals[i]) {
+ ++flagstr;
}
}
-
- /* Close the socket. */
- (void) close(skfd);
- return EXIT_SUCCESS;
}
-static void displayroutes(void)
+/* also used in netstat */
+void FAST_FUNC bb_displayroutes(int noresolve, int netstatfmt)
{
- char buff[256];
- int nl = 0 ;
- struct in_addr dest;
- struct in_addr gw;
+ char devname[64], flags[16], *sdest, *sgw;
+ unsigned long d, g, m;
+ int flgs, ref, use, metric, mtu, win, ir;
+ struct sockaddr_in s_addr;
struct in_addr mask;
- int flgs, ref, use, metric;
- char flags[4];
- unsigned long int d,g,m;
-
- char sdest[16], sgw[16];
+ FILE *fp = xfopen_for_read("/proc/net/route");
- FILE *fp = xfopen("/proc/net/route", "r");
+ printf("Kernel IP routing table\n"
+ "Destination Gateway Genmask Flags %s Iface\n",
+ netstatfmt ? " MSS Window irtt" : "Metric Ref Use");
- while( fgets(buff, sizeof(buff), fp) != NULL ) {
- if(nl) {
- int ifl = 0;
- while(buff[ifl]!=' ' && buff[ifl]!='\t' && buff[ifl]!='\0')
- ifl++;
- buff[ifl]=0; /* interface */
- if(sscanf(buff+ifl+1, "%lx%lx%d%d%d%d%lx",
- &d, &g, &flgs, &ref, &use, &metric, &m)!=7) {
- error_msg_and_die( "Unsuported kernel route format\n");
+ if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */
+ goto ERROR; /* Empty or missing line, or read error. */
+ }
+ while (1) {
+ int r;
+ r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n",
+ devname, &d, &g, &flgs, &ref, &use, &metric, &m,
+ &mtu, &win, &ir);
+ if (r != 11) {
+ if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
+ break;
}
- if(nl==1) {
- printf("Kernel IP routing table\n"
-"Destination Gateway Genmask Flags Metric Ref Use Iface\n");
- } else {
- nl++;
- continue;
+ ERROR:
+ bb_error_msg_and_die("fscanf");
+ }
+
+ if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */
+ continue;
+ }
+
+ set_flags(flags, (flgs & IPV4_MASK));
+#ifdef RTF_REJECT
+ if (flgs & RTF_REJECT) {
+ flags[0] = '!';
+ }
+#endif
+
+ memset(&s_addr, 0, sizeof(struct sockaddr_in));
+ s_addr.sin_family = AF_INET;
+ s_addr.sin_addr.s_addr = d;
+ sdest = INET_rresolve(&s_addr, (noresolve | 0x8000), m); /* 'default' instead of '*' */
+ s_addr.sin_addr.s_addr = g;
+ sgw = INET_rresolve(&s_addr, (noresolve | 0x4000), m); /* Host instead of net */
+ mask.s_addr = m;
+ /* "%15.15s" truncates hostnames, do we really want that? */
+ printf("%-15.15s %-15.15s %-16s%-6s", sdest, sgw, inet_ntoa(mask), flags);
+ free(sdest);
+ free(sgw);
+ if (netstatfmt) {
+ printf("%5d %-5d %6d %s\n", mtu, win, ir, devname);
+ } else {
+ printf("%-6d %-2d %7d %s\n", metric, ref, use, devname);
+ }
+ }
+ fclose(fp);
+}
+
+#if ENABLE_FEATURE_IPV6
+
+static void INET6_displayroutes(void)
+{
+ char addr6[128], *naddr6;
+ /* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses.
+ * We read the non-delimited strings into the tail of the buffer
+ * using fscanf and then modify the buffer by shifting forward
+ * while inserting ':'s and the nul terminator for the first string.
+ * Hence the strings are at addr6x and addr6x+40. This generates
+ * _much_ less code than the previous (upstream) approach. */
+ char addr6x[80];
+ char iface[16], flags[16];
+ int iflags, metric, refcnt, use, prefix_len, slen;
+ struct sockaddr_in6 snaddr6;
+
+ FILE *fp = xfopen_for_read("/proc/net/ipv6_route");
+
+ printf("Kernel IPv6 routing table\n%-44s%-40s"
+ "Flags Metric Ref Use Iface\n",
+ "Destination", "Next Hop");
+
+ while (1) {
+ int r;
+ r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n",
+ addr6x+14, &prefix_len, &slen, addr6x+40+7,
+ &metric, &use, &refcnt, &iflags, iface);
+ if (r != 9) {
+ if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
+ break;
}
+ ERROR:
+ bb_error_msg_and_die("fscanf");
+ }
- ifl = 0; /* parse flags */
- if(flgs&1)
- flags[ifl++]='U';
- if(flgs&2)
- flags[ifl++]='G';
- if(flgs&4)
- flags[ifl++]='H';
- flags[ifl]=0;
- dest.s_addr = d;
- gw.s_addr = g;
- mask.s_addr = m;
- strcpy(sdest, (dest.s_addr==0 ? "default" :
- inet_ntoa(dest)));
- strcpy(sgw, (gw.s_addr==0 ? "*" :
- inet_ntoa(gw)));
- printf("%-16s%-16s%-16s%-6s%-6d %-2d %7d %s\n",
- sdest, sgw,
- inet_ntoa(mask),
- flags, metric, ref, use, buff);
+ /* Do the addr6x shift-and-insert changes to ':'-delimit addresses.
+ * For now, always do this to validate the proc route format, even
+ * if the interface is down. */
+ {
+ int i = 0;
+ char *p = addr6x+14;
+
+ do {
+ if (!*p) {
+ if (i == 40) { /* nul terminator for 1st address? */
+ addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */
+ ++p; /* Skip and continue. */
+ continue;
+ }
+ goto ERROR;
+ }
+ addr6x[i++] = *p++;
+ if (!((i+1) % 5)) {
+ addr6x[i++] = ':';
+ }
+ } while (i < 40+28+7);
+ }
+
+ if (!(iflags & RTF_UP)) { /* Skip interfaces that are down. */
+ continue;
+ }
+
+ set_flags(flags, (iflags & IPV6_MASK));
+
+ r = 0;
+ while (1) {
+ inet_pton(AF_INET6, addr6x + r,
+ (struct sockaddr *) &snaddr6.sin6_addr);
+ snaddr6.sin6_family = AF_INET6;
+ naddr6 = INET6_rresolve((struct sockaddr_in6 *) &snaddr6,
+ 0x0fff /* Apparently, upstream never resolves. */
+ );
+
+ if (!r) { /* 1st pass */
+ snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len);
+ r += 40;
+ free(naddr6);
+ } else { /* 2nd pass */
+ /* Print the info. */
+ printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n",
+ addr6, naddr6, flags, metric, refcnt, use, iface);
+ free(naddr6);
+ break;
+ }
}
- nl++;
}
+ fclose(fp);
}
-int route_main(int argc, char **argv)
+#endif
+
+#define ROUTE_OPT_A 0x01
+#define ROUTE_OPT_n 0x02
+#define ROUTE_OPT_e 0x04
+#define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */
+
+/* 1st byte is offset to next entry offset. 2nd byte is return value. */
+/* 2nd byte matches RTACTION_* code */
+static const char tbl_verb[] ALIGN1 =
+ "\006\001add\0"
+ "\006\002del\0"
+/* "\011\002delete\0" */
+ "\010\002delete" /* Since it's last, we can save a byte. */
+;
+
+int route_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int route_main(int argc UNUSED_PARAM, char **argv)
{
- int what = 0;
-
- argc--;
- argv++;
-
- if (*argv == NULL) {
- displayroutes();
- return EXIT_SUCCESS;
- } else {
- /* check verb */
- if (strcmp(*argv, "add")==0)
- what = RTACTION_ADD;
- else if (strcmp(*argv, "del")==0 || strcmp(*argv, "delete")==0)
- what = RTACTION_DEL;
- else if (strcmp(*argv, "flush")==0)
- what = RTACTION_FLUSH;
+ unsigned opt;
+ int what;
+ char *family;
+ char **p;
+
+ /* First, remap '-net' and '-host' to avoid getopt problems. */
+ p = argv;
+ while (*++p) {
+ if (strcmp(*p, "-net") == 0 || strcmp(*p, "-host") == 0) {
+ p[0][0] = '#';
+ }
+ }
+
+ opt = getopt32(argv, "A:ne", &family);
+
+ if ((opt & ROUTE_OPT_A) && strcmp(family, "inet") != 0) {
+#if ENABLE_FEATURE_IPV6
+ if (strcmp(family, "inet6") == 0) {
+ opt |= ROUTE_OPT_INET6; /* Set flag for ipv6. */
+ } else
+#endif
+ bb_show_usage();
+ }
+
+ argv += optind;
+
+ /* No more args means display the routing table. */
+ if (!*argv) {
+ int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0;
+#if ENABLE_FEATURE_IPV6
+ if (opt & ROUTE_OPT_INET6)
+ INET6_displayroutes();
else
- show_usage();
+#endif
+ bb_displayroutes(noresolve, opt & ROUTE_OPT_e);
+
+ fflush_stdout_and_exit(EXIT_SUCCESS);
}
- return INET_setroute(what, 0, ++argv);
+ /* Check verb. At the moment, must be add, del, or delete. */
+ what = kw_lookup(tbl_verb, &argv);
+ if (!what || !*argv) { /* Unknown verb or no more args. */
+ bb_show_usage();
+ }
+
+#if ENABLE_FEATURE_IPV6
+ if (opt & ROUTE_OPT_INET6)
+ INET6_setroute(what, argv);
+ else
+#endif
+ INET_setroute(what, argv);
+
+ return EXIT_SUCCESS;
}