view usr/src/cmd/cmd-inet/usr.sbin/ndd.c @ 12016:0248e987199b

PSARC 2009/306 Brussels II - ipadm and libipadm PSARC 2010/080 Brussels II addendum 6827318 Brussels Phase II aka ipadm(1m) 6731945 need BSD getifaddrs() API 6909065 explicitly disallow non-contiguous netmasks in the next minor release 6853922 ifconfig dumps core when ether address is non-hexadecimal. 6815806 ipReasmTimeout value should be variable 6567083 nd_getset has some dead and confusing code. 6884466 remove unused tcp/sctp ndd tunables 6928813 Comments at odds with default value of tcp_time_wait_interval 6236982 ifconfig usesrc lets adapter use itself as source address 6936855 modifying the ip6_strict_src_multihoming to non-zero value will unbind V4 IREs
author Girish Moodalbail <Girish.Moodalbail@Sun.COM>
date Fri, 26 Mar 2010 17:53:11 -0400
parents 90d41bc81368
children 34b1540309cc
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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
 * or http://www.opensolaris.org/os/licensing.
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 1990  Mentat Inc.
 * ndd.c 2.1, last change 11/14/90
 */

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <stropts.h>
#include <inet/tunables.h>
#include <inet/nd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <libdllink.h>
#include <libintl.h>
#include <libipadm.h>

static boolean_t do_getset(int fd, int cmd, char *buf, int buf_len);
static int	get_value(char *msg, char *buf, int buf_len);
static void	name_print(char *buf);
static void	getset_interactive(int fd);
static int	open_device(void);
static char	*errmsg(int err);
static void	fatal(char *fmt, ...);
static void	printe(boolean_t print_errno, char *fmt, ...);

static char	modpath[128];	/* path to module */
static char	gbuf[65536];	/* need large buffer to retrieve all names */
static char	usage_str[] =	"usage: ndd -set device_name name value\n"
				"       ndd [-get] device_name name [name ...]";

/*
 * Maps old ndd_name to the new ipadm_name. Any ndd property that is moved to
 * libipadm should have an entry here to ensure backward compatibility
 */
typedef struct ndd2ipadm_map {
	char	*ndd_name;
	char	*ipadm_name;
	uint_t	ipadm_proto;
	uint_t	ipadm_flags;
	uint_t	ndd_perm;
} ndd2ipadm_map_t;

static ndd2ipadm_map_t map[] = {
	{ "ip_def_ttl",			"ttl",		MOD_PROTO_IPV4, 0, 0 },
	{ "ip6_def_hops",		"hoplimit",	MOD_PROTO_IPV6, 0, 0 },
	{ "ip_forwarding",		"forwarding",	MOD_PROTO_IPV4, 0, 0 },
	{ "ip6_forwarding",		"forwarding",	MOD_PROTO_IPV6, 0, 0 },
	{ "icmp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_RAWIP, 0, 0 },
	{ "icmp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_RAWIP, 0, 0 },
	{ "tcp_ecn_permitted",		"ecn",		MOD_PROTO_TCP, 0, 0 },
	{ "tcp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_TCP,
	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
	{ "tcp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_TCP,
	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
	{ "tcp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_TCP,
	    0, MOD_PROP_PERM_READ },
	{ "tcp_largest_anon_port",	"largest_anon_port",	MOD_PROTO_TCP,
	    0, 0 },
	{ "tcp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_TCP, 0, 0 },
	{ "tcp_sack_permitted",		"sack",		MOD_PROTO_TCP, 0, 0 },
	{ "tcp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_TCP, 0, 0 },
	{ "tcp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_TCP,
	    0, 0 },
	{ "tcp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_TCP,
	    0, 0 },
	{ "udp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_UDP,
	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
	{ "udp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_UDP,
	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
	{ "udp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_UDP,
	    0, MOD_PROP_PERM_READ },
	{ "udp_largest_anon_port",	"largest_anon_port",    MOD_PROTO_UDP,
	    0, 0 },
	{ "udp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_UDP, 0, 0 },
	{ "udp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_UDP, 0, 0 },
	{ "udp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_UDP,
	    0, 0 },
	{ "udp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_UDP,
	    0, 0 },
	{ "sctp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_SCTP,
	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
	{ "sctp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_SCTP,
	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
	{ "sctp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_SCTP,
	    0, MOD_PROP_PERM_READ },
	{ "sctp_largest_anon_port",	"largest_anon_port",	MOD_PROTO_SCTP,
	    0, 0 },
	{ "sctp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_SCTP, 0, 0 },
	{ "sctp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_SCTP, 0, 0 },
	{ "sctp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_SCTP,
	    0, 0 },
	{ "sctp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_SCTP,
	    0, 0 },
	{ NULL, NULL, 0, 0, 0 }
};

static uint_t
ndd_str2proto(const char *protostr)
{
	if (strcmp(protostr, "tcp") == 0 ||
	    strcmp(protostr, "tcp6") == 0) {
		return (MOD_PROTO_TCP);
	} else if (strcmp(protostr, "udp") == 0 ||
	    strcmp(protostr, "udp6") == 0) {
		return (MOD_PROTO_UDP);
	} else if (strcmp(protostr, "ip") == 0 ||
	    strcmp(protostr, "ip6") == 0 ||
	    strcmp(protostr, "arp") == 0) {
		return (MOD_PROTO_IP);
	} else if (strcmp(protostr, "icmp") == 0 ||
	    strcmp(protostr, "icmp6") == 0) {
		return (MOD_PROTO_RAWIP);
	} else if (strcmp(protostr, "sctp") == 0 ||
	    strcmp(protostr, "sctp6") == 0) {
		return (MOD_PROTO_SCTP);
	}
	return (MOD_PROTO_NONE);
}

static char *
ndd_perm2str(uint_t perm)
{
	switch (perm) {
	case MOD_PROP_PERM_READ:
		return ("read only");
	case MOD_PROP_PERM_WRITE:
		return ("write only");
	case MOD_PROP_PERM_RW:
		return ("read and write");
	}

	return (NULL);
}

/*
 * This function converts any new property names to old ndd name by consulting
 * ndd2ipadm_map_t. This is done to preserve backward compatibility.
 */
static void
print_ipadm2ndd(char *oldbuf, uint_t obufsize)
{
	ndd2ipadm_map_t	*nimap;
	char		*pname, *rwtag, *protostr;
	uint_t		proto, perm;
	boolean_t	matched;

	pname = oldbuf;
	while (pname[0] && pname < (oldbuf + obufsize - 1)) {
		for (protostr = pname; !isspace(*protostr); protostr++)
			;
		*protostr++ = '\0';
		/* protostr now points to protocol */

		for (rwtag = protostr; !isspace(*rwtag); rwtag++)
			;
		*rwtag++ = '\0';
		/* rwtag now points to permissions */

		proto = atoi(protostr);
		perm = atoi(rwtag);
		matched = B_FALSE;
		for (nimap = map; nimap->ndd_name != NULL; nimap++) {
			if (strcmp(pname, nimap->ipadm_name) != 0 ||
			    !(nimap->ipadm_proto & proto))
				continue;

			matched = B_TRUE;
			if (nimap->ndd_perm != 0)
				perm = nimap->ndd_perm;
			(void) printf("%-30s (%s)\n", nimap->ndd_name,
			    ndd_perm2str(perm));
		}
		if (!matched)
			(void) printf("%-30s (%s)\n", pname,
			    ndd_perm2str(perm));
		for (pname = rwtag; *pname++; )
			;
	}
}

/*
 * get/set the value for a given property by calling into libipadm. The
 * IPH_LEGACY flag is used by libipadm for special handling. For some
 * properties, libipadm.so displays strings (for e.g., on/off,
 * never/passive/active, et al) instead of numerals. However ndd(1M) always
 * printed numberals. This flag will help in avoiding printing strings.
 */
static boolean_t
do_ipadm_getset(int cmd, char *buf, int buflen)
{
	ndd2ipadm_map_t	*nimap;
	ipadm_handle_t	iph = NULL;
	ipadm_status_t	status;
	char		*mod;
	uint_t		proto, perm = 0, flags = 0;
	char		*pname, *pvalp;
	int		i;

	if ((mod = strrchr(modpath, '/')) == NULL)
		mod = modpath;
	else
		++mod;
	if ((proto = ndd_str2proto(mod)) == MOD_PROTO_NONE)
		return (B_FALSE);

	if ((status = ipadm_open(&iph, IPH_LEGACY)) != IPADM_SUCCESS)
		goto fail;

	pname = buf;
	for (nimap = map; nimap->ndd_name != NULL; nimap++) {
		if (strcmp(pname, nimap->ndd_name) == 0)
			break;
	}
	if (nimap->ndd_name != NULL) {
		pname = nimap->ipadm_name;
		proto = nimap->ipadm_proto;
		flags = nimap->ipadm_flags;
		perm = nimap->ndd_perm;
	}
	if (cmd == ND_GET) {
		char		propval[MAXPROPVALLEN], allprop[64536];
		uint_t		pvalsz;
		sa_family_t	af = AF_UNSPEC;
		int		err;

		if (perm == MOD_PROP_PERM_WRITE)
			fatal("operation failed: Permission denied");

		if (strcmp(pname, "?") == 0) {
			pvalp = allprop;
			pvalsz = sizeof (allprop);
		} else {
			pvalp = propval;
			pvalsz = sizeof (propval);
		}

		status = ipadm_get_prop(iph, pname, pvalp, &pvalsz, proto,
		    IPADM_OPT_ACTIVE);
		if (status != IPADM_SUCCESS)
			goto fail;

		if (strcmp(pname, "?") == 0) {
			(void) print_ipadm2ndd(pvalp, pvalsz);
		} else {
			char *tmp = pvalp;

			/*
			 * For backward compatibility if there are multiple
			 * values print each value in it's own line.
			 */
			while (*tmp != '\0') {
				if (*tmp == ',')
					*tmp = '\n';
				tmp++;
			}
			(void) printf("%s\n", pvalp);
		}
		(void) fflush(stdout);
	} else {
		if (perm == MOD_PROP_PERM_READ)
			fatal("operation failed: Permission denied");

		/* walk past the property name to find the property value */
		for (i = 0; buf[i] != '\0'; i++)
			;

		pvalp = &buf[++i];
		status = ipadm_set_prop(iph, pname, pvalp, proto,
		    flags|IPADM_OPT_ACTIVE);
	}
fail:
	ipadm_close(iph);
	if (status != IPADM_SUCCESS)
		fatal("operation failed: %s", ipadm_status2str(status));
	return (B_TRUE);
}

/*
 * gldv3_warning() catches the case of /sbin/ndd abuse to administer
 * ethernet/MII props. Note that /sbin/ndd has not been abused
 * for administration of other datalink types, which makes it permissible
 * to test for support of the flowctrl property.
 */
static void
gldv3_warning(char *module)
{
	datalink_id_t	linkid;
	dladm_status_t	status;
	char		buf[DLADM_PROP_VAL_MAX], *cp;
	uint_t		cnt = 1;
	char		*link;
	dladm_handle_t	handle;

	link = strrchr(module, '/');
	if (link == NULL)
		return;

	if (dladm_open(&handle) != DLADM_STATUS_OK)
		return;

	status = dladm_name2info(handle, ++link, &linkid, NULL, NULL, NULL);
	if (status == DLADM_STATUS_OK) {
		cp = buf;
		status = dladm_get_linkprop(handle, linkid,
		    DLADM_PROP_VAL_CURRENT, "flowctrl", &cp, &cnt);
		if (status == DLADM_STATUS_OK) {
			(void) fprintf(stderr, gettext(
			    "WARNING: The ndd commands for datalink "
			    "administration are obsolete and may be "
			    "removed in a future release of Solaris. "
			    "Use dladm(1M) to manage datalink tunables.\n"));
		}
	}
	dladm_close(handle);
}

/* ARGSUSED */
int
main(int argc, char **argv)
{
	char	*cp, *value, *mod;
	int	cmd;
	int	fd = 0;

	if (!(cp = *++argv)) {
		while ((fd = open_device()) != -1) {
			getset_interactive(fd);
			(void) close(fd);
		}
		return (EXIT_SUCCESS);
	}

	cmd = ND_GET;
	if (cp[0] == '-') {
		if (strncmp(&cp[1], "set", 3) == 0)
			cmd = ND_SET;
		else if (strncmp(&cp[1], "get", 3) != 0)
			fatal(usage_str);
		if (!(cp = *++argv))
			fatal(usage_str);
	}

	gldv3_warning(cp);

	mod = strrchr(cp, '/');
	if (mod != NULL)
		mod++;
	else
		mod = cp;

	if (ndd_str2proto(mod) == MOD_PROTO_NONE) {
		if ((fd = open(cp, O_RDWR)) == -1)
			fatal("open of %s failed: %s", cp, errmsg(errno));
		if (!isastream(fd))
			fatal("%s is not a streams device", cp);
	}

	(void) strlcpy(modpath, cp, sizeof (modpath));
	if (!(cp = *++argv)) {
		getset_interactive(fd);
		(void) close(fd);
		return (EXIT_SUCCESS);
	}

	if (cmd == ND_SET) {
		if (!(value = *++argv))
			fatal(usage_str);
		(void) snprintf(gbuf, sizeof (gbuf), "%s%c%s%c", cp, '\0',
		    value, '\0');
		if (!do_getset(fd, cmd, gbuf, sizeof (gbuf)))
			return (EXIT_FAILURE);
	} else {
		do {
			(void) memset(gbuf, '\0', sizeof (gbuf));
			(void) strlcpy(gbuf, cp, sizeof (gbuf));
			if (!do_getset(fd, cmd, gbuf, sizeof (gbuf)))
				return (EXIT_FAILURE);
			if (cp = *++argv)
				(void) putchar('\n');
		} while (cp);
	}

	(void) close(fd);
	return (EXIT_SUCCESS);
}

static void
name_print(char *buf)
{
	char *cp, *rwtag;

	for (cp = buf; cp[0]; ) {
		for (rwtag = cp; !isspace(*rwtag); rwtag++)
			;
		*rwtag++ = '\0';
		while (isspace(*rwtag))
			rwtag++;
		(void) printf("%-30s%s\n", cp, rwtag);
		for (cp = rwtag; *cp++; )
			;
	}
}

/*
 * This function is vile, but it's better here than in the kernel.
 */
static boolean_t
is_obsolete(const char *param)
{
	if (strcmp(param, "ip_enable_group_ifs") == 0 ||
	    strcmp(param, "ifgrp_status") == 0) {
		(void) fprintf(stderr, "The \"%s\" tunable has been superseded "
		    "by IP Multipathing.\nPlease see the IP Network "
		    "Multipathing Administration Guide for details.\n", param);
		return (B_TRUE);
	}
	return (B_FALSE);
}

static boolean_t
do_getset(int fd, int cmd, char *buf, int buf_len)
{
	char	*cp;
	struct strioctl	stri;
	boolean_t	is_name_get;

	if (is_obsolete(buf))
		return (B_TRUE);

	/*
	 * See if libipadm can handle this request, i.e., properties on
	 * following modules arp, ip, ipv4, ipv6, tcp, udp and sctp
	 */
	if (do_ipadm_getset(cmd, buf, buf_len))
		return (B_TRUE);

	stri.ic_cmd = cmd;
	stri.ic_timout = 0;
	stri.ic_len = buf_len;
	stri.ic_dp = buf;
	is_name_get = stri.ic_cmd == ND_GET && buf[0] == '?' && buf[1] == '\0';

	if (ioctl(fd, I_STR, &stri) == -1) {
		if (errno == ENOENT)
			(void) printf("name is non-existent for this module\n"
			    "for a list of valid names, use name '?'\n");
		else
			(void) printf("operation failed: %s\n", errmsg(errno));
		return (B_FALSE);
	}
	if (is_name_get)
		name_print(buf);
	else if (stri.ic_cmd == ND_GET) {
		for (cp = buf; *cp != '\0'; cp += strlen(cp) + 1)
			(void) puts(cp);
	}
	(void) fflush(stdout);
	return (B_TRUE);
}

static int
get_value(char *msg, char *buf, int buf_len)
{
	int	len;

	(void) printf("%s", msg);
	(void) fflush(stdout);

	buf[buf_len-1] = '\0';
	if (fgets(buf, buf_len-1, stdin) == NULL)
		exit(EXIT_SUCCESS);
	len = strlen(buf);
	if (buf[len-1] == '\n')
		buf[len - 1] = '\0';
	else
		len++;
	return (len);
}

static void
getset_interactive(int fd)
{
	int	cmd;
	char	*cp;
	int	len, buf_len;
	char	len_buf[10];

	for (;;) {
		(void) memset(gbuf, '\0', sizeof (gbuf));
		len = get_value("name to get/set ? ", gbuf, sizeof (gbuf));
		if (len == 1 || (gbuf[0] == 'q' && gbuf[1] == '\0'))
			return;
		for (cp = gbuf; cp < &gbuf[len]; cp++) {
			if (isspace(*cp))
				*cp = '\0';
		}
		cmd = ND_GET;
		if (gbuf[0] != '?' &&
		    get_value("value ? ", &gbuf[len], sizeof (gbuf) - len) > 1)
			cmd = ND_SET;
		if (cmd == ND_GET && gbuf[0] != '?' &&
		    get_value("length ? ", len_buf, sizeof (len_buf)) > 1) {
			if (!isdigit(len_buf[0])) {
				(void) printf("invalid length\n");
				continue;
			}
			buf_len = atoi(len_buf);
		} else
			buf_len = sizeof (gbuf);
		(void) do_getset(fd, cmd, gbuf, buf_len);
	}
}

static void
printe(boolean_t print_errno, char *fmt, ...)
{
	va_list	ap;
	int error = errno;

	va_start(ap, fmt);
	(void) printf("*ERROR* ");
	(void) vprintf(fmt, ap);
	va_end(ap);

	if (print_errno)
		(void) printf(": %s\n", errmsg(error));
	else
		(void) printf("\n");
}

static int
open_device()
{
	int	fd, len;
	char	*mod;

	for (;;) {
		len = get_value("module to query ? ", modpath,
		    sizeof (modpath));
		if (len <= 1 ||
		    (len == 2 && (modpath[0] == 'q' || modpath[0] == 'Q')))
			return (-1);

		mod = strrchr(modpath, '/');
		if (mod != NULL)
			mod++;
		else
			mod = modpath;
		if (ndd_str2proto(mod) == MOD_PROTO_NONE) {
			if ((fd = open(modpath, O_RDWR)) == -1) {
				printe(B_TRUE, "open of %s failed", modpath);
				continue;
			}
		} else {
			return (0);
		}

		gldv3_warning(modpath);

		if (isastream(fd))
			return (fd);

		(void) close(fd);
		printe(B_FALSE, "%s is not a streams device", modpath);
	}
}

static void
fatal(char *fmt, ...)
{
	va_list	ap;

	va_start(ap, fmt);
	(void) vfprintf(stderr, fmt, ap);
	va_end(ap);
	(void) fprintf(stderr, "\n");

	exit(EXIT_FAILURE);
}

static char *
errmsg(int error)
{
	char *msg = strerror(error);

	return (msg != NULL ? msg : "unknown error");
}