routeadm.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>
#include <stropts.h>
#include <errno.h>
#include <libintl.h>
#include <locale.h>
#include <fcntl.h>
#include <signal.h>
#include <procfs.h>
static char *myname; /* copied from argv[0] */
#define RA_CONF_FILE "/etc/inet/routing.conf"
#define RA_MAX_CONF_LINE 256
#define ND_IP_FORWARDING "ip_forwarding"
#define ND_IP6_FORWARDING "ip6_forwarding"
#define ND_IP6_SENDREDIR "ip6_send_redirects"
#define ND_IP6_IGNREDIR "ip6_ignore_redirect"
#define ND_ON_STR "\0" "1" "\0"
#define ND_OFF_STR "\0" "0" "\0"
#define OPT_STRBUFSIZE 1024
#define RAD_ARGNUM 10
#define IPV4_ROUTING_DAEMON_DEF "/usr/sbin/in.routed"
#define IPV4_ROUTING_DAEMON_ARGS_DEF ""
#define IPV4_ROUTING_STOP_CMD_DEF "kill -TERM `cat /var/tmp/in.routed.pid`"
#define IPV6_ROUTING_DAEMON_DEF "/usr/lib/inet/in.ripngd"
#define IPV6_ROUTING_DAEMON_ARGS_DEF "-s"
#define IPV6_ROUTING_STOP_CMD_DEF "kill -TERM `cat /var/tmp/in.ripngd.pid`"
#define NDPD_DAEMON_DEF "/usr/lib/inet/in.ndpd"
#define NDPD_STOP_CMD_DEF "kill -TERM `cat /var/run/in.ndpd.pid`"
#define IN_ROUTED_PID "/var/run/in.routed.pid"
#define IN_RIPNGD_PID "/var/run/in.ripngd.pid"
#define IN_NDPD_PID "/var/run/in.ndpd.pid"
/*
* The rad_stop_cmd is exec-ed only if rad_pidfile is NULL, i.e.,
* default routing daemon has changed.
*/
typedef struct ra_daemon {
char **rad_argv;
char *rad_pidfile;
char *rad_stop_cmd;
} ra_daemon_t;
static int ipsock = -1;
typedef enum option_values {
} oval_t;
#define OPT2INTLSTR(oval) \
gettext("default"))))
typedef oval_t (*ra_stat_func_t)(void);
typedef void (*ra_update_func_t)(void);
/*
* A routeadm option. These options are those that are enabled or disabled
* with the -e and -d command-line options.
*/
typedef struct ra_opt {
const char *opt_name;
} raopt_t;
#define OPT_IS_FORWARDING(opt) \
/*
* A routeadm variable. These are assigned using the -s command-line
* option.
*/
typedef struct ra_var {
const char *var_name;
char *var_new; /* specified on command-line */
char *var_conf; /* Currently configured value */
char *var_def; /* The variable's default value */
} ravar_t;
char *);
static oval_t v4forw_cur(void);
static oval_t v4rout_cur(void);
static oval_t v6forw_cur(void);
static oval_t v6rout_cur(void);
static void enable_v4forw(void);
static void disable_v4forw(void);
static void enable_v4rout(void);
static void disable_v4rout(void);
static void enable_v6forw(void);
static void disable_v6forw(void);
static void enable_v6rout(void);
static void disable_v6rout(void);
static void usage(void);
static void ra_update(void);
static int ra_parseconf(void);
static int ra_parseopt(char *, int, raopt_t *);
static int ra_parsevar(char *, int, ravar_t *);
static int ra_writeconf(void);
static raopt_t *ra_str2opt(const char *);
static oval_t ra_str2oval(const char *);
static ravar_t *ra_str2var(const char *);
static char *ra_intloptname(const char *);
static int open_ipsock(void);
static int ra_ndioctl(int, char *, int);
static void ra_rundaemon(ra_daemon_t *);
static void ra_killdaemon(ra_daemon_t *);
static int ra_numv6intfs(void);
static void start_ndpd(void);
/*
* The list describing the supported options. If an option is added here,
* remember to also add support for the human readable description of the
* option to the ra_intloptname() function.
*/
{ "ipv4-forwarding",
{ "ipv4-routing",
{ "ipv6-forwarding",
{ "ipv6-routing",
{ NULL,
};
char *v_opt[] = {
#define IPV4_ROUTING_DAEMON 0
"ipv4-routing-daemon",
#define IPV4_ROUTING_DAEMON_ARGS 1
"ipv4-routing-daemon-args",
#define IPV4_ROUTING_STOP_CMD 2
"ipv4-routing-stop-cmd",
#define IPV6_ROUTING_DAEMON 3
"ipv6-routing-daemon",
#define IPV6_ROUTING_DAEMON_ARGS 4
"ipv6-routing-daemon-args",
#define IPV6_ROUTING_STOP_CMD 5
"ipv6-routing-stop-cmd",
};
/*
* the list describing the supported routeadm variables.
*/
};
static void
usage(void)
{
"usage: %1$s [-p] [-R <root-dir>]\n"
" %1$s [-e <option>] [-d <option>] [-r <option>]\n"
" [-s <var>=<val>] [-R <root-dir>]\n"
" %1$s -u\n\n"
" <option> is one of:\n"
" ipv4-forwarding\n"
" ipv4-routing\n"
" ipv6-forwarding\n"
" ipv6-routing\n\n"
" <var> is one of:\n"
" ipv4-routing-daemon\n"
" ipv4-routing-daemon-args\n"
" ipv4-routing-stop-cmd\n"
" ipv6-routing-daemon\n"
" ipv6-routing-daemon-args\n"
" ipv6-routing-stop-cmd\n"), myname);
}
int
{
int fdnull;
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
switch (opt) {
case 'b':
/*
* This is a project-private option that allows the
* boot script to give us revert values for all of
* the options. These values will be used if the
* user hasn't set the options, or has reverted
* them using the -r flag. We save these values in
* the config file so that we can fall back to
* these when the admin uses "-r <option>".
*/
break;
case 'd':
case 'e':
case 'r':
/*
* If -b was specified, then the
* values given are those we will revert to.
*/
if (booting)
else
switch (opt) {
case 'd':
*val = OPT_DISABLED;
break;
case 'e':
*val = OPT_ENABLED;
break;
case 'r':
break;
}
if (opt != 'r') {
usage();
return (EXIT_FAILURE);
}
} else {
"%1$s: invalid option: %2$s\n"),
usage();
return (EXIT_FAILURE);
}
break;
case 'F':
/*
* This is a project-private option that allows the
* net-loopback method to configure IP forwarding
* before network interfaces are configured in
* net-physical. This allows administrators to
* configure interface-specific IP forwarding
* "router" or "-router" ifconfig commands.
*/
break;
case 'p':
break;
case 'R':
"%1$s: failed to chroot to %2$s: %3$s\n"),
return (EXIT_FAILURE);
}
break;
case 's':
while (*options != '\0') {
usage();
return (EXIT_FAILURE);
}
if (opt_index == -1) {
"%1$s: invalid variable: %2$s\n"),
usage();
return (EXIT_FAILURE);
}
"unable to allocate memory.\n"),
myname);
return (EXIT_FAILURE);
}
}
break;
case 'u':
break;
default:
usage();
return (EXIT_FAILURE);
}
}
/* There shouldn't be any extra args. */
usage();
return (EXIT_FAILURE);
}
if (booting) {
}
"used with any of -deru\n"), myname);
usage();
return (EXIT_FAILURE);
}
if (ra_parseconf() != 0)
return (EXIT_FAILURE);
if (modify)
status = ra_writeconf();
/*
* In order to update the running system or print a report, the
* daemon structures must reflect the current state of the
* daemon configuration variables.
*/
return (EXIT_FAILURE);
}
if (update)
ra_update();
}
/*
* Initialize the daemon structure pointed to by rad with the variables
* passed in.
*/
static boolean_t
{
int i = 1;
char *args;
/*
* We only use the pidfile if the admin hasn't altered the name
* of the daemon or its kill command.
*/
} else {
}
return (B_FALSE);
}
return (B_FALSE);
}
return (B_FALSE);
}
}
i++;
}
return (B_TRUE);
}
/* Apply currently configured values to the running system. */
static void
ra_update(void)
{
int i;
/*
* If we're only updating forwarding settings, skip all
* options that aren't related to IP forwarding.
*/
continue;
/*
* Likewise, if we're booting (the net-init boot script has
* specified -b on the command line) and we're updating the
* rest of the options, skip the forwarding options we set
* in the network boot script.
*/
continue;
case OPT_ENABLED:
(ra_opts[i].opt_enable)();
break;
case OPT_DISABLED:
(ra_opts[i].opt_disable)();
break;
case OPT_DEFAULT:
case OPT_ENABLED:
(ra_opts[i].opt_enable)();
break;
case OPT_DISABLED:
(ra_opts[i].opt_disable)();
break;
}
}
}
}
/*
* Print the configured values to stdout. If parseable is set, the output
* is machine readable. The parseable output is of the form:
* <varname> persistent=<opt_conf> default=<opt_rev> current=<opt_getcur()>
* for options, and is of the form:
* <varname> persistent=<var_conf> default=<var_def>
* for variables.
*/
static void
{
int i;
char confstr[OPT_STRBUFSIZE];
if (parseable) {
(void) printf("%s persistent=%s default=%s "
}
(void) printf("%s persistent=\"%s\" "
"default=\"%s\" \n",
}
return;
}
" Configuration Current Current\n"
" Option Configuration System State\n"
"---------------------------------------------------------------"
"\n"));
"%s (%s)",
} else {
}
}
(void) printf("\n");
}
}
/*
* Parse the configuration file and fill the ra_opts array with opt_conf
* and opt_rev values, and the ra_vars array with opt_conf values.
*/
static int
ra_parseconf(void)
{
char line[RA_MAX_CONF_LINE];
/*
* There's no config file, so we need to create one. The
* system doesn't ship with one, so this is not an error
* condition.
*
* If we're being called from the net-loopback boot script
* (forwarding_only is set), then there isn't anything for
* us to do in the absense of a configuration file. In
* this case, we would only set user-configured forwarding
* settings. If the routing.conf file doesn't exist, then
* we just exit since the user obviously hasn't configured
* anything.
*/
if (forwarding_only)
return (ra_writeconf());
}
/* Skip leading whitespace */
cp++;
/* Skip comment lines and empty lines */
continue;
/*
* Anything else must be of the form:
* <option> <value> <default_value>
*/
gettext("%1$s: %2$s: invalid entry on line %3$d\n"),
continue;
}
return (-1);
}
return (-1);
}
} else {
gettext("%1$s: %2$s: invalid option name on "
"line %3$d\n"),
continue;
}
}
/*
* We call ra_writeconf() here in case there were missing entries
* in the file. If all entries have been read, ra_writeconf() will
* return without having written anything.
*/
return (ra_writeconf());
}
static int
{
gettext("%1$s: %2$s: WARNING, option defined on "
"multiple lines, ignoring line %3$d\n"),
return (0);
}
gettext("%1$s: %2$s: missing value on line %3$d\n"),
return (0);
}
gettext("%1$s: %2$s: invalid option "
"value on line %3$d\n"),
return (0);
}
gettext("%1$s: %2$s: missing revert "
"value on line %3$d\n"),
return (0);
}
gettext("%1$s: %2$s: invalid revert "
"value on line %3$d\n"),
return (0);
}
return (0);
}
static int
{
gettext("%1$s: %2$s: WARNING, variable defined on "
"multiple lines, ignoring line %3$d\n"),
return (0);
}
/*
* This isn't an error condition, it simply means that the
* variable has no value.
*/
return (0);
}
"unable to allocate memory\n"), myname);
return (-1);
}
return (0);
}
/*
* Write options to the configuration file. The options are gathered from
* the ra_opts[] and ra_vars[] arrays.
*
* The format of the file is:
* - comment lines start with '#'
* - other lines are written in the form "<opt_name> <opt_new> <opt_newrev>"
*/
static int
ra_writeconf(void)
{
int fd, i;
/*
* At this point, the *_conf members are the current configuration
* in the /etc/inet/routing.conf file. The *_new members are those
* that were passed in on the command line to override the current
* configuration.
*/
/* Make sure we don't needlessly overwrite the file. */
/* there was no configuration for this option */
}
/* the new configuration overrides the existing one */
}
/* a new revert value was passed in */
}
}
/* the variable wasn't in the configuration file */
}
/* a new variable value was passed in */
}
}
if (!changed)
return (0);
gettext("%1$s: failed to open %2$s: %3$s\n"),
return (-1);
}
gettext("%1$s: failed to open stream for %2$s: %3$s\n"),
return (-1);
}
(void) fputs(
"#\n"
"# routing.conf\n"
"#\n"
"# Parameters for IP forwarding and routing.\n"
"# Do not edit this file by hand -- use routeadm(1m) instead.\n"
"#\n",
fp);
/*
* Option entries are of the form:
* <name> <val> <revert-val>
*/
}
/*
* Variable entries are of the form:
* <name> =<value>
*/
}
return (0);
}
/*
* return the ra_opts array element whose opt_name matches the string
* passed in as an argument.
*/
static raopt_t *
ra_str2opt(const char *optnamestr)
{
int i;
break;
}
return (NULL);
else
return (&ra_opts[i]);
}
/* Convert a string to an option value. */
static oval_t
ra_str2oval(const char *valstr)
{
return (OPT_ENABLED);
return (OPT_DISABLED);
return (OPT_DEFAULT);
return (OPT_INVALID);
}
static ravar_t *
ra_str2var(const char *varnamestr)
{
int i;
break;
}
return (NULL);
else
return (&ra_vars[i]);
}
/*
* Given an option name, this function provides an internationalized, human
* readable version of the option name.
*/
static char *
ra_intloptname(const char *optname)
{
return (gettext("IPv4 forwarding"));
return (gettext("IPv4 routing"));
return (gettext("IPv6 forwarding"));
return (gettext("IPv6 routing"));
return (gettext("IPv4 routing daemon"));
return (gettext("IPv4 routing daemon args"));
return (gettext("IPv4 routing daemon stop"));
return (gettext("IPv6 routing daemon"));
return (gettext("IPv6 routing daemon args"));
return (gettext("IPv6 routing daemon stop"));
/*
* If we get here, there's a bug and someone should trip over this
* NULL pointer.
*/
return (NULL);
}
static int
open_ipsock(void)
{
gettext("%1$s: unable to open %2$s: %3$s\n"),
}
return (ipsock);
}
static int
{
if (open_ipsock() == -1)
return (-1);
return (-1);
return (0);
}
/*
* Returns the process id of the specified command if it's running, -1 if
* it's not.
*/
static pid_t
{
char procpath[MAXPATHLEN];
int procfd;
return (-1);
return (-1);
return (-1);
/* Make sure the process we're interested in is still running. */
return (-1);
return (-1);
}
return (-1);
}
return (pid);
}
/*
* Fork and exec a daemon, and wait until it has daemonized to return. We
* first attempt to kill it if it's already running, as the command-line
* arguments may have changed.
*/
static void
{
gettext("%1$s: unable to fork %2$s: %3$s\n"),
} else if (daemon_pid == 0) {
/* We're the child, execute the daemon. */
gettext("%1$s: unable to execute %2$s: %3$s\n"),
}
} else {
/* Wait for the child to daemonize or terminate. */
}
}
/*
* If the daemon has a pidfile, use the pid to kill the targeted process.
* Otherwise, use the daemon's configured stop command.
*/
static void
{
/*
* rad_pidfile is cleared out if the user sets a non-default
* routing daemon
*/
return;
"%1$s: unable to kill %2$s: %3$s\n"), myname,
}
} else {
if (!booting) {
"%2$s failed: %3$s\n"),
}
}
}
/*
* Return the number of IPv6 addresses configured. This answers the
* generic question, "is IPv6 configured?". We only start in.ndpd if IPv6
* is configured, and we also only enable IPv6 routing if IPv6 is enabled.
*/
static int
ra_numv6intfs(void)
{
static int num = -1;
if (num != -1)
return (num);
if (open_ipsock() == -1)
return (0);
lifn.lifn_flags = 0;
return (0);
}
/* Run in.ndpd */
static void
start_ndpd(void)
{
}
/* Is ip_forwarding turned on? */
static oval_t
v4forw_cur(void)
{
char ndbuf[] = ND_IP_FORWARDING;
return (OPT_DISABLED);
}
/* Is in.routed running? */
static oval_t
v4rout_cur(void)
{
/*
* routeadm cannot really know the status of a user-configured
* routing daemon. We clear the rad_pidfile field of the daemon
* structure when the user configures the daemon.
*/
return (OPT_UNKNOWN);
}
/* Is ip6_forwarding turned on? */
static oval_t
v6forw_cur(void)
{
char ndbuf[] = ND_IP6_FORWARDING;
return (OPT_DISABLED);
}
/* Is in.ripngd running? */
static oval_t
v6rout_cur(void)
{
/*
* routeadm cannot really know the status of a user-configured
* routing daemon. We clear the rad_pidfile field of the daemon
* structure when the user configures the daemon.
*/
return (OPT_UNKNOWN);
}
static void
enable_v4forw(void)
{
}
static void
disable_v4forw(void)
{
}
static void
enable_v4rout(void)
{
" Use -s to set the ipv4-routing-daemon variable, \n"
" or use -d to disable ipv4-routing.\n"), myname,
ra_intloptname("ipv4-routing-daemon"));
} else {
ra_rundaemon(&v4d);
}
}
static void
disable_v4rout(void)
{
ra_killdaemon(&v4d);
}
/* Turn on ip6_forwarding, ip6_ignore_redirect, and ip6_send_redirects. */
static void
enable_v6forw(void)
{
sizeof (nd_ip6_sendredir_on));
}
/*
* in.ripngd is tied to IPv6 forwarding due to a limitation in its
* implementation. It will propagate routes blindly without checking if
* forwarding is enabled on the interfaces it's using. Until that's fixed,
* make sure in.ripngd doesn't run if IPv6 forwarding isn't enabled.
*/
static void
disable_v6forw(void)
{
gettext("%1$s: unable to kill %2$s: %3$s\n"),
}
sizeof (nd_ip6_sendredir_off));
}
/*
* We only enable IPv6 routing if there is at least one IPv6 interface
* configured.
*
* If in.ndpd isn't already running, then we start it here because
* in.ripngd depends on having routes based on the prefixes configured by
* in.ndpd. We only start in.ripngd if IPv6 forwarding is enabled. This
* is due to a giant gap in in.ripngd's design which causes in.ripngd to
* propagate routes on all interfaces regardless of their forwarding
* status. If that's fixed, then we can start in.ripngd regardless of the
* global IPv6 forwarding status.
*/
static void
enable_v6rout(void)
{
if (ra_numv6intfs() == 0)
return;
start_ndpd();
if (v6forw_cur() != OPT_ENABLED)
return;
sizeof (nd_ip6_ignredir_on));
" Use -s to set the ipv6-routing-daemon variable, \n"
" or use -d to disable ipv6-routing.\n"), myname,
ra_intloptname("ipv6-routing-daemon"));
} else {
ra_rundaemon(&v6d);
}
}
static void
disable_v6rout(void)
{
/*
* We always start in.ndpd if there is an IPv6 interface
* configured, regardless of the status of IPv6 routing.
*/
if (ra_numv6intfs() > 0)
start_ndpd();
sizeof (nd_ip6_ignredir_off));
ra_killdaemon(&v6d);
}