route.c revision 45916cd2fec6e79bca5dee0421bd39e3c2910d1e
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/* Copyright (c) 1990 Mentat Inc. */
/*
*
* Copyright (c) 1983, 1989, 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)route.c 8.6 (Berkeley) 4/28/95
* @(#)linkaddr.c 8.1 (Berkeley) 6/4/93
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/param.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/sysmacros.h>
#include <sys/tihdr.h>
#include <sys/types.h>
#include <sys/ccompile.h>
#include <net/if.h>
#include <net/route.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <inet/mib2.h>
#include <inet/ip.h>
#include <limits.h>
#include <locale.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stropts.h>
#include <fcntl.h>
#include <stdarg.h>
#include <assert.h>
#include <strings.h>
#include <libtsnet.h>
#include <tsol/label.h>
static struct keytab {
char *kt_cp;
int kt_i;
} keywords[] = {
#define K_ADD 1
{"add", K_ADD},
#define K_BLACKHOLE 2
{"blackhole", K_BLACKHOLE},
#define K_CHANGE 3
{"change", K_CHANGE},
#define K_CLONING 4
{"cloning", K_CLONING},
#define K_DELETE 5
{"delete", K_DELETE},
#define K_DST 6
{"dst", K_DST},
#define K_EXPIRE 7
{"expire", K_EXPIRE},
#define K_FLUSH 8
{"flush", K_FLUSH},
#define K_GATEWAY 9
{"gateway", K_GATEWAY},
#define K_GET 11
{"get", K_GET},
#define K_HOPCOUNT 12
{"hopcount", K_HOPCOUNT},
#define K_HOST 13
{"host", K_HOST},
#define K_IFA 14
{"ifa", K_IFA},
#define K_IFACE 15
{"iface", K_IFACE},
#define K_IFP 16
{"ifp", K_IFP},
#define K_INET 17
{"inet", K_INET},
#define K_INET6 18
{"inet6", K_INET6},
#define K_INTERFACE 19
{"interface", K_INTERFACE},
#define K_LINK 20
{"link", K_LINK},
#define K_LOCK 21
{"lock", K_LOCK},
#define K_LOCKREST 22
{"lockrest", K_LOCKREST},
#define K_MASK 23
{"mask", K_MASK},
#define K_MONITOR 24
{"monitor", K_MONITOR},
#define K_MTU 25
{"mtu", K_MTU},
#define K_NET 26
{"net", K_NET},
#define K_NETMASK 27
{"netmask", K_NETMASK},
#define K_NOSTATIC 28
{"nostatic", K_NOSTATIC},
#define K_PRIVATE 29
{"private", K_PRIVATE},
#define K_PROTO1 30
{"proto1", K_PROTO1},
#define K_PROTO2 31
{"proto2", K_PROTO2},
#define K_RECVPIPE 32
{"recvpipe", K_RECVPIPE},
#define K_REJECT 33
{"reject", K_REJECT},
#define K_RTT 34
{"rtt", K_RTT},
#define K_RTTVAR 35
{"rttvar", K_RTTVAR},
#define K_SA 36
{"sa", K_SA},
#define K_SENDPIPE 37
{"sendpipe", K_SENDPIPE},
#define K_SSTHRESH 38
{"ssthresh", K_SSTHRESH},
#define K_STATIC 39
{"static", K_STATIC},
#define K_XRESOLVE 40
{"xresolve", K_XRESOLVE},
#define K_MULTIRT 41
{"multirt", K_MULTIRT},
#define K_SETSRC 42
{"setsrc", K_SETSRC},
#define K_SHOW 43
{"show", K_SHOW},
#define K_SECATTR 43
{"secattr", K_SECATTR},
{0, 0}
};
/*
* Size of buffers used to hold command lines from the saved route file as
* well as error strings.
*/
#define BUF_SIZE 2048
typedef union sockunion {
struct sockaddr sa;
struct sockaddr_in sin;
struct sockaddr_dl sdl;
struct sockaddr_in6 sin6;
} su_t;
/*
* This structure represents the digested information from parsing arguments
* to route add, change, delete, and get.
*
*/
typedef struct rtcmd_irep {
int ri_cmd;
int ri_flags;
int ri_af;
ulong_t ri_inits;
struct rt_metrics ri_metrics;
int ri_addrs;
su_t ri_dst;
char *ri_dest_str;
su_t ri_src;
su_t ri_gate;
struct hostent *ri_gate_hp;
char *ri_gate_str;
su_t ri_mask;
su_t ri_ifa;
su_t ri_ifp;
char *ri_ifp_str;
int ri_rtsa_cnt; /* number of gateway security attributes */
struct rtsa_s ri_rtsa; /* enough space for one attribute */
} rtcmd_irep_t;
typedef struct mib_item_s {
struct mib_item_s *next_item;
long group;
long mib_id;
long length;
intmax_t *valp;
} mib_item_t;
typedef enum {
ADDR_TYPE_ANY,
ADDR_TYPE_HOST,
ADDR_TYPE_NET
} addr_type_t;
typedef enum {
SEARCH_MODE_NULL,
SEARCH_MODE_PRINT,
SEARCH_MODE_DEL
} search_mode_t;
static boolean_t args_to_rtcmd(rtcmd_irep_t *rcip, char **argv,
char *cmd_string);
static void bprintf(FILE *fp, int b, char *s);
static boolean_t compare_rtcmd(rtcmd_irep_t *srch_rt,
rtcmd_irep_t *file_rt);
static void delRouteEntry(mib2_ipRouteEntry_t *rp,
mib2_ipv6RouteEntry_t *rp6, int seqno);
static void del_rtcmd_irep(rtcmd_irep_t *rcip);
static void flushroutes(int argc, char *argv[]);
static boolean_t getaddr(rtcmd_irep_t *rcip, int which, char *s,
addr_type_t atype);
static boolean_t in6_getaddr(char *s, struct sockaddr_in6 *sin6,
int *plenp, struct hostent **hpp);
static boolean_t in_getaddr(char *s, struct sockaddr_in *sin,
int *plenp, int which, struct hostent **hpp, addr_type_t atype,
rtcmd_irep_t *rcip);
static int in_getprefixlen(char *addr, int max_plen);
static boolean_t in_prefixlentomask(int prefixlen, int maxlen,
uchar_t *mask);
static void inet_makenetandmask(rtcmd_irep_t *rcip, in_addr_t net,
struct sockaddr_in *sin);
static in_addr_t inet_makesubnetmask(in_addr_t addr, in_addr_t mask);
static int keyword(const char *cp);
static void link_addr(const char *addr, struct sockaddr_dl *sdl);
static char *link_ntoa(const struct sockaddr_dl *sdl);
static mib_item_t *mibget(int sd);
static char *netname(struct sockaddr *sa);
static int newroute(char **argv);
static rtcmd_irep_t *new_rtcmd_irep(void);
static void pmsg_addrs(const char *cp, size_t len, uint_t addrs);
static void pmsg_common(const struct rt_msghdr *rtm, size_t len);
static void print_getmsg(rtcmd_irep_t *req_rt,
struct rt_msghdr *rtm, int msglen);
static void print_rtcmd_short(FILE *to, rtcmd_irep_t *rcip,
boolean_t gw_good, boolean_t to_saved);
static void print_rtmsg(struct rt_msghdr *rtm, int msglen);
static void quit(char *s, int err) __NORETURN;
static char *routename(const struct sockaddr *sa);
static void rtmonitor(int argc, char *argv[]);
static int rtmsg(rtcmd_irep_t *rcip);
static int salen(const struct sockaddr *sa);
static void save_route(int argc, char **argv, int do_flush);
static void save_string(char **dst, char *src);
static int search_rtfile(FILE *fp, FILE *temp_fp, rtcmd_irep_t *rt,
search_mode_t mode);
static void set_metric(rtcmd_irep_t *rcip, char *value, int key,
boolean_t lock);
static int show_saved_routes(int argc);
static void sockaddr(char *addr, struct sockaddr *sa);
static void sodump(su_t *su, char *which);
static void syntax_arg_missing(char *keyword);
static void syntax_bad_keyword(char *keyword);
static void syntax_error(char *err, ...);
static void usage(char *cp);
static void write_to_rtfile(FILE *fp, int argc, char **argv);
static void pmsg_secattr(const char *, size_t, const char *);
static pid_t pid;
static int s;
static boolean_t nflag;
static int af = AF_INET;
static boolean_t qflag, tflag;
static boolean_t verbose;
static boolean_t debugonly;
static boolean_t fflag;
static boolean_t update_table;
static boolean_t perm_flag;
static boolean_t early_v6_keyword;
static char perm_file_sfx[] = "/etc/inet/static_routes";
static char *perm_file;
static char temp_file_sfx[] = "/etc/inet/static_routes.tmp";
static char *temp_file;
static struct in6_addr in6_host_mask = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
/*
* WARNING:
* This next variable indicates whether certain functions exit when an error
* is detected in the user input. Currently, exit_on_error is only set false
* in search_rtfile(), when argument are being parsed. Only those functions
* used by search_rtfile() to parse its arguments are designed to work in
* both modes. Take particular care in setting this false to ensure that any
* functions you call that might act on this flag properly return errors when
* exit_on_error is false.
*/
static int exit_on_error = B_TRUE;
static struct {
struct rt_msghdr m_rtm;
char m_space[BUF_SIZE];
} m_rtmsg;
/*
* Sizes of data structures extracted from the base mib.
* This allows the size of the tables entries to grow while preserving
* binary compatibility.
*/
static int ipRouteEntrySize;
static int ipv6RouteEntrySize;
#define ROUNDUP_LONG(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof (long) - 1))) : sizeof (long))
#define ADVANCE(x, n) ((x) += ROUNDUP_LONG(salen(n)))
#define C(x) ((x) & 0xff)
/*
* return values from in_getprefixlen()
*/
#define BAD_ADDR -1 /* prefix is invalid */
#define NO_PREFIX -2 /* no prefix was found */
void
usage(char *cp)
{
if (cp != NULL) {
(void) fprintf(stderr, gettext("route: botched keyword: %s\n"),
cp);
}
(void) fprintf(stderr, gettext("usage: route [ -fnpqv ] "
"[ -R <root-dir> ] cmd [[ -<qualifers> ] args ]\n"));
exit(1);
/* NOTREACHED */
}
/*PRINTFLIKE1*/
void
syntax_error(char *err, ...)
{
va_list args;
if (exit_on_error) {
va_start(args, err);
(void) vfprintf(stderr, err, args);
va_end(args);
exit(1);
}
/* NOTREACHED */
}
void
syntax_bad_keyword(char *keyword)
{
syntax_error(gettext("route: botched keyword: %s\n"), keyword);
}
void
syntax_arg_missing(char *keyword)
{
syntax_error(gettext("route: argument required following keyword %s\n"),
keyword);
}
void
quit(char *s, int sverrno)
{
(void) fprintf(stderr, "route: ");
if (s != NULL)
(void) fprintf(stderr, "%s: ", s);
(void) fprintf(stderr, "%s\n", strerror(sverrno));
exit(sverrno);
/* NOTREACHED */
}
int
main(int argc, char **argv)
{
extern int optind;
extern char *optarg;
int ch;
int rval;
size_t size;
const char *root_dir = NULL;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
if (argc < 2)
usage(NULL);
while ((ch = getopt(argc, argv, "R:nqdtvfp")) != EOF) {
switch (ch) {
case 'n':
nflag = B_TRUE;
break;
case 'q':
qflag = B_TRUE;
break;
case 'v':
verbose = B_TRUE;
break;
case 't':
tflag = B_TRUE;
break;
case 'd':
debugonly = B_TRUE;
break;
case 'f':
fflag = B_TRUE;
break;
case 'p':
perm_flag = B_TRUE;
break;
case 'R':
root_dir = optarg;
break;
case '?':
default:
usage(NULL);
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
pid = getpid();
if (tflag)
s = open("/dev/null", O_WRONLY);
else
s = socket(PF_ROUTE, SOCK_RAW, 0);
if (s < 0)
quit("socket", errno);
/*
* Handle the -p and -R flags. The -R flag only applies
* when the -p flag is set.
*/
if (root_dir == NULL) {
perm_file = perm_file_sfx;
temp_file = temp_file_sfx;
} else {
size = strlen(root_dir) + sizeof (perm_file_sfx);
perm_file = malloc(size);
if (perm_file == NULL)
quit("malloc", errno);
(void) snprintf(perm_file, size, "%s%s", root_dir,
perm_file_sfx);
size = strlen(root_dir) + sizeof (temp_file_sfx);
temp_file = malloc(size);
if (temp_file == NULL)
quit("malloc", errno);
(void) snprintf(temp_file, size, "%s%s", root_dir,
temp_file_sfx);
}
/*
* Whether or not to act on the routing table. The only time the
* routing table is not modified is when both -p and -R are present.
*/
update_table = (!perm_flag || root_dir == NULL);
if (tflag)
perm_flag = 0;
if (fflag) {
/*
* Accept an address family keyword after the -f. Since the
* default address family is AF_INET, reassign af only for the
* other valid address families.
*/
if (*argv != NULL) {
switch (keyword(*argv)) {
case K_INET6:
af = AF_INET6;
early_v6_keyword = B_TRUE;
/* fallthrough */
case K_INET:
/* Skip over the address family parameter. */
argc--;
argv++;
break;
}
}
flushroutes(0, NULL);
}
if (*argv != NULL) {
switch (keyword(*argv)) {
case K_GET:
case K_CHANGE:
case K_ADD:
case K_DELETE:
rval = 0;
if (update_table) {
rval = newroute(argv);
}
if (perm_flag && (rval == 0 || rval == EEXIST ||
rval == ESRCH)) {
save_route(argc, argv, B_FALSE);
return (0);
}
return (rval);
case K_SHOW:
if (perm_flag) {
return (show_saved_routes(argc));
} else {
syntax_error(gettext(
"route: show command requires -p\n"));
}
/* NOTREACHED */
case K_MONITOR:
rtmonitor(argc, argv);
/* NOTREACHED */
case K_FLUSH:
flushroutes(argc, argv);
return (0);
}
}
if (!fflag)
usage(*argv);
return (0);
}
/*
* Purge all entries in the routing tables not
* associated with network interfaces.
*/
void
flushroutes(int argc, char *argv[])
{
int seqno;
int sd; /* mib stream */
mib_item_t *item;
mib2_ipRouteEntry_t *rp;
mib2_ipv6RouteEntry_t *rp6;
int oerrno;
int off = 0;
int on = 1;
if (argc > 1) {
argv++;
if (argc == 2 && **argv == '-') {
/*
* The address family (preceded by a dash) may be used
* to flush the routes of that particular family.
*/
switch (keyword(*argv + 1)) {
case K_INET:
af = AF_INET;
break;
case K_LINK:
af = AF_LINK;
break;
case K_INET6:
af = AF_INET6;
break;
default:
usage(*argv);
/* NOTREACHED */
}
} else {
usage(*argv);
}
}
if (perm_flag) {
/* This flushes the persistent route file */
save_route(0, NULL, B_TRUE);
}
if (!update_table) {
return;
}
if (setsockopt(s, SOL_SOCKET, SO_USELOOPBACK, (char *)&off,
sizeof (off)) < 0)
quit("setsockopt", errno);
sd = open("/dev/ip", O_RDWR);
oerrno = errno;
if (sd < 0) {
switch (errno) {
case EACCES:
(void) fprintf(stderr,
gettext("route: flush: insufficient privileges\n"));
exit(oerrno);
/* NOTREACHED */
default:
quit(gettext("can't open mib stream"), oerrno);
/* NOTREACHED */
}
}
if ((item = mibget(sd)) == NULL)
quit("mibget", errno);
if (verbose) {
(void) printf("Examining routing table from "
"T_SVR4_OPTMGMT_REQ\n");
}
seqno = 0; /* ??? */
switch (af) {
case AF_INET:
/* Extract ipRouteEntrySize */
for (; item != NULL; item = item->next_item) {
if (item->mib_id != 0)
continue;
if (item->group == MIB2_IP) {
ipRouteEntrySize =
((mib2_ip_t *)item->valp)->ipRouteEntrySize;
assert(IS_P2ALIGNED(ipRouteEntrySize,
sizeof (mib2_ipRouteEntry_t *)));
break;
}
}
if (ipRouteEntrySize == 0) {
(void) fprintf(stderr,
gettext("ipRouteEntrySize can't be determined.\n"));
exit(1);
}
for (; item != NULL; item = item->next_item) {
/*
* skip all the other trash that comes up the mib stream
*/
if (item->group != MIB2_IP ||
item->mib_id != MIB2_IP_ROUTE)
continue;
for (rp = (mib2_ipRouteEntry_t *)item->valp;
(char *)rp < (char *)item->valp + item->length;
/* LINTED */
rp = (mib2_ipRouteEntry_t *)
((char *)rp + ipRouteEntrySize)) {
delRouteEntry(rp, NULL, seqno);
seqno++;
}
break;
}
break;
case AF_INET6:
/* Extract ipv6RouteEntrySize */
for (; item != NULL; item = item->next_item) {
if (item->mib_id != 0)
continue;
if (item->group == MIB2_IP6) {
ipv6RouteEntrySize =
((mib2_ipv6IfStatsEntry_t *)item->valp)->
ipv6RouteEntrySize;
assert(IS_P2ALIGNED(ipv6RouteEntrySize,
sizeof (mib2_ipv6RouteEntry_t *)));
break;
}
}
if (ipv6RouteEntrySize == 0) {
(void) fprintf(stderr, gettext(
"ipv6RouteEntrySize cannot be determined.\n"));
exit(1);
}
for (; item != NULL; item = item->next_item) {
/*
* skip all the other trash that comes up the mib stream
*/
if (item->group != MIB2_IP6 ||
item->mib_id != MIB2_IP6_ROUTE)
continue;
for (rp6 = (mib2_ipv6RouteEntry_t *)item->valp;
(char *)rp6 < (char *)item->valp + item->length;
/* LINTED */
rp6 = (mib2_ipv6RouteEntry_t *)
((char *)rp6 + ipv6RouteEntrySize)) {
delRouteEntry(NULL, rp6, seqno);
seqno++;
}
break;
}
break;
}
if (setsockopt(s, SOL_SOCKET, SO_USELOOPBACK, (char *)&on,
sizeof (on)) < 0)
quit("setsockopt", errno);
}
/*
* Given the contents of a mib_item_t of id type MIB2_IP_ROUTE or
* MIB2_IP6_ROUTE, construct and send an RTM_DELETE routing socket message in
* order to facilitate the flushing of RTF_GATEWAY routes.
*/
static void
delRouteEntry(mib2_ipRouteEntry_t *rp, mib2_ipv6RouteEntry_t *rp6, int seqno)
{
char *cp;
int ire_type;
int rlen;
struct rt_msghdr *rtm;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
int slen;
if (rp != NULL)
ire_type = rp->ipRouteInfo.re_ire_type;
else
ire_type = rp6->ipv6RouteInfo.re_ire_type;
if (ire_type != IRE_DEFAULT &&
ire_type != IRE_PREFIX &&
ire_type != IRE_HOST &&
ire_type != IRE_HOST_REDIRECT)
return;
rtm = &m_rtmsg.m_rtm;
(void) memset(rtm, 0, sizeof (m_rtmsg));
rtm->rtm_type = RTM_DELETE;
rtm->rtm_seq = seqno;
rtm->rtm_flags |= RTF_GATEWAY;
rtm->rtm_version = RTM_VERSION;
rtm->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
cp = m_rtmsg.m_space;
if (rp != NULL) {
slen = sizeof (struct sockaddr_in);
if (rp->ipRouteMask == IP_HOST_MASK)
rtm->rtm_flags |= RTF_HOST;
(void) memset(&sin, 0, slen);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = rp->ipRouteDest;
(void) memmove(cp, &sin, slen);
cp += slen;
sin.sin_addr.s_addr = rp->ipRouteNextHop;
(void) memmove(cp, &sin, slen);
cp += slen;
sin.sin_addr.s_addr = rp->ipRouteMask;
(void) memmove(cp, &sin, slen);
cp += slen;
} else {
slen = sizeof (struct sockaddr_in6);
if (rp6->ipv6RoutePfxLength == IPV6_ABITS)
rtm->rtm_flags |= RTF_HOST;
(void) memset(&sin6, 0, slen);
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = rp6->ipv6RouteDest;
(void) memmove(cp, &sin6, slen);
cp += slen;
sin6.sin6_addr = rp6->ipv6RouteNextHop;
(void) memmove(cp, &sin6, slen);
cp += slen;
(void) memset(&sin6.sin6_addr, 0, sizeof (sin6.sin6_addr));
(void) in_prefixlentomask(rp6->ipv6RoutePfxLength, IPV6_ABITS,
(uchar_t *)&sin6.sin6_addr.s6_addr);
(void) memmove(cp, &sin6, slen);
cp += slen;
}
rtm->rtm_msglen = cp - (char *)&m_rtmsg;
if (debugonly) {
/*
* In debugonly mode, the routing socket message to delete the
* current entry is not actually sent. However if verbose is
* also set, the routing socket message that would have been
* is printed.
*/
if (verbose)
print_rtmsg(rtm, rtm->rtm_msglen);
return;
}
rlen = write(s, (char *)&m_rtmsg, rtm->rtm_msglen);
if (rlen < (int)rtm->rtm_msglen) {
if (rlen < 0) {
(void) fprintf(stderr,
gettext("route: write to routing socket: %s\n"),
strerror(errno));
} else {
(void) fprintf(stderr, gettext("route: write to "
"routing socket got only %d for rlen\n"), rlen);
}
return;
}
if (qflag) {
/*
* In quiet mode, nothing is printed at all (unless the write()
* itself failed.
*/
return;
}
if (verbose) {
print_rtmsg(rtm, rlen);
} else {
struct sockaddr *sa = (struct sockaddr *)(rtm + 1);
(void) printf("%-20.20s ",
rtm->rtm_flags & RTF_HOST ? routename(sa) :
netname(sa));
/* LINTED */
sa = (struct sockaddr *)(salen(sa) + (char *)sa);
(void) printf("%-20.20s ", routename(sa));
(void) printf("done\n");
}
}
/*
* Return the name of the host whose address is given.
*/
char *
routename(const struct sockaddr *sa)
{
char *cp;
static char line[MAXHOSTNAMELEN + 1];
struct hostent *hp = NULL;
static char domain[MAXHOSTNAMELEN + 1];
static boolean_t first = B_TRUE;
struct in_addr in;
struct in6_addr in6;
int error_num;
ushort_t *s;
ushort_t *slim;
if (first) {
first = B_FALSE;
if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
(cp = strchr(domain, '.')))
(void) strcpy(domain, cp + 1);
else
domain[0] = 0;
}
if (salen(sa) == 0) {
(void) strcpy(line, "default");
return (line);
}
switch (sa->sa_family) {
case AF_INET:
/* LINTED */
in = ((struct sockaddr_in *)sa)->sin_addr;
cp = NULL;
if (in.s_addr == INADDR_ANY)
cp = "default";
if (cp == NULL && !nflag) {
hp = gethostbyaddr((char *)&in, sizeof (struct in_addr),
AF_INET);
if (hp != NULL) {
if (((cp = strchr(hp->h_name, '.')) != NULL) &&
(strcmp(cp + 1, domain) == 0))
*cp = 0;
cp = hp->h_name;
}
}
if (cp != NULL) {
(void) strncpy(line, cp, MAXHOSTNAMELEN);
line[MAXHOSTNAMELEN] = '\0';
} else {
in.s_addr = ntohl(in.s_addr);
(void) sprintf(line, "%u.%u.%u.%u", C(in.s_addr >> 24),
C(in.s_addr >> 16), C(in.s_addr >> 8),
C(in.s_addr));
}
break;
case AF_LINK:
return (link_ntoa((struct sockaddr_dl *)sa));
case AF_INET6:
/* LINTED */
in6 = ((struct sockaddr_in6 *)sa)->sin6_addr;
cp = NULL;
if (IN6_IS_ADDR_UNSPECIFIED(&in6))
cp = "default";
if (cp == NULL && !nflag) {
hp = getipnodebyaddr((char *)&in6,
sizeof (struct in6_addr), AF_INET6, &error_num);
if (hp != NULL) {
if (((cp = strchr(hp->h_name, '.')) != NULL) &&
(strcmp(cp + 1, domain) == 0))
*cp = 0;
cp = hp->h_name;
}
}
if (cp != NULL) {
(void) strncpy(line, cp, MAXHOSTNAMELEN);
line[MAXHOSTNAMELEN] = '\0';
} else {
(void) inet_ntop(AF_INET6, (void *)&in6, line,
INET6_ADDRSTRLEN);
}
if (hp != NULL)
freehostent(hp);
break;
default:
s = (ushort_t *)sa;
slim = s + ((salen(sa) + 1) >> 1);
cp = line + sprintf(line, "(%d)", sa->sa_family);
while (++s < slim) /* start with sa->sa_data */
cp += sprintf(cp, " %x", *s);
break;
}
return (line);
}
/*
* Return the name of the network whose address is given.
* The address is assumed to be that of a net or subnet, not a host.
*/
static char *
netname(struct sockaddr *sa)
{
char *cp = NULL;
static char line[MAXHOSTNAMELEN + 1];
struct netent *np;
in_addr_t net, mask;
int subnetshift;
struct in_addr in;
ushort_t *s;
ushort_t *slim;
switch (sa->sa_family) {
case AF_INET:
/* LINTED */
in = ((struct sockaddr_in *)sa)->sin_addr;
in.s_addr = ntohl(in.s_addr);
if (in.s_addr == INADDR_ANY) {
cp = "default";
} else if (!nflag) {
if (IN_CLASSA(in.s_addr)) {
mask = IN_CLASSA_NET;
subnetshift = 8;
} else if (IN_CLASSB(in.s_addr)) {
mask = IN_CLASSB_NET;
subnetshift = 8;
} else {
mask = IN_CLASSC_NET;
subnetshift = 4;
}
/*
* If there are more bits than the standard mask
* would suggest, subnets must be in use.
* Guess at the subnet mask, assuming reasonable
* width subnet fields.
*/
while (in.s_addr &~ mask)
mask = (long)mask >> subnetshift;
net = in.s_addr & mask;
while ((mask & 1) == 0)
mask >>= 1, net >>= 1;
np = getnetbyaddr(net, AF_INET);
if (np != NULL)
cp = np->n_name;
}
if (cp != NULL) {
(void) strncpy(line, cp, MAXHOSTNAMELEN);
line[MAXHOSTNAMELEN] = '\0';
} else if ((in.s_addr & 0xffffff) == 0) {
(void) sprintf(line, "%u", C(in.s_addr >> 24));
} else if ((in.s_addr & 0xffff) == 0) {
(void) sprintf(line, "%u.%u", C(in.s_addr >> 24),
C(in.s_addr >> 16));
} else if ((in.s_addr & 0xff) == 0) {
(void) sprintf(line, "%u.%u.%u", C(in.s_addr >> 24),
C(in.s_addr >> 16), C(in.s_addr >> 8));
} else {
(void) sprintf(line, "%u.%u.%u.%u", C(in.s_addr >> 24),
C(in.s_addr >> 16), C(in.s_addr >> 8),
C(in.s_addr));
}
break;
case AF_LINK:
return (link_ntoa((struct sockaddr_dl *)sa));
case AF_INET6:
return (routename(sa));
default:
/* LINTED */
s = (ushort_t *)sa->sa_data;
slim = s + ((salen(sa) + 1) >> 1);
cp = line + sprintf(line, "af %d:", sa->sa_family);
while (s < slim)
cp += sprintf(cp, " %x", *s++);
break;
}
return (line);
}
/*
* Initialize a new structure. Keep in mind that ri_dst_str, ri_gate_str and
* ri_ifp_str will be freed by det_rtcmd_irep, so they should either be NULL
* or point to dynamically allocated memory.
*/
rtcmd_irep_t *
new_rtcmd_irep(void)
{
rtcmd_irep_t *rcip;
rcip = calloc(1, sizeof (rtcmd_irep_t));
if (rcip == NULL) {
quit("calloc", errno);
}
rcip->ri_af = af;
rcip->ri_flags = RTF_STATIC;
return (rcip);
}
void
del_rtcmd_irep(rtcmd_irep_t *rcip)
{
free(rcip->ri_dest_str);
free(rcip->ri_gate_str);
free(rcip->ri_ifp_str);
if (rcip->ri_gate_hp != NULL) {
freehostent(rcip->ri_gate_hp);
}
free(rcip);
}
void
save_string(char **dst, char *src)
{
free(*dst);
*dst = strdup(src);
if (*dst == NULL) {
quit("malloc", errno);
}
}
/*
* Print the short form summary of a route command.
* Eg. "add net default: gateway 10.0.0.1"
* The final newline is not added, allowing the caller to append additional
* information.
*/
void
print_rtcmd_short(FILE *to, rtcmd_irep_t *rcip, boolean_t gw_good,
boolean_t to_saved)
{
char *cmd;
char obuf[INET6_ADDRSTRLEN];
switch (rcip->ri_cmd) {
case RTM_ADD:
cmd = "add";
break;
case RTM_CHANGE:
cmd = "change";
break;
case RTM_DELETE:
cmd = "delete";
break;
case RTM_GET:
cmd = "get";
break;
default:
assert(0);
}
(void) fprintf(to, "%s%s %s %s", cmd,
(to_saved) ? " persistent" : "",
(rcip->ri_flags & RTF_HOST) ? "host" : "net",
(rcip->ri_dest_str == NULL) ? "NULL" : rcip->ri_dest_str);
if (rcip->ri_gate_str != NULL) {
switch (rcip->ri_af) {
case AF_INET:
if (nflag) {
(void) fprintf(to, ": gateway %s",
inet_ntoa(rcip->ri_gate.sin.sin_addr));
} else if (gw_good &&
rcip->ri_gate_hp != NULL &&
rcip->ri_gate_hp->h_addr_list[1] != NULL) {
/*
* Print the actual address used in the case
* where there was more than one address
* available for the name, and one was used
* successfully.
*/
(void) fprintf(to, ": gateway %s (%s)",
rcip->ri_gate_str,
inet_ntoa(rcip->ri_gate.sin.sin_addr));
} else {
(void) fprintf(to, ": gateway %s",
rcip->ri_gate_str);
}
break;
case AF_INET6:
if (inet_ntop(AF_INET6,
&rcip->ri_gate.sin6.sin6_addr, obuf,
INET6_ADDRSTRLEN) != NULL) {
if (nflag) {
(void) fprintf(to, ": gateway %s",
obuf);
break;
}
if (gw_good &&
rcip->ri_gate_hp->h_addr_list[1] != NULL) {
(void) fprintf(to, ": gateway %s (%s)",
rcip->ri_gate_str, obuf);
break;
}
}
/* FALLTHROUGH */
default:
(void) fprintf(to, ": gateway %s",
rcip->ri_gate_str);
break;
}
}
}
void
set_metric(rtcmd_irep_t *rcip, char *value, int key, boolean_t lock)
{
int flag = 0;
uint_t noval, *valp = &noval;
switch (key) {
#define caseof(x, y, z) \
case (x): valp = &(rcip->ri_metrics.z); flag = (y); break
caseof(K_MTU, RTV_MTU, rmx_mtu);
caseof(K_HOPCOUNT, RTV_HOPCOUNT, rmx_hopcount);
caseof(K_EXPIRE, RTV_EXPIRE, rmx_expire);
caseof(K_RECVPIPE, RTV_RPIPE, rmx_recvpipe);
caseof(K_SENDPIPE, RTV_SPIPE, rmx_sendpipe);
caseof(K_SSTHRESH, RTV_SSTHRESH, rmx_ssthresh);
caseof(K_RTT, RTV_RTT, rmx_rtt);
caseof(K_RTTVAR, RTV_RTTVAR, rmx_rttvar);
#undef caseof
}
rcip->ri_inits |= flag;
if (lock)
rcip->ri_metrics.rmx_locks |= flag;
*valp = atoi(value);
}
/*
* Parse the options give in argv[], filling in rcip with the results.
* If cmd_string is non-null, argc and argv are ignored, and cmd_string is
* tokenized to produce the command line. Cmd_string is tokenized using
* strtok, which will overwrite whitespace in the string with nulls.
*
* Returns B_TRUE on success and B_FALSE on failure.
*/
boolean_t
args_to_rtcmd(rtcmd_irep_t *rcip, char **argv, char *cmd_string)
{
const char *ws = "\f\n\r\t\v ";
char *tok = cmd_string;
char *keyword_str;
addr_type_t atype = ADDR_TYPE_ANY;
boolean_t iflag = B_FALSE;
boolean_t locknext = B_FALSE;
boolean_t lockrest = B_FALSE;
boolean_t dash_keyword;
int key;
char *err;
if (cmd_string == NULL) {
tok = argv[0];
} else {
tok = strtok(cmd_string, ws);
}
/*
* The command keywords are already fully checked by main() or
* search_rtfile().
*/
switch (*tok) {
case 'a':
rcip->ri_cmd = RTM_ADD;
break;
case 'c':
rcip->ri_cmd = RTM_CHANGE;
break;
case 'd':
rcip->ri_cmd = RTM_DELETE;
break;
case 'g':
rcip->ri_cmd = RTM_GET;
break;
default:
/* NOTREACHED */
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
#define NEXTTOKEN \
((tok = (cmd_string == NULL ? *++argv : strtok(NULL, ws))) != NULL)
while (NEXTTOKEN) {
keyword_str = tok;
if (*tok == '-') {
dash_keyword = B_TRUE;
key = keyword(tok + 1);
} else {
dash_keyword = B_FALSE;
key = keyword(tok);
if (key != K_HOST && key != K_NET) {
/* All others must be preceded by '-' */
key = 0;
}
}
switch (key) {
case K_HOST:
if (atype == ADDR_TYPE_NET) {
syntax_error(gettext("route: -host and -net "
"are mutually exclusive\n"));
return (B_FALSE);
}
atype = ADDR_TYPE_HOST;
break;
case K_NET:
if (atype == ADDR_TYPE_HOST) {
syntax_error(gettext("route: -host and -net "
"are mutually exclusive\n"));
return (B_FALSE);
}
atype = ADDR_TYPE_NET;
break;
case K_LINK:
rcip->ri_af = AF_LINK;
break;
case K_INET:
rcip->ri_af = AF_INET;
break;
case K_SA:
rcip->ri_af = PF_ROUTE;
break;
case K_INET6:
rcip->ri_af = AF_INET6;
break;
case K_IFACE:
case K_INTERFACE:
iflag = B_TRUE;
/* fallthrough */
case K_NOSTATIC:
rcip->ri_flags &= ~RTF_STATIC;
break;
case K_LOCK:
locknext = B_TRUE;
break;
case K_LOCKREST:
lockrest = B_TRUE;
break;
case K_REJECT:
rcip->ri_flags |= RTF_REJECT;
break;
case K_BLACKHOLE:
rcip->ri_flags |= RTF_BLACKHOLE;
break;
case K_PROTO1:
rcip->ri_flags |= RTF_PROTO1;
break;
case K_PROTO2:
rcip->ri_flags |= RTF_PROTO2;
break;
case K_CLONING:
rcip->ri_flags |= RTF_CLONING;
break;
case K_XRESOLVE:
rcip->ri_flags |= RTF_XRESOLVE;
break;
case K_STATIC:
rcip->ri_flags |= RTF_STATIC;
break;
case K_IFA:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if (!getaddr(rcip, RTA_IFA, tok, atype)) {
return (B_FALSE);
}
break;
case K_IFP:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if (!getaddr(rcip, RTA_IFP, tok, atype)) {
return (B_FALSE);
}
break;
case K_GATEWAY:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if (!getaddr(rcip, RTA_GATEWAY, tok, atype)) {
return (B_FALSE);
}
break;
case K_DST:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if (!getaddr(rcip, RTA_DST, tok, atype)) {
return (B_FALSE);
}
break;
case K_NETMASK:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if (!getaddr(rcip, RTA_NETMASK, tok, atype)) {
return (B_FALSE);
}
atype = ADDR_TYPE_NET;
break;
case K_MTU:
case K_HOPCOUNT:
case K_EXPIRE:
case K_RECVPIPE:
case K_SENDPIPE:
case K_SSTHRESH:
case K_RTT:
case K_RTTVAR:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
set_metric(rcip, tok, key, locknext || lockrest);
locknext = B_FALSE;
break;
case K_PRIVATE:
rcip->ri_flags |= RTF_PRIVATE;
break;
case K_MULTIRT:
rcip->ri_flags |= RTF_MULTIRT;
break;
case K_SETSRC:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if (!getaddr(rcip, RTA_SRC, tok, atype)) {
return (B_FALSE);
}
rcip->ri_flags |= RTF_SETSRC;
break;
case K_SECATTR:
if (!NEXTTOKEN) {
syntax_arg_missing(keyword_str);
return (B_FALSE);
}
if ((rcip->ri_cmd == RTM_ADD ||
rcip->ri_cmd == RTM_CHANGE) &&
rcip->ri_rtsa_cnt++ < 1 && is_system_labeled()) {
int err;
if (!rtsa_keyword(tok, &rcip->ri_rtsa, &err,
NULL)) {
(void) fprintf(stderr, gettext("route: "
"bad security attribute: %s\n"),
tsol_strerror(err, errno));
return (B_FALSE);
}
}
if (rcip->ri_rtsa_cnt > 1) {
(void) fprintf(stderr,
gettext("route: can't specify more "
"than one security attribute\n"));
return (B_FALSE);
}
break;
default:
if (dash_keyword) {
syntax_bad_keyword(tok + 1);
return (B_FALSE);
}
if ((rcip->ri_addrs & RTA_DST) == 0) {
if (!getaddr(rcip, RTA_DST, tok, atype)) {
return (B_FALSE);
}
} else if ((rcip->ri_addrs & RTA_GATEWAY) == 0) {
/*
* For the gateway parameter, retrieve the
* pointer to the struct hostent so that all
* possible addresses can be tried until one
* is successful.
*/
if (!getaddr(rcip, RTA_GATEWAY, tok, atype)) {
return (B_FALSE);
}
} else {
ulong_t metric;
/*
* Assume that a regular number is a metric.
* Needed for compatibility with old route
* command syntax.
*/
errno = 0;
metric = strtoul(tok, &err, 10);
if (errno == 0 && *err == '\0' &&
metric < 0x80000000ul) {
iflag = (metric == 0);
if (verbose) {
(void) printf("old usage of "
"trailing number, assuming "
"route %s\n", iflag ?
"to if" : "via gateway");
}
continue;
}
if (!getaddr(rcip, RTA_NETMASK, tok, atype)) {
return (B_FALSE);
}
}
}
}
#undef NEXTTOKEN
if ((rcip->ri_addrs & RTA_DST) == 0) {
syntax_error(gettext("route: destination required\n"));
return (B_FALSE);
} else if ((rcip->ri_cmd == RTM_ADD || rcip->ri_cmd == RTM_DELETE) &&
(rcip->ri_addrs & RTA_GATEWAY) == 0) {
syntax_error(gettext(
"route: gateway required for add or delete command\n"));
return (B_FALSE);
}
if (!iflag) {
rcip->ri_flags |= RTF_GATEWAY;
}
if (atype != ADDR_TYPE_NET) {
if (rcip->ri_addrs & RTA_NETMASK) {
/*
* We know the netmask, so we can set the host flag
* based on whether the netmask is the host netmask.
*/
if (rcip->ri_af == AF_INET &&
rcip->ri_mask.sin.sin_addr.s_addr ==
IP_HOST_MASK) {
rcip->ri_flags |= RTF_HOST;
}
if (rcip->ri_af == AF_INET6 &&
memcmp(&rcip->ri_mask.sin6.sin6_addr,
&in6_host_mask,
sizeof (struct in6_addr)) == 0) {
rcip->ri_flags |= RTF_HOST;
}
} else {
/*
* If no prefix mask has been saved at this point, it
* only makes sense to treat the destination address
* as a host address.
*/
rcip->ri_flags |= RTF_HOST;
}
}
return (B_TRUE);
}
/*
* This command always seeks to the end of the file prior to writing.
*/
void
write_to_rtfile(FILE *fp, int argc, char **argv)
{
char file_line[BUF_SIZE];
int len;
int i;
len = 0;
if (early_v6_keyword) {
/*
* This flag is set when "inet6" was seen as an
* argument to the -f flag. Normally, when writing
* routes to the persistent route file, everything on
* the command line after "add" is saved verbatim.
* In this case, the arguments after "add" may not be
* sufficient, as the ipv6 keyword came before "add",
* yet must be present in the persistent route file.
*/
len += snprintf(file_line, BUF_SIZE, "-inet6 ");
}
for (i = 0; argc > 0 && len < BUF_SIZE; i++, argc--) {
len += snprintf(&file_line[len], BUF_SIZE - len, "%s ",
argv[i]);
}
if (len >= BUF_SIZE)
quit(gettext("Internal Error"), EINVAL);
file_line[len - 1] = '\n';
if (fseek(fp, 0, SEEK_END) != 0 ||
fputs(file_line, fp) == EOF) {
quit(gettext("failed to write to route file"),
errno);
}
}
boolean_t
compare_rtcmd(rtcmd_irep_t *srch_rt, rtcmd_irep_t *file_rt)
{
if (strcmp(srch_rt->ri_dest_str, file_rt->ri_dest_str) != 0 ||
memcmp(&srch_rt->ri_mask, &file_rt->ri_mask, sizeof (su_t)) != 0) {
return (B_FALSE);
}
return (srch_rt->ri_gate_str == NULL ||
strcmp(srch_rt->ri_gate_str, file_rt->ri_gate_str) == 0);
}
/*
* Search the route file for routes matching the supplied route. There are 3
* modes of operation:
* SEARCH_MODE_RET - no side effects.
* SEARCH_MODE_PRINT - prints each matching line.
* SEARCH_MODE_DEL - copies all valid, non-matching lines to tmp_fp.
*
* In all cases, the number of matches is returned. If rt is NULL, all routes
* matching the global af value are considered matching.
*/
int
search_rtfile(FILE *fp, FILE *temp_fp, rtcmd_irep_t *rt, search_mode_t mode)
{
char *tmp_buf;
int match_cnt;
boolean_t match;
char file_line[BUF_SIZE + 4] = "add ";
rtcmd_irep_t *thisrt;
match_cnt = 0;
/*
* Leave space at the beginning of file_line for "add ".
*/
while (fgets(file_line + 4, BUF_SIZE, fp) != NULL) {
if (file_line[4] == '#' || file_line[4] == '\n') {
/* Handle comments and blank lines */
if (mode == SEARCH_MODE_DEL &&
fputs(file_line + 4, temp_fp) == EOF) {
quit(gettext(
"route: failed to write to temp file"),
errno);
}
continue;
}
thisrt = new_rtcmd_irep();
/*
* thisrt->ri_af defaults to whatever address family happens
* to be set in the global af, but routes in the persistent
* route file must be treated as AF_INET by default.
*/
thisrt->ri_af = AF_INET;
exit_on_error = B_FALSE;
tmp_buf = strdup(file_line);
/* args_to_rtcmd() will mangle the string passed. */
if (!args_to_rtcmd(thisrt, NULL, tmp_buf)) {
/* There was an error in args_to_rtcmd() or helpers */
del_rtcmd_irep(thisrt);
free(tmp_buf);
continue;
}
exit_on_error = B_TRUE;
free(tmp_buf);
if (thisrt->ri_gate_str == NULL) {
del_rtcmd_irep(thisrt);
continue;
}
match = (rt == NULL) ? (thisrt->ri_af == af) :
compare_rtcmd(rt, thisrt);
if (match) match_cnt++;
if (match && mode == SEARCH_MODE_PRINT) {
(void) printf("persistent: route %s", file_line);
}
if (match && mode == SEARCH_MODE_DEL) {
thisrt->ri_cmd = RTM_DELETE;
print_rtcmd_short(stdout, thisrt, B_FALSE, B_TRUE);
(void) printf("\n");
}
del_rtcmd_irep(thisrt);
if (!match && mode == SEARCH_MODE_DEL &&
fputs(file_line + 4, temp_fp) == EOF) {
quit(gettext("failed to write to temp file"),
errno);
}
}
return (match_cnt);
}
/*
* Perform the route operation given in argv on the persistent route file.
* If do_flush is set, the persistent route file is flushed of all routes
* matching the global family, and the arguments are ignored.
*/
void
save_route(int argc, char **argv, int do_flush)
{
rtcmd_irep_t *rt;
int perm_fd;
FILE *perm_fp;
FILE *temp_fp;
mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
struct flock lock;
struct stat st;
const char commentstr[] =
"# File generated by route(1M) - do not edit.\n";
perm_fd = open(perm_file, O_RDWR | O_CREAT, fmode);
if (perm_fd == -1 || fstat(perm_fd, &st) == -1)
quit("failed to open route file", errno);
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(perm_fd, F_SETLK, &lock) != 0) {
quit(gettext("failed to lock route file"), errno);
/* NOTREACHED */
}
if (st.st_size == 0 &&
write(perm_fd, commentstr, sizeof (commentstr) - 1) !=
sizeof (commentstr) - 1)
quit(gettext("failed to open route file"), errno);
if ((perm_fp = fdopen(perm_fd, "r+")) == NULL) {
quit(gettext("failed to open route file"), errno);
/* NOTREACHED */
}
if (!do_flush) {
rt = new_rtcmd_irep();
(void) args_to_rtcmd(rt, argv, NULL);
}
if (do_flush || rt->ri_cmd == RTM_DELETE) {
if ((temp_fp = fopen(temp_file, "w")) == NULL) {
quit(gettext("failed to open temp file"), errno);
/* NOTREACHED */
}
}
if (do_flush) {
(void) search_rtfile(perm_fp, temp_fp, NULL, SEARCH_MODE_DEL);
if (fclose(temp_fp) != 0 || rename(temp_file, perm_file) != 0) {
quit(gettext("failed to update route file"), errno);
/* NOTREACHED */
}
(void) fclose(perm_fp);
return;
}
switch (rt->ri_cmd) {
case RTM_ADD:
if (search_rtfile(perm_fp, NULL, rt, SEARCH_MODE_NULL) > 0) {
/* Route is already in the file */
print_rtcmd_short(stderr, rt, B_FALSE, B_TRUE);
(void) fprintf(stderr, ": entry exists\n");
exit(1);
}
write_to_rtfile(perm_fp, argc - 1, argv + 1);
print_rtcmd_short(stdout, rt, B_FALSE, B_TRUE);
(void) printf("\n");
break;
case RTM_CHANGE:
syntax_error(
gettext("route: change command not supported with -p\n"));
/* NOTREACHED */
case RTM_DELETE:
if (search_rtfile(perm_fp, temp_fp, rt, SEARCH_MODE_DEL) <= 0) {
/* Route not found */
print_rtcmd_short(stderr, rt, B_FALSE, B_TRUE);
(void) fprintf(stderr, gettext(": not in file\n"));
exit(1);
}
if (fclose(temp_fp) != 0 || rename(temp_file, perm_file) != 0) {
quit(gettext("failed to update route file"), errno);
/* NOTREACHED */
}
break;
case RTM_GET:
if (search_rtfile(perm_fp, temp_fp, rt, SEARCH_MODE_PRINT) <=
0) {
print_rtcmd_short(stdout, rt, B_FALSE, B_TRUE);
(void) printf(gettext(": not in file\n"));
}
break;
default:
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
/*
* Closing the file unlocks it.
*/
(void) fclose(perm_fp);
}
int
show_saved_routes(int argc)
{
int perm_fd;
FILE *perm_fp;
struct flock lock;
int count = 0;
if (argc != 1) {
syntax_error(gettext("route: invalid arguments for show\n"));
}
perm_fd = open(perm_file, O_RDONLY, 0);
if (perm_fd == -1) {
if (errno == ENOENT) {
(void) printf("No persistent routes are defined\n");
return (0);
} else {
quit(gettext("failed to open route file"), errno);
}
}
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(perm_fd, F_SETLK, &lock) != 0) {
quit(gettext("failed to lock route file"),
errno);
/* NOTREACHED */
}
if ((perm_fp = fdopen(perm_fd, "r")) == NULL) {
quit(gettext("failed to open route file"), errno);
/* NOTREACHED */
}
count += search_rtfile(perm_fp, NULL, NULL, SEARCH_MODE_PRINT);
(void) fseek(perm_fp, 0, SEEK_SET);
af = AF_INET6;
count += search_rtfile(perm_fp, NULL, NULL, SEARCH_MODE_PRINT);
if (count == 0)
(void) printf("No persistent routes are defined\n");
(void) fclose(perm_fp);
return (0);
}
int
newroute(char **argv)
{
rtcmd_irep_t *newrt;
int ret, attempts, oerrno;
char *err;
char obuf[INET6_ADDRSTRLEN];
#define hp (newrt->ri_gate_hp)
newrt = new_rtcmd_irep();
(void) args_to_rtcmd(newrt, argv, NULL);
if (newrt->ri_cmd != RTM_GET && !tflag) {
/* Don't want to read back our messages */
(void) shutdown(s, 0);
}
if (newrt->ri_addrs & RTA_IFP) {
newrt->ri_ifp.sdl.sdl_index = if_nametoindex(newrt->ri_ifp_str);
if (newrt->ri_ifp.sdl.sdl_index == 0) {
if (errno != ENXIO) {
quit("if_nametoindex", errno);
} else {
(void) fprintf(stderr,
gettext("route: %s: no such interface\n"),
newrt->ri_ifp_str);
exit(1);
}
}
newrt->ri_ifp.sdl.sdl_family = AF_LINK;
}
for (attempts = 1; ; attempts++) {
errno = 0;
if ((ret = rtmsg(newrt)) == 0)
break;
if (errno != ENETUNREACH && errno != ESRCH)
break;
if ((newrt->ri_addrs & RTA_GATEWAY) && hp != NULL &&
hp->h_addr_list[attempts] != NULL) {
switch (af) {
case AF_INET:
(void) memmove(&newrt->ri_gate.sin.sin_addr,
hp->h_addr_list[attempts], hp->h_length);
continue;
case AF_INET6:
(void) memmove(&newrt->ri_gate.sin6.sin6_addr,
hp->h_addr_list[attempts], hp->h_length);
continue;
}
}
break;
}
oerrno = errno;
if (newrt->ri_cmd != RTM_GET) {
print_rtcmd_short(stdout, newrt, (ret == 0), B_FALSE);
if (ret == 0)
(void) printf("\n");
} else if (ret != 0) {
/*
* Note: there is nothing additional to print for get
* if ret == 0.
*/
if (nflag) {
switch (newrt->ri_af) {
case AF_INET:
(void) printf(" %s",
inet_ntoa(newrt->ri_dst.sin.sin_addr));
break;
case AF_INET6:
if (inet_ntop(AF_INET6,
(void *)&newrt->ri_dst.sin6.sin6_addr,
obuf, INET6_ADDRSTRLEN) != NULL) {
(void) printf(" %s", obuf);
break;
}
/* FALLTHROUGH */
default:
(void) printf("%s", newrt->ri_dest_str);
break;
}
} else {
(void) printf("%s", newrt->ri_dest_str);
}
}
if (ret != 0) {
switch (oerrno) {
case ESRCH:
err = "not in table";
break;
case EBUSY:
err = "entry in use";
break;
case ENOBUFS:
err = "routing table overflow";
break;
case EEXIST:
err = "entry exists";
break;
case EPERM:
err = "insufficient privileges";
break;
default:
err = strerror(oerrno);
break;
}
(void) printf(": %s\n", err);
}
del_rtcmd_irep(newrt);
return (oerrno);
#undef hp
}
/*
* Convert a network number to the corresponding IP address.
* If the RTA_NETMASK hasn't been specified yet set it based
* on the class of address.
*/
static void
inet_makenetandmask(rtcmd_irep_t *rcip, in_addr_t net, struct sockaddr_in *sin)
{
in_addr_t addr, mask;
if (net == 0) {
mask = addr = 0;
} else if (net < 128) {
addr = net << IN_CLASSA_NSHIFT;
mask = IN_CLASSA_NET;
} else if (net < 65536) {
addr = net << IN_CLASSB_NSHIFT;
mask = IN_CLASSB_NET;
} else if (net < 16777216L) {
addr = net << IN_CLASSC_NSHIFT;
mask = IN_CLASSC_NET;
} else {
addr = net;
if ((addr & IN_CLASSA_HOST) == 0)
mask = IN_CLASSA_NET;
else if ((addr & IN_CLASSB_HOST) == 0)
mask = IN_CLASSB_NET;
else if ((addr & IN_CLASSC_HOST) == 0)
mask = IN_CLASSC_NET;
else {
if (IN_CLASSA(addr))
mask = IN_CLASSA_NET;
else if (IN_CLASSB(addr))
mask = IN_CLASSB_NET;
else if (IN_CLASSC(addr))
mask = IN_CLASSC_NET;
else
mask = IP_HOST_MASK;
mask = inet_makesubnetmask(addr, mask);
}
}
sin->sin_addr.s_addr = htonl(addr);
if (!(rcip->ri_addrs & RTA_NETMASK)) {
rcip->ri_addrs |= RTA_NETMASK;
sin = &rcip->ri_mask.sin;
sin->sin_addr.s_addr = htonl(mask);
sin->sin_family = AF_INET;
}
}
static in_addr_t
inet_makesubnetmask(in_addr_t addr, in_addr_t mask)
{
int n;
struct ifconf ifc;
struct ifreq ifreq;
struct ifreq *ifr;
struct sockaddr_in *sin;
char *buf;
int numifs;
size_t bufsize;
int iosoc;
in_addr_t if_addr, if_mask;
in_addr_t if_subnetmask = 0;
short if_flags;
if (mask == 0)
return (0);
if ((iosoc = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
quit("socket", errno);
if (ioctl(iosoc, SIOCGIFNUM, (char *)&numifs) < 0)
quit("ioctl", errno);
bufsize = numifs * sizeof (struct ifreq);
buf = malloc(bufsize);
if (buf == NULL)
quit("malloc", errno);
(void) memset(&ifc, 0, sizeof (ifc));
ifc.ifc_len = bufsize;
ifc.ifc_buf = buf;
if (ioctl(iosoc, SIOCGIFCONF, (char *)&ifc) < 0)
quit("ioctl (get interface configuration)", errno);
/* Let's check to see if this is maybe a local subnet route. */
ifr = ifc.ifc_req;
for (n = ifc.ifc_len / sizeof (struct ifreq); n > 0; n--, ifr++) {
ifreq = *ifr;
/* LINTED */
sin = (struct sockaddr_in *)&ifr->ifr_addr;
if_addr = ntohl(sin->sin_addr.s_addr);
if (ioctl(iosoc, SIOCGIFFLAGS, (char *)&ifreq) < 0)
quit("ioctl (get interface flags)", errno);
if ((ifreq.ifr_flags & IFF_UP) == 0)
continue;
if_flags = ifreq.ifr_flags;
if (ioctl(iosoc, SIOCGIFNETMASK, (char *)&ifreq) < 0)
quit("ioctl (get netmask)", errno);
/* LINTED */
sin = (struct sockaddr_in *)&ifreq.ifr_addr;
if_mask = ntohl(sin->sin_addr.s_addr);
if ((if_addr & mask) == (addr & mask)) {
/*
* Don't trust pt-pt interfaces if there are
* other interfaces.
*/
if (if_flags & IFF_POINTOPOINT) {
if_subnetmask = if_mask;
continue;
}
/*
* Fine. Just assume the same net mask as the
* directly attached subnet interface is using.
*/
return (if_mask);
}
}
if (if_subnetmask != 0)
return (if_subnetmask);
return (mask);
}
/*
* Interpret an argument as a network address of some kind.
*
* If the address family is one looked up in getaddr() using one of the
* getipnodebyX() functions (currently only AF_INET6), then callers should
* freehostent() the returned "struct hostent" pointer if one was passed in.
*
* If exit_on_error is true, this function will cause route to exit on error by
* calling syntax_error(). Otherwise, it returns B_TRUE on success or B_FALSE
* on failure.
*/
static boolean_t
getaddr(rtcmd_irep_t *rcip, int which, char *s, addr_type_t atype)
{
su_t *su;
struct hostent **hpp;
struct hostent *hp;
int masklen;
if (which == RTA_GATEWAY) {
hpp = &(rcip->ri_gate_hp);
} else {
hpp = &hp;
}
*hpp = NULL;
rcip->ri_addrs |= which;
switch (which) {
case RTA_DST:
save_string(&rcip->ri_dest_str, s);
su = &rcip->ri_dst;
su->sa.sa_family = rcip->ri_af;
break;
case RTA_GATEWAY:
save_string(&rcip->ri_gate_str, s);
su = &rcip->ri_gate;
su->sa.sa_family = rcip->ri_af;
break;
case RTA_NETMASK:
su = &rcip->ri_mask;
su->sa.sa_family = rcip->ri_af;
break;
case RTA_IFP:
save_string(&rcip->ri_ifp_str, s);
return (B_TRUE);
/*
* RTA_SRC has overloaded meaning. It can represent the
* src address of incoming or outgoing packets.
*/
case RTA_IFA:
su = &rcip->ri_ifa;
su->sa.sa_family = rcip->ri_af;
break;
case RTA_SRC:
su = &rcip->ri_src;
su->sa.sa_family = rcip->ri_af;
break;
default:
/* NOTREACHED */
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
if (strcmp(s, "default") == 0) {
if (which == RTA_DST) {
return (getaddr(rcip, RTA_NETMASK, s, ADDR_TYPE_NET));
}
if (which == RTA_SRC) {
return (B_TRUE);
}
return (B_TRUE);
}
switch (rcip->ri_af) {
case AF_LINK:
link_addr(s, &su->sdl);
return (B_TRUE);
case PF_ROUTE:
sockaddr(s, &su->sa);
return (B_TRUE);
case AF_INET6:
switch (which) {
case RTA_DST:
if (!in6_getaddr(s, &su->sin6, &masklen, hpp)) {
return (B_FALSE);
}
if (masklen != NO_PREFIX) {
(void) memset(&rcip->ri_mask.sin6.sin6_addr, 0,
sizeof (rcip->ri_mask.sin6.sin6_addr));
if (!in_prefixlentomask(masklen, IPV6_ABITS,
(uchar_t *)&rcip->ri_mask.sin6.sin6_addr)) {
syntax_error(gettext(
"route: bad prefix length: %d\n"),
masklen);
return (B_FALSE);
}
rcip->ri_mask.sin6.sin6_family = rcip->ri_af;
rcip->ri_addrs |= RTA_NETMASK;
}
return (B_TRUE);
case RTA_GATEWAY:
case RTA_IFA:
case RTA_SRC:
return (in6_getaddr(s, &su->sin6, NULL, hpp));
case RTA_NETMASK:
syntax_error(
gettext("route: -netmask not supported for IPv6: "
"use <prefix>/<prefix-length> instead\n"));
return (B_FALSE);
default:
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
case AF_INET:
switch (which) {
case RTA_DST:
if (!in_getaddr(s, &su->sin, &masklen, which, hpp,
atype, rcip)) {
return (B_FALSE);
}
if (masklen != NO_PREFIX) {
(void) memset(&rcip->ri_mask.sin.sin_addr, 0,
sizeof (rcip->ri_mask.sin.sin_addr));
if (!in_prefixlentomask(masklen, IP_ABITS,
(uchar_t *)&rcip->ri_mask.sin.sin_addr)) {
syntax_error(gettext(
"route: bad prefix length: %d\n"),
masklen);
return (B_FALSE);
}
rcip->ri_mask.sin.sin_family = rcip->ri_af;
rcip->ri_addrs |= RTA_NETMASK;
}
return (B_TRUE);
case RTA_GATEWAY:
case RTA_IFA:
case RTA_NETMASK:
case RTA_SRC:
return (in_getaddr(s, &su->sin, NULL, which, hpp, atype,
rcip));
default:
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
default:
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
return (B_TRUE);
}
/*
* Interpret an argument as an IPv4 network address of some kind,
* returning B_TRUE on success or B_FALSE on failure.
* This function will cause an exit() on failure if exit_on_failure is set.
*
* Note that this tries host interpretation before network interpretation,
* except when -net has been given and the destination address is being parsed.
*
* If the plenp argument is non-NULL, allow <addr>/<n> syntax and
* pass out <n> in *plenp.
* If <n> doesn't parse return BAD_ADDR as *plenp.
* If no /<n> is present return NO_PREFIX as *plenp.
*/
static boolean_t
in_getaddr(char *s, struct sockaddr_in *sin, int *plenp, int which,
struct hostent **hpp, addr_type_t atype, rtcmd_irep_t *rcip)
{
struct hostent *hp;
struct netent *np;
in_addr_t val;
char str[BUFSIZ];
(void) strlcpy(str, s, sizeof (str));
/*
* If plenp is non-NULL, /<n> syntax for netmask is allowed.
*/
if (plenp != NULL) {
char *cp;
*plenp = in_getprefixlen(str, IP_ABITS);
if (*plenp == BAD_ADDR)
return (B_FALSE);
cp = strchr(str, '/');
if (cp != NULL)
*cp = '\0';
} else if (strchr(str, '/') != NULL) {
syntax_error(gettext("route: %s: unexpected '/'\n"), str);
return (B_FALSE);
}
(void) memset(sin, 0, sizeof (*sin));
sin->sin_family = AF_INET;
/*
* Handle 255.255.255.255 as a special case first.
*/
if (strcmp(str, "255.255.255.255") == 0) {
sin->sin_addr.s_addr = INADDR_BROADCAST;
return (B_TRUE);
}
val = inet_addr(str);
if (val != (in_addr_t)-1) {
/* Numeric address */
sin->sin_addr.s_addr = val;
if (which == RTA_DST) {
if (atype == ADDR_TYPE_NET ||
(atype == ADDR_TYPE_ANY &&
inet_lnaof(sin->sin_addr) == INADDR_ANY)) {
/* This looks like a network address. */
inet_makenetandmask(rcip, ntohl(val),
sin);
}
}
return (B_TRUE);
}
/* Host or net name */
if (which != RTA_DST || atype != ADDR_TYPE_NET) {
/* A host name is allowed. */
if ((hp = gethostbyname(str)) != NULL) {
*hpp = hp;
(void) memmove(&sin->sin_addr, hp->h_addr,
hp->h_length);
return (B_TRUE);
}
}
if (atype != ADDR_TYPE_HOST) {
/* A network name is allowed */
if ((np = getnetbyname(str)) != NULL &&
(val = np->n_net) != 0) {
if (which == RTA_DST) {
inet_makenetandmask(rcip, val, sin);
}
return (B_TRUE);
}
}
syntax_error(gettext("%s: bad value\n"), s);
return (B_FALSE);
}
/*
* Interpret an argument as an IPv6 network address of some kind,
* returning B_TRUE on success or B_FALSE on failure.
* This function will cause an exit() on failure if exit_on_failure is set.
*
* If the last argument is non-NULL allow a <addr>/<n> syntax and
* pass out <n> in *plenp.
* If <n> doesn't parse return BAD_ADDR as *plenp.
* If no /<n> is present return NO_PREFIX as *plenp.
*/
static boolean_t
in6_getaddr(char *s, struct sockaddr_in6 *sin6, int *plenp,
struct hostent **hpp)
{
struct hostent *hp;
char str[BUFSIZ];
int error_num;
(void) strlcpy(str, s, sizeof (str));
/*
* If plenp is non-NULL, /<n> syntax for netmask is allowed.
*/
if (plenp != NULL) {
char *cp;
*plenp = in_getprefixlen(str, IPV6_ABITS);
if (*plenp == BAD_ADDR)
return (B_FALSE);
cp = strchr(str, '/');
if (cp != NULL)
*cp = '\0';
} else if (strchr(str, '/') != NULL) {
syntax_error(gettext("route: %s: unexpected '/'\n"), str);
return (B_FALSE);
}
(void) memset(sin6, 0, sizeof (struct sockaddr_in6));
sin6->sin6_family = AF_INET6;
hp = getipnodebyname(str, AF_INET6, 0, &error_num);
if (hp != NULL) {
*hpp = hp;
(void) memmove(&sin6->sin6_addr, hp->h_addr, hp->h_length);
return (B_TRUE);
}
if (error_num == TRY_AGAIN) {
/*
* This isn't a problem if we aren't going to use the address
* right away.
*/
if (!exit_on_error) {
return (B_TRUE);
}
syntax_error(gettext("route: %s: bad address (try "
"again later)\n"), s);
return (B_FALSE);
}
syntax_error(gettext("route: %s: bad address\n"), s);
return (B_FALSE);
}
/*
* Parse <addr>/<n> syntax and return the integer n.
* If <addr> is missing or <n> is not a valid integer, this function calls
* syntax_error() and returns BAD_ADDR.
* if n is not between 0 and max_plen inclusive, this functions calls
* syntax_error() and returns BAD_ADDR.
* If /<n> is not present, this function returns NO_PREFIX.
* The string addr is not modified.
*/
int
in_getprefixlen(char *addr, int max_plen)
{
int prefixlen;
char *str, *end;
str = strchr(addr, '/');
if (str == addr) {
syntax_error(gettext("route: %s: unexpected '/'\n"), addr);
return (BAD_ADDR);
}
if (str == NULL)
return (NO_PREFIX);
str++;
errno = 0;
prefixlen = strtoul(str, &end, 10);
if (errno != 0 || str == end) {
syntax_error(gettext("route: bad prefix length %s\n"), str);
return (BAD_ADDR);
}
if (prefixlen > max_plen) {
syntax_error(gettext("route: prefix length %s out of range\n"),
str);
return (BAD_ADDR);
}
return (prefixlen);
}
/*
* Convert a prefix length to a mask.
* Returns B_TRUE if ok. B_FALSE otherwise.
* Assumes the mask array is zeroed by the caller.
*/
boolean_t
in_prefixlentomask(int prefixlen, int maxlen, uchar_t *mask)
{
if (prefixlen < 0 || prefixlen > maxlen)
return (B_FALSE);
while (prefixlen > 0) {
if (prefixlen >= 8) {
*mask++ = 0xFF;
prefixlen -= 8;
continue;
}
*mask |= 1 << (8 - prefixlen);
prefixlen--;
}
return (B_TRUE);
}
void
rtmonitor(int argc, char *argv[])
{
int n;
intmax_t msg[2048 / sizeof (intmax_t)];
if (tflag)
exit(0);
verbose = B_TRUE;
if (argc > 1) {
argv++;
if (argc == 2 && **argv == '-') {
switch (keyword(*argv + 1)) {
case K_INET:
af = AF_INET;
break;
case K_LINK:
af = AF_LINK;
break;
case K_INET6:
af = AF_INET6;
break;
default:
usage(*argv);
/* NOTREACHED */
}
} else {
usage(*argv);
}
(void) close(s);
s = socket(PF_ROUTE, SOCK_RAW, af);
if (s < 0)
quit("socket", errno);
}
for (;;) {
n = read(s, msg, sizeof (msg));
if (n <= 0)
quit("read", errno);
(void) printf("got message of size %d\n", n);
print_rtmsg((struct rt_msghdr *)msg, n);
}
}
int
rtmsg(rtcmd_irep_t *newrt)
{
static int seq;
int rlen;
char *cp = m_rtmsg.m_space;
int l;
errno = 0;
(void) memset(&m_rtmsg, 0, sizeof (m_rtmsg));
if (newrt->ri_cmd == RTM_GET) {
newrt->ri_ifp.sa.sa_family = AF_LINK;
newrt->ri_addrs |= RTA_IFP;
}
#define rtm m_rtmsg.m_rtm
rtm.rtm_type = newrt->ri_cmd;
rtm.rtm_flags = newrt->ri_flags;
rtm.rtm_version = RTM_VERSION;
rtm.rtm_seq = ++seq;
rtm.rtm_addrs = newrt->ri_addrs;
rtm.rtm_rmx = newrt->ri_metrics;
rtm.rtm_inits = newrt->ri_inits;
#define NEXTADDR(w, u) \
if (newrt->ri_addrs & (w)) { \
l = ROUNDUP_LONG(salen(&u.sa)); \
(void) memmove(cp, &(u), l); \
cp += l; \
if (verbose) \
sodump(&(u), #u); \
}
NEXTADDR(RTA_DST, newrt->ri_dst);
NEXTADDR(RTA_GATEWAY, newrt->ri_gate);
NEXTADDR(RTA_NETMASK, newrt->ri_mask);
NEXTADDR(RTA_IFP, newrt->ri_ifp);
NEXTADDR(RTA_IFA, newrt->ri_ifa);
/*
* RTA_SRC has overloaded meaning. It can represent the
* src address of incoming or outgoing packets.
*/
NEXTADDR(RTA_SRC, newrt->ri_src);
#undef NEXTADDR
if (newrt->ri_rtsa_cnt > 0) {
/* LINTED: aligned */
rtm_ext_t *rtm_ext = (rtm_ext_t *)cp;
tsol_rtsecattr_t *rtsecattr;
rtm_ext->rtmex_type = RTMEX_GATEWAY_SECATTR;
rtm_ext->rtmex_len = TSOL_RTSECATTR_SIZE(1);
rtsecattr = (tsol_rtsecattr_t *)(rtm_ext + 1);
rtsecattr->rtsa_cnt = 1;
bcopy(&newrt->ri_rtsa, rtsecattr->rtsa_attr,
sizeof (newrt->ri_rtsa));
cp = (char *)(rtsecattr->rtsa_attr + 1);
}
rtm.rtm_msglen = l = cp - (char *)&m_rtmsg;
if (verbose)
print_rtmsg(&rtm, l);
if (debugonly)
return (0);
if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) {
switch (errno) {
case ESRCH:
case EBUSY:
case ENOBUFS:
case EEXIST:
case ENETUNREACH:
case EHOSTUNREACH:
case EPERM:
break;
default:
perror(gettext("writing to routing socket"));
break;
}
return (-1);
} else if (rlen < (int)rtm.rtm_msglen) {
(void) fprintf(stderr,
gettext("route: write to routing socket got only %d for "
"len\n"), rlen);
return (-1);
}
if (newrt->ri_cmd == RTM_GET) {
do {
l = read(s, (char *)&m_rtmsg, sizeof (m_rtmsg));
} while (l > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid));
if (l < 0) {
(void) fprintf(stderr,
gettext("route: read from routing socket: %s\n"),
strerror(errno));
} else {
print_getmsg(newrt, &rtm, l);
}
}
#undef rtm
return (0);
}
static char *msgtypes[] = {
"",
"RTM_ADD: Add Route",
"RTM_DELETE: Delete Route",
"RTM_CHANGE: Change Metrics or flags",
"RTM_GET: Report Metrics",
"RTM_LOSING: Kernel Suspects Partitioning",
"RTM_REDIRECT: Told to use different route",
"RTM_MISS: Lookup failed on this address",
"RTM_LOCK: fix specified metrics",
"RTM_OLDADD: caused by SIOCADDRT",
"RTM_OLDDEL: caused by SIOCDELRT",
"RTM_RESOLVE: Route created by cloning",
"RTM_NEWADDR: address being added to iface",
"RTM_DELADDR: address being removed from iface",
"RTM_IFINFO: iface status change",
0,
};
#define NMSGTYPES (sizeof (msgtypes) / sizeof (msgtypes[0]))
static char metricnames[] =
"\011pksent\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire\2hopcount"
"\1mtu";
static char routeflags[] =
"\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE\010MASK_PRESENT"
"\011CLONING\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE"
"\016PRIVATE\017PROTO2\020PROTO1\021MULTIRT\022SETSRC";
static char ifnetflags[] =
"\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6NOTRAILERS\7RUNNING\010NOARP"
"\011PPROMISC\012ALLMULTI\013INTELLIGENT\014MULTICAST"
"\015MULTI_BCAST\016UNNUMBERED\017DHCP\020PRIVATE"
"\021NOXMIT\022NOLOCAL\023DEPRECATED\024ADDRCONF"
"\025ROUTER\026NONUD\027ANYCAST\030NORTEXCH\031IPv4\032IPv6"
"\033MIP\034NOFAILOVER\035FAILED\036STANDBY\037INACTIVE\040OFFLINE"
"\041XRESOLV\042COS\043PREFERRED\044TEMPORARY";
static char addrnames[] =
"\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD\011SRC";
void
print_rtmsg(struct rt_msghdr *rtm, int msglen)
{
struct if_msghdr *ifm;
struct ifa_msghdr *ifam;
if (!verbose)
return;
if (rtm->rtm_version != RTM_VERSION) {
(void) printf("routing message version %d not understood\n",
rtm->rtm_version);
return;
}
if (rtm->rtm_msglen != msglen) {
(void) printf("message length mismatch, in packet %d, "
"returned %d\n",
rtm->rtm_msglen, msglen);
if (msglen > rtm->rtm_msglen)
msglen = rtm->rtm_msglen;
}
/*
* Since rtm->rtm_type is unsigned, we'll just check the case of zero
* and the upper-bound of (NMSGTYPES - 1).
*/
if (rtm->rtm_type == 0 || rtm->rtm_type >= (NMSGTYPES - 1)) {
(void) printf("routing message type %d not understood\n",
rtm->rtm_type);
return;
}
(void) printf("%s: len %d, ", msgtypes[rtm->rtm_type], msglen);
switch (rtm->rtm_type) {
case RTM_IFINFO:
ifm = (struct if_msghdr *)rtm;
(void) printf("if# %d, flags:", ifm->ifm_index);
bprintf(stdout, ifm->ifm_flags, ifnetflags);
pmsg_addrs((const char *)(ifm + 1), msglen - sizeof (*ifm),
ifm->ifm_addrs);
break;
case RTM_NEWADDR:
case RTM_DELADDR:
ifam = (struct ifa_msghdr *)rtm;
(void) printf("metric %d, flags:", ifam->ifam_metric);
bprintf(stdout, ifam->ifam_flags, routeflags);
pmsg_addrs((const char *)(ifam + 1), msglen - sizeof (*ifam),
ifam->ifam_addrs);
break;
default:
(void) printf("pid: %ld, seq %d, errno %d, flags:",
rtm->rtm_pid, rtm->rtm_seq, rtm->rtm_errno);
bprintf(stdout, rtm->rtm_flags, routeflags);
pmsg_common(rtm, msglen);
break;
}
}
void
print_getmsg(rtcmd_irep_t *req_rt, struct rt_msghdr *rtm, int msglen)
{
struct sockaddr *dst = NULL, *gate = NULL, *mask = NULL, *src = NULL;
struct sockaddr_dl *ifp = NULL;
struct sockaddr *sa;
char *cp;
int i;
(void) printf(" route to: %s\n", routename(&req_rt->ri_dst.sa));
if (rtm->rtm_version != RTM_VERSION) {
(void) fprintf(stderr,
gettext("routing message version %d not understood\n"),
rtm->rtm_version);
return;
}
if (rtm->rtm_msglen > (ushort_t)msglen) {
(void) fprintf(stderr,
gettext("message length mismatch, in packet %d, "
"returned %d\n"), rtm->rtm_msglen, msglen);
}
if (rtm->rtm_errno) {
(void) fprintf(stderr, "RTM_GET: %s (errno %d)\n",
strerror(rtm->rtm_errno), rtm->rtm_errno);
return;
}
cp = ((char *)(rtm + 1));
if (rtm->rtm_addrs != 0) {
for (i = 1; i != 0; i <<= 1) {
if (i & rtm->rtm_addrs) {
/* LINTED */
sa = (struct sockaddr *)cp;
switch (i) {
case RTA_DST:
dst = sa;
break;
case RTA_GATEWAY:
gate = sa;
break;
case RTA_NETMASK:
mask = sa;
break;
case RTA_IFP:
if (sa->sa_family == AF_LINK &&
((struct sockaddr_dl *)sa)->
sdl_nlen != 0)
ifp = (struct sockaddr_dl *)sa;
break;
case RTA_SRC:
src = sa;
break;
}
ADVANCE(cp, sa);
}
}
}
if (dst != NULL && mask != NULL)
mask->sa_family = dst->sa_family; /* XXX */
if (dst != NULL)
(void) printf("destination: %s\n", routename(dst));
if (mask != NULL) {
boolean_t savenflag = nflag;
nflag = B_TRUE;
(void) printf(" mask: %s\n", routename(mask));
nflag = savenflag;
}
if (gate != NULL && rtm->rtm_flags & RTF_GATEWAY)
(void) printf(" gateway: %s\n", routename(gate));
if (src != NULL && rtm->rtm_flags & RTF_SETSRC)
(void) printf(" setsrc: %s\n", routename(src));
if (ifp != NULL) {
if (verbose) {
int i;
(void) printf(" interface: %.*s index %d address ",
ifp->sdl_nlen, ifp->sdl_data, ifp->sdl_index);
for (i = ifp->sdl_nlen;
i < ifp->sdl_nlen + ifp->sdl_alen;
i++) {
(void) printf("%02x ",
ifp->sdl_data[i] & 0xFF);
}
(void) printf("\n");
} else {
(void) printf(" interface: %.*s\n",
ifp->sdl_nlen, ifp->sdl_data);
}
}
(void) printf(" flags: ");
bprintf(stdout, rtm->rtm_flags, routeflags);
#define lock(f) ((rtm->rtm_rmx.rmx_locks & RTV_ ## f) ? 'L' : ' ')
#define msec(u) (((u) + 500) / 1000) /* usec to msec */
(void) printf("\n%s\n", " recvpipe sendpipe ssthresh rtt,ms "
"rttvar,ms hopcount mtu expire");
(void) printf("%8d%c ", rtm->rtm_rmx.rmx_recvpipe, lock(RPIPE));
(void) printf("%8d%c ", rtm->rtm_rmx.rmx_sendpipe, lock(SPIPE));
(void) printf("%8d%c ", rtm->rtm_rmx.rmx_ssthresh, lock(SSTHRESH));
(void) printf("%8d%c ", msec(rtm->rtm_rmx.rmx_rtt), lock(RTT));
(void) printf("%8d%c ", msec(rtm->rtm_rmx.rmx_rttvar), lock(RTTVAR));
(void) printf("%8d%c ", rtm->rtm_rmx.rmx_hopcount, lock(HOPCOUNT));
(void) printf("%8d%c ", rtm->rtm_rmx.rmx_mtu, lock(MTU));
if (rtm->rtm_rmx.rmx_expire)
rtm->rtm_rmx.rmx_expire -= time(0);
(void) printf("%8d%c", rtm->rtm_rmx.rmx_expire, lock(EXPIRE));
#undef lock
#undef msec
#define RTA_IGN \
(RTA_DST|RTA_GATEWAY|RTA_NETMASK|RTA_IFP|RTA_IFA|RTA_BRD|RTA_SRC)
if (verbose) {
pmsg_common(rtm, msglen);
} else {
const char *sptr, *endptr;
const struct sockaddr *sa;
uint_t addrs;
/* Not verbose; just print out the exceptional cases */
if (rtm->rtm_addrs &~ RTA_IGN) {
(void) printf("\nsockaddrs: ");
bprintf(stdout, rtm->rtm_addrs, addrnames);
}
sptr = (const char *)(rtm + 1);
endptr = (const char *)rtm + msglen;
addrs = rtm->rtm_addrs;
while (addrs != 0 && sptr + sizeof (*sa) <= endptr) {
addrs &= addrs - 1;
/* LINTED */
sa = (const struct sockaddr *)sptr;
ADVANCE(sptr, sa);
}
if (addrs == 0)
pmsg_secattr(sptr, endptr - sptr, " secattr: ");
(void) putchar('\n');
}
#undef RTA_IGN
}
static void
pmsg_common(const struct rt_msghdr *rtm, size_t msglen)
{
(void) printf("\nlocks: ");
bprintf(stdout, (int)rtm->rtm_rmx.rmx_locks, metricnames);
(void) printf(" inits: ");
bprintf(stdout, (int)rtm->rtm_inits, metricnames);
pmsg_addrs((const char *)(rtm + 1), msglen - sizeof (*rtm),
rtm->rtm_addrs);
}
static void
pmsg_addrs(const char *cp, size_t msglen, uint_t addrs)
{
const struct sockaddr *sa;
const char *maxptr;
int i;
if (addrs != 0) {
(void) printf("\nsockaddrs: ");
bprintf(stdout, addrs, addrnames);
(void) putchar('\n');
maxptr = cp + msglen;
for (i = 1; i != 0 && cp + sizeof (*sa) <= maxptr; i <<= 1) {
if (i & addrs) {
/* LINTED */
sa = (const struct sockaddr *)cp;
(void) printf(" %s", routename(sa));
ADVANCE(cp, sa);
}
}
if (i != 0)
msglen = 0;
else
msglen = maxptr - cp;
}
pmsg_secattr(cp, msglen, "secattr: ");
(void) putchar('\n');
(void) fflush(stdout);
}
void
bprintf(FILE *fp, int b, char *s)
{
int i;
boolean_t gotsome = B_FALSE;
if (b == 0)
return;
while ((i = *s++) != 0) {
if (b & (1 << (i - 1))) {
if (!gotsome)
i = '<';
else
i = ',';
(void) putc(i, fp);
gotsome = B_TRUE;
for (; (i = *s) > ' '; s++)
(void) putc(i, fp);
} else {
while (*s > ' ')
s++;
}
}
if (gotsome)
(void) putc('>', fp);
}
int
keyword(const char *cp)
{
struct keytab *kt = keywords;
while (kt->kt_cp && strcmp(kt->kt_cp, cp))
kt++;
return (kt->kt_i);
}
void
sodump(su_t *su, char *which)
{
static char obuf[INET6_ADDRSTRLEN];
switch (su->sa.sa_family) {
case AF_LINK:
(void) printf("%s: link %s; ",
which, link_ntoa(&su->sdl));
break;
case AF_INET:
(void) printf("%s: inet %s; ",
which, inet_ntoa(su->sin.sin_addr));
break;
case AF_INET6:
if (inet_ntop(AF_INET6, (void *)&su->sin6.sin6_addr, obuf,
INET6_ADDRSTRLEN) != NULL) {
(void) printf("%s: inet6 %s; ", which, obuf);
break;
}
/* FALLTHROUGH */
default:
quit(gettext("Internal Error"), EINVAL);
/* NOTREACHED */
}
(void) fflush(stdout);
}
/* States */
#define VIRGIN 0
#define GOTONE 1
#define GOTTWO 2
#define RESET 3
/* Inputs */
#define DIGIT (4*0)
#define END (4*1)
#define DELIM (4*2)
#define LETTER (4*3)
void
sockaddr(char *addr, struct sockaddr *sa)
{
char *cp = (char *)sa;
int size = salen(sa);
char *cplim = cp + size;
int byte = 0, state = VIRGIN, new;
(void) memset(cp, 0, size);
cp++;
do {
if ((*addr >= '0') && (*addr <= '9')) {
new = *addr - '0';
} else if ((*addr >= 'a') && (*addr <= 'f')) {
new = *addr - 'a' + 10;
} else if ((*addr >= 'A') && (*addr <= 'F')) {
new = *addr - 'A' + 10;
} else if (*addr == 0) {
state |= END;
} else {
state |= DELIM;
}
addr++;
switch (state /* | INPUT */) {
case GOTTWO | DIGIT:
*cp++ = byte;
/* FALLTHROUGH */
case VIRGIN | DIGIT:
state = GOTONE; byte = new; continue;
case GOTONE | DIGIT:
state = GOTTWO; byte = new + (byte << 4); continue;
default: /* | DELIM */
state = VIRGIN; *cp++ = byte; byte = 0; continue;
case GOTONE | END:
case GOTTWO | END:
*cp++ = byte;
/* FALLTHROUGH */
case VIRGIN | END:
break;
}
break;
} while (cp < cplim);
}
int
salen(const struct sockaddr *sa)
{
switch (sa->sa_family) {
case AF_INET:
return (sizeof (struct sockaddr_in));
case AF_LINK:
return (sizeof (struct sockaddr_dl));
case AF_INET6:
return (sizeof (struct sockaddr_in6));
default:
return (sizeof (struct sockaddr));
}
}
void
link_addr(const char *addr, struct sockaddr_dl *sdl)
{
char *cp = sdl->sdl_data;
char *cplim = sizeof (struct sockaddr_dl) + (char *)sdl;
int byte = 0, state = VIRGIN, new;
(void) memset(sdl, 0, sizeof (struct sockaddr_dl));
sdl->sdl_family = AF_LINK;
do {
state &= ~LETTER;
if ((*addr >= '0') && (*addr <= '9')) {
new = *addr - '0';
} else if ((*addr >= 'a') && (*addr <= 'f')) {
new = *addr - 'a' + 10;
} else if ((*addr >= 'A') && (*addr <= 'F')) {
new = *addr - 'A' + 10;
} else if (*addr == 0) {
state |= END;
} else if (state == VIRGIN &&
(((*addr >= 'A') && (*addr <= 'Z')) ||
((*addr >= 'a') && (*addr <= 'z')))) {
state |= LETTER;
} else {
state |= DELIM;
}
addr++;
switch (state /* | INPUT */) {
case VIRGIN | DIGIT:
case VIRGIN | LETTER:
*cp++ = addr[-1];
continue;
case VIRGIN | DELIM:
state = RESET;
sdl->sdl_nlen = cp - sdl->sdl_data;
continue;
case GOTTWO | DIGIT:
*cp++ = byte;
/* FALLTHROUGH */
case RESET | DIGIT:
state = GOTONE;
byte = new;
continue;
case GOTONE | DIGIT:
state = GOTTWO;
byte = new + (byte << 4);
continue;
default: /* | DELIM */
state = RESET;
*cp++ = byte;
byte = 0;
continue;
case GOTONE | END:
case GOTTWO | END:
*cp++ = byte;
/* FALLTHROUGH */
case RESET | END:
break;
}
break;
} while (cp < cplim);
sdl->sdl_alen = cp - LLADDR(sdl);
}
static char hexlist[] = "0123456789abcdef";
char *
link_ntoa(const struct sockaddr_dl *sdl)
{
static char obuf[64];
char *out = obuf;
int i;
uchar_t *in = (uchar_t *)LLADDR(sdl);
uchar_t *inlim = in + sdl->sdl_alen;
boolean_t firsttime = B_TRUE;
if (sdl->sdl_nlen) {
(void) memcpy(obuf, sdl->sdl_data, sdl->sdl_nlen);
out += sdl->sdl_nlen;
if (sdl->sdl_alen)
*out++ = ':';
}
while (in < inlim) {
if (firsttime)
firsttime = B_FALSE;
else
*out++ = '.';
i = *in++;
if (i > 0xf) {
out[1] = hexlist[i & 0xf];
i >>= 4;
out[0] = hexlist[i];
out += 2;
} else {
*out++ = hexlist[i];
}
}
*out = 0;
return (obuf);
}
static mib_item_t *
mibget(int sd)
{
intmax_t buf[512 / sizeof (intmax_t)];
int flags;
int i, j, getcode;
struct strbuf ctlbuf, databuf;
struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf;
struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf;
struct T_error_ack *tea = (struct T_error_ack *)buf;
struct opthdr *req;
mib_item_t *first_item = NULL;
mib_item_t *last_item = NULL;
mib_item_t *temp;
tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
tor->OPT_offset = sizeof (struct T_optmgmt_req);
tor->OPT_length = sizeof (struct opthdr);
tor->MGMT_flags = T_CURRENT;
req = (struct opthdr *)&tor[1];
req->level = MIB2_IP; /* any MIB2_xxx value ok here */
req->name = 0;
req->len = 0;
ctlbuf.buf = (char *)buf;
ctlbuf.len = tor->OPT_length + tor->OPT_offset;
flags = 0;
if (putmsg(sd, &ctlbuf, NULL, flags) < 0) {
perror("mibget: putmsg (ctl)");
return (NULL);
}
/*
* each reply consists of a ctl part for one fixed structure
* or table, as defined in mib2.h. The format is a T_OPTMGMT_ACK,
* containing an opthdr structure. level/name identify the entry,
* len is the size of the data part of the message.
*/
req = (struct opthdr *)&toa[1];
ctlbuf.maxlen = sizeof (buf);
for (j = 1; ; j++) {
flags = 0;
getcode = getmsg(sd, &ctlbuf, NULL, &flags);
if (getcode < 0) {
perror("mibget: getmsg (ctl)");
if (verbose) {
(void) fprintf(stderr,
"# level name len\n");
i = 0;
for (last_item = first_item; last_item != NULL;
last_item = last_item->next_item) {
(void) printf("%d %4ld %5ld %ld\n",
++i, last_item->group,
last_item->mib_id,
last_item->length);
}
}
break;
}
if (getcode == 0 &&
ctlbuf.len >= sizeof (struct T_optmgmt_ack) &&
toa->PRIM_type == T_OPTMGMT_ACK &&
toa->MGMT_flags == T_SUCCESS &&
req->len == 0) {
if (verbose) {
(void) printf("mibget getmsg() %d returned EOD "
"(level %lu, name %lu)\n", j, req->level,
req->name);
}
return (first_item); /* this is EOD msg */
}
if (ctlbuf.len >= sizeof (struct T_error_ack) &&
tea->PRIM_type == T_ERROR_ACK) {
(void) fprintf(stderr, gettext("mibget %d gives "
"T_ERROR_ACK: TLI_error = 0x%lx, UNIX_error = "
"0x%lx\n"), j, tea->TLI_error, tea->UNIX_error);
errno = (tea->TLI_error == TSYSERR)
? tea->UNIX_error : EPROTO;
break;
}
if (getcode != MOREDATA ||
ctlbuf.len < sizeof (struct T_optmgmt_ack) ||
toa->PRIM_type != T_OPTMGMT_ACK ||
toa->MGMT_flags != T_SUCCESS) {
(void) printf("mibget getmsg(ctl) %d returned %d, "
"ctlbuf.len = %d, PRIM_type = %ld\n",
j, getcode, ctlbuf.len, toa->PRIM_type);
if (toa->PRIM_type == T_OPTMGMT_ACK) {
(void) printf("T_OPTMGMT_ACK: "
"MGMT_flags = 0x%lx, req->len = %ld\n",
toa->MGMT_flags, req->len);
}
errno = ENOMSG;
break;
}
temp = malloc(sizeof (mib_item_t));
if (temp == NULL) {
perror("mibget: malloc");
break;
}
if (last_item != NULL)
last_item->next_item = temp;
else
first_item = temp;
last_item = temp;
last_item->next_item = NULL;
last_item->group = req->level;
last_item->mib_id = req->name;
last_item->length = req->len;
last_item->valp = malloc(req->len);
if (verbose) {
(void) printf("msg %d: group = %4ld mib_id = %5ld "
"length = %ld\n",
j, last_item->group, last_item->mib_id,
last_item->length);
}
databuf.maxlen = last_item->length;
databuf.buf = (char *)last_item->valp;
databuf.len = 0;
flags = 0;
getcode = getmsg(sd, NULL, &databuf, &flags);
if (getcode < 0) {
perror("mibget: getmsg (data)");
break;
} else if (getcode != 0) {
(void) printf("mibget getmsg(data) returned %d, "
"databuf.maxlen = %d, databuf.len = %d\n",
getcode, databuf.maxlen, databuf.len);
break;
}
}
/*
* On error, free all the allocated mib_item_t objects.
*/
while (first_item != NULL) {
last_item = first_item;
first_item = first_item->next_item;
free(last_item);
}
return (NULL);
}
/*
* print label security attributes for gateways.
*/
static void
pmsg_secattr(const char *sptr, size_t msglen, const char *labelstr)
{
rtm_ext_t rtm_ext;
tsol_rtsecattr_t sp;
struct rtsa_s *rtsa = &sp.rtsa_attr[0];
const char *endptr;
char buf[256];
int i;
if (!is_system_labeled())
return;
endptr = sptr + msglen;
for (;;) {
if (sptr + sizeof (rtm_ext_t) + sizeof (sp) > endptr)
return;
bcopy(sptr, &rtm_ext, sizeof (rtm_ext));
sptr += sizeof (rtm_ext);
if (rtm_ext.rtmex_type == RTMEX_GATEWAY_SECATTR)
break;
sptr += rtm_ext.rtmex_len;
}
/* bail if this entry is corrupt or overruns buffer length */
if (rtm_ext.rtmex_len < sizeof (sp) ||
sptr + rtm_ext.rtmex_len > endptr)
return;
/* run up just to the end of this extension */
endptr = sptr + rtm_ext.rtmex_len;
bcopy(sptr, &sp, sizeof (sp));
sptr += sizeof (sp);
if (sptr + (sp.rtsa_cnt - 1) * sizeof (*rtsa) != endptr)
return;
for (i = 0; i < sp.rtsa_cnt; i++) {
if (i > 0) {
/* first element is part of sp initalized above */
bcopy(sptr, rtsa, sizeof (*rtsa));
sptr += sizeof (*rtsa);
}
(void) printf("\n%s%s", labelstr, rtsa_to_str(rtsa, buf,
sizeof (buf)));
}
}