1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24/* Copyright (c) 1990 Mentat Inc. */
25
26#include <assert.h>
27#include <stdio.h>
28#include <errno.h>
29#include <ctype.h>
30#include <stdarg.h>
31#include <fcntl.h>
32#include <unistd.h>
33#include <sys/types.h>
34#include <stropts.h>
35#include <inet/tunables.h>
36#include <inet/nd.h>
37#include <string.h>
38#include <strings.h>
39#include <stdlib.h>
40#include <libdllink.h>
41#include <libintl.h>
42#include <libipadm.h>
43
44static boolean_t do_getset(int fd, int cmd, char *buf, int buf_len);
45static int	get_value(char *msg, char *buf, int buf_len);
46static void	name_print(char *buf);
47static void	getset_interactive(int fd);
48static int	open_device(void);
49static char	*errmsg(int err);
50static void	fatal(char *fmt, ...);
51static void	printe(boolean_t print_errno, char *fmt, ...);
52
53static char	modpath[128];	/* path to module */
54static char	gbuf[65536];	/* need large buffer to retrieve all names */
55static char	usage_str[] =	"usage: ndd -set device_name name value\n"
56				"       ndd [-get] device_name name [name ...]";
57
58/*
59 * Maps old ndd_name to the new ipadm_name. Any ndd property that is moved to
60 * libipadm should have an entry here to ensure backward compatibility
61 */
62typedef struct ndd2ipadm_map {
63	char	*ndd_name;
64	char	*ipadm_name;
65	uint_t	ipadm_proto;
66	uint_t	ipadm_flags;
67	uint_t	ndd_perm;
68} ndd2ipadm_map_t;
69
70static ndd2ipadm_map_t map[] = {
71	{ "ip_def_ttl",			"ttl",		MOD_PROTO_IPV4, 0, 0 },
72	{ "ip6_def_hops",		"hoplimit",	MOD_PROTO_IPV6, 0, 0 },
73	{ "ip_forwarding",		"forwarding",	MOD_PROTO_IPV4, 0, 0 },
74	{ "ip6_forwarding",		"forwarding",	MOD_PROTO_IPV6, 0, 0 },
75	{ "icmp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_RAWIP, 0, 0 },
76	{ "icmp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_RAWIP, 0, 0 },
77	{ "tcp_ecn_permitted",		"ecn",		MOD_PROTO_TCP, 0, 0 },
78	{ "tcp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_TCP,
79	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
80	{ "tcp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_TCP,
81	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
82	{ "tcp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_TCP,
83	    0, MOD_PROP_PERM_READ },
84	{ "tcp_largest_anon_port",	"largest_anon_port",	MOD_PROTO_TCP,
85	    0, 0 },
86	{ "tcp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_TCP, 0, 0 },
87	{ "tcp_sack_permitted",		"sack",		MOD_PROTO_TCP, 0, 0 },
88	{ "tcp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_TCP, 0, 0 },
89	{ "tcp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_TCP,
90	    0, 0 },
91	{ "tcp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_TCP,
92	    0, 0 },
93	{ "udp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_UDP,
94	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
95	{ "udp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_UDP,
96	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
97	{ "udp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_UDP,
98	    0, MOD_PROP_PERM_READ },
99	{ "udp_largest_anon_port",	"largest_anon_port",    MOD_PROTO_UDP,
100	    0, 0 },
101	{ "udp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_UDP, 0, 0 },
102	{ "udp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_UDP, 0, 0 },
103	{ "udp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_UDP,
104	    0, 0 },
105	{ "udp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_UDP,
106	    0, 0 },
107	{ "sctp_extra_priv_ports_add",	"extra_priv_ports",	MOD_PROTO_SCTP,
108	    IPADM_OPT_APPEND, MOD_PROP_PERM_WRITE },
109	{ "sctp_extra_priv_ports_del",	"extra_priv_ports",	MOD_PROTO_SCTP,
110	    IPADM_OPT_REMOVE, MOD_PROP_PERM_WRITE },
111	{ "sctp_extra_priv_ports",	"extra_priv_ports",	MOD_PROTO_SCTP,
112	    0, MOD_PROP_PERM_READ },
113	{ "sctp_largest_anon_port",	"largest_anon_port",	MOD_PROTO_SCTP,
114	    0, 0 },
115	{ "sctp_recv_hiwat",		"recv_maxbuf",	MOD_PROTO_SCTP, 0, 0 },
116	{ "sctp_xmit_hiwat",		"send_maxbuf",	MOD_PROTO_SCTP, 0, 0 },
117	{ "sctp_smallest_anon_port",	"smallest_anon_port",	MOD_PROTO_SCTP,
118	    0, 0 },
119	{ "sctp_smallest_nonpriv_port",	"smallest_nonpriv_port", MOD_PROTO_SCTP,
120	    0, 0 },
121	{ NULL, NULL, 0, 0, 0 }
122};
123
124static uint_t
125ndd_str2proto(const char *protostr)
126{
127	if (strcmp(protostr, "tcp") == 0 ||
128	    strcmp(protostr, "tcp6") == 0) {
129		return (MOD_PROTO_TCP);
130	} else if (strcmp(protostr, "udp") == 0 ||
131	    strcmp(protostr, "udp6") == 0) {
132		return (MOD_PROTO_UDP);
133	} else if (strcmp(protostr, "ip") == 0 ||
134	    strcmp(protostr, "ip6") == 0 ||
135	    strcmp(protostr, "arp") == 0) {
136		return (MOD_PROTO_IP);
137	} else if (strcmp(protostr, "icmp") == 0 ||
138	    strcmp(protostr, "icmp6") == 0) {
139		return (MOD_PROTO_RAWIP);
140	} else if (strcmp(protostr, "sctp") == 0 ||
141	    strcmp(protostr, "sctp6") == 0) {
142		return (MOD_PROTO_SCTP);
143	}
144	return (MOD_PROTO_NONE);
145}
146
147static char *
148ndd_perm2str(uint_t perm)
149{
150	switch (perm) {
151	case MOD_PROP_PERM_READ:
152		return ("read only");
153	case MOD_PROP_PERM_WRITE:
154		return ("write only");
155	case MOD_PROP_PERM_RW:
156		return ("read and write");
157	}
158
159	return (NULL);
160}
161
162/*
163 * Print all the protocol properties for the given protocol name. The kernel
164 * returns all the properties for the given protocol therefore we have to
165 * apply some filters before we print them.
166 *
167 *	- convert any new ipadm name to old ndd name using the table.
168 *	  For example: `sack' --> `tcp_sack_permitted'.
169 *
170 *	- replace leading underscores with protocol name.
171 *	  For example: `_strong_iss' --> `tcp_strong_iss'
172 *
173 *	- don't print new public properties that are supported only by ipadm(1M)
174 *	  For example: `hostmodel' should be supported only from ipadm(1M).
175 *	  Such properties are identified by not having leading '_' and not
176 *	  being present in the mapping table.
177 */
178static void
179print_ipadm2ndd(char *oldbuf, uint_t obufsize)
180{
181	ndd2ipadm_map_t	*nimap;
182	char		*pname, *rwtag, *protostr;
183	uint_t		proto, perm;
184	boolean_t	matched;
185
186	pname = oldbuf;
187	while (pname[0] && pname < (oldbuf + obufsize - 1)) {
188		for (protostr = pname; !isspace(*protostr); protostr++)
189			;
190		*protostr++ = '\0';
191		/* protostr now points to protocol */
192
193		for (rwtag = protostr; !isspace(*rwtag); rwtag++)
194			;
195		*rwtag++ = '\0';
196		/* rwtag now points to permissions */
197
198		proto = atoi(protostr);
199		perm = atoi(rwtag);
200		matched = B_FALSE;
201		for (nimap = map; nimap->ndd_name != NULL; nimap++) {
202			if (strcmp(pname, nimap->ipadm_name) != 0 ||
203			    !(nimap->ipadm_proto & proto))
204				continue;
205
206			matched = B_TRUE;
207			if (nimap->ndd_perm != 0)
208				perm = nimap->ndd_perm;
209			(void) printf("%-30s (%s)\n", nimap->ndd_name,
210			    ndd_perm2str(perm));
211		}
212		/*
213		 * print only if it's a private property. We should
214		 * not be printing any new public property in ndd(1M)
215		 * output.
216		 */
217		if (!matched && pname[0] == '_') {
218			char	tmpstr[512];
219			int	err;
220
221			err = ipadm_new2legacy_propname(pname, tmpstr,
222			    sizeof (tmpstr), proto);
223			assert(err != -1);
224
225			(void) printf("%-30s (%s)\n", tmpstr,
226			    ndd_perm2str(perm));
227		}
228		for (pname = rwtag; *pname++; )
229			;
230	}
231}
232
233/*
234 * get/set the value for a given property by calling into libipadm. The
235 * IPH_LEGACY flag is used by libipadm for special handling. For some
236 * properties, libipadm.so displays strings (for e.g., on/off,
237 * never/passive/active, et al) instead of numerals. However ndd(1M) always
238 * printed numberals. This flag will help in avoiding printing strings.
239 */
240static boolean_t
241do_ipadm_getset(int cmd, char *buf, int buflen)
242{
243	ndd2ipadm_map_t	*nimap;
244	ipadm_handle_t	iph = NULL;
245	ipadm_status_t	status;
246	char		*mod;
247	uint_t		proto, perm = 0, flags = 0;
248	char		*pname, *pvalp, nname[512];
249	int		i;
250
251	if ((mod = strrchr(modpath, '/')) == NULL)
252		mod = modpath;
253	else
254		++mod;
255	if ((proto = ndd_str2proto(mod)) == MOD_PROTO_NONE)
256		return (B_FALSE);
257
258	if ((status = ipadm_open(&iph, IPH_LEGACY)) != IPADM_SUCCESS)
259		goto fail;
260
261	pname = buf;
262	for (nimap = map; nimap->ndd_name != NULL; nimap++) {
263		if (strcmp(pname, nimap->ndd_name) == 0) {
264			pname = nimap->ipadm_name;
265			proto = nimap->ipadm_proto;
266			flags = nimap->ipadm_flags;
267			perm = nimap->ndd_perm;
268			break;
269		}
270	}
271
272	if (nimap->ndd_name == NULL && strcmp(pname, "?") != 0) {
273		/* do not allow set/get of public properties from ndd(1M) */
274		if (ipadm_legacy2new_propname(pname, nname, sizeof (nname),
275		    &proto) != 0) {
276			status = IPADM_PROP_UNKNOWN;
277			goto fail;
278		} else {
279			pname = nname;
280		}
281	}
282
283	if (cmd == ND_GET) {
284		char		propval[MAXPROPVALLEN], allprop[64536];
285		uint_t		pvalsz;
286		sa_family_t	af = AF_UNSPEC;
287		int		err;
288
289		if (perm == MOD_PROP_PERM_WRITE)
290			fatal("operation failed: Permission denied");
291
292		if (strcmp(pname, "?") == 0) {
293			pvalp = allprop;
294			pvalsz = sizeof (allprop);
295		} else {
296			pvalp = propval;
297			pvalsz = sizeof (propval);
298		}
299
300		status = ipadm_get_prop(iph, pname, pvalp, &pvalsz, proto,
301		    IPADM_OPT_ACTIVE);
302		if (status != IPADM_SUCCESS)
303			goto fail;
304
305		if (strcmp(pname, "?") == 0) {
306			(void) print_ipadm2ndd(pvalp, pvalsz);
307		} else {
308			char *tmp = pvalp;
309
310			/*
311			 * For backward compatibility if there are multiple
312			 * values print each value in it's own line.
313			 */
314			while (*tmp != '\0') {
315				if (*tmp == ',')
316					*tmp = '\n';
317				tmp++;
318			}
319			(void) printf("%s\n", pvalp);
320		}
321		(void) fflush(stdout);
322	} else {
323		if (perm == MOD_PROP_PERM_READ)
324			fatal("operation failed: Permission denied");
325
326		/* walk past the property name to find the property value */
327		for (i = 0; buf[i] != '\0'; i++)
328			;
329
330		pvalp = &buf[++i];
331		status = ipadm_set_prop(iph, pname, pvalp, proto,
332		    flags|IPADM_OPT_ACTIVE);
333	}
334fail:
335	ipadm_close(iph);
336	if (status != IPADM_SUCCESS)
337		fatal("operation failed: %s", ipadm_status2str(status));
338	return (B_TRUE);
339}
340
341/*
342 * gldv3_warning() catches the case of /sbin/ndd abuse to administer
343 * ethernet/MII props. Note that /sbin/ndd has not been abused
344 * for administration of other datalink types, which makes it permissible
345 * to test for support of the flowctrl property.
346 */
347static void
348gldv3_warning(char *module)
349{
350	datalink_id_t	linkid;
351	dladm_status_t	status;
352	char		buf[DLADM_PROP_VAL_MAX], *cp;
353	uint_t		cnt = 1;
354	char		*link;
355	dladm_handle_t	handle;
356
357	link = strrchr(module, '/');
358	if (link == NULL)
359		return;
360
361	if (dladm_open(&handle) != DLADM_STATUS_OK)
362		return;
363
364	status = dladm_name2info(handle, ++link, &linkid, NULL, NULL, NULL);
365	if (status == DLADM_STATUS_OK) {
366		cp = buf;
367		status = dladm_get_linkprop(handle, linkid,
368		    DLADM_PROP_VAL_CURRENT, "flowctrl", &cp, &cnt);
369		if (status == DLADM_STATUS_OK) {
370			(void) fprintf(stderr, gettext(
371			    "WARNING: The ndd commands for datalink "
372			    "administration are obsolete and may be "
373			    "removed in a future release of Solaris. "
374			    "Use dladm(1M) to manage datalink tunables.\n"));
375		}
376	}
377	dladm_close(handle);
378}
379
380/* ARGSUSED */
381int
382main(int argc, char **argv)
383{
384	char	*cp, *value, *mod;
385	int	cmd;
386	int	fd = 0;
387
388	if (!(cp = *++argv)) {
389		while ((fd = open_device()) != -1) {
390			getset_interactive(fd);
391			(void) close(fd);
392		}
393		return (EXIT_SUCCESS);
394	}
395
396	cmd = ND_GET;
397	if (cp[0] == '-') {
398		if (strncmp(&cp[1], "set", 3) == 0)
399			cmd = ND_SET;
400		else if (strncmp(&cp[1], "get", 3) != 0)
401			fatal(usage_str);
402		if (!(cp = *++argv))
403			fatal(usage_str);
404	}
405
406	gldv3_warning(cp);
407
408	mod = strrchr(cp, '/');
409	if (mod != NULL)
410		mod++;
411	else
412		mod = cp;
413
414	if (ndd_str2proto(mod) == MOD_PROTO_NONE) {
415		if ((fd = open(cp, O_RDWR)) == -1)
416			fatal("open of %s failed: %s", cp, errmsg(errno));
417		if (!isastream(fd))
418			fatal("%s is not a streams device", cp);
419	}
420
421	(void) strlcpy(modpath, cp, sizeof (modpath));
422	if (!(cp = *++argv)) {
423		getset_interactive(fd);
424		(void) close(fd);
425		return (EXIT_SUCCESS);
426	}
427
428	if (cmd == ND_SET) {
429		if (!(value = *++argv))
430			fatal(usage_str);
431		(void) snprintf(gbuf, sizeof (gbuf), "%s%c%s%c", cp, '\0',
432		    value, '\0');
433		if (!do_getset(fd, cmd, gbuf, sizeof (gbuf)))
434			return (EXIT_FAILURE);
435	} else {
436		do {
437			(void) memset(gbuf, '\0', sizeof (gbuf));
438			(void) strlcpy(gbuf, cp, sizeof (gbuf));
439			if (!do_getset(fd, cmd, gbuf, sizeof (gbuf)))
440				return (EXIT_FAILURE);
441			if (cp = *++argv)
442				(void) putchar('\n');
443		} while (cp);
444	}
445
446	(void) close(fd);
447	return (EXIT_SUCCESS);
448}
449
450static void
451name_print(char *buf)
452{
453	char *cp, *rwtag;
454
455	for (cp = buf; cp[0]; ) {
456		for (rwtag = cp; !isspace(*rwtag); rwtag++)
457			;
458		*rwtag++ = '\0';
459		while (isspace(*rwtag))
460			rwtag++;
461		(void) printf("%-30s%s\n", cp, rwtag);
462		for (cp = rwtag; *cp++; )
463			;
464	}
465}
466
467/*
468 * This function is vile, but it's better here than in the kernel.
469 */
470static boolean_t
471is_obsolete(const char *param)
472{
473	if (strcmp(param, "ip_enable_group_ifs") == 0 ||
474	    strcmp(param, "ifgrp_status") == 0) {
475		(void) fprintf(stderr, "The \"%s\" tunable has been superseded "
476		    "by IP Multipathing.\nPlease see the IP Network "
477		    "Multipathing Administration Guide for details.\n", param);
478		return (B_TRUE);
479	}
480	return (B_FALSE);
481}
482
483static boolean_t
484do_getset(int fd, int cmd, char *buf, int buf_len)
485{
486	char	*cp;
487	struct strioctl	stri;
488	boolean_t	is_name_get;
489
490	if (is_obsolete(buf))
491		return (B_TRUE);
492
493	/*
494	 * See if libipadm can handle this request, i.e., properties on
495	 * following modules arp, ip, ipv4, ipv6, tcp, udp and sctp
496	 */
497	if (do_ipadm_getset(cmd, buf, buf_len))
498		return (B_TRUE);
499
500	stri.ic_cmd = cmd;
501	stri.ic_timout = 0;
502	stri.ic_len = buf_len;
503	stri.ic_dp = buf;
504	is_name_get = stri.ic_cmd == ND_GET && buf[0] == '?' && buf[1] == '\0';
505
506	if (ioctl(fd, I_STR, &stri) == -1) {
507		if (errno == ENOENT)
508			(void) printf("name is non-existent for this module\n"
509			    "for a list of valid names, use name '?'\n");
510		else
511			(void) printf("operation failed: %s\n", errmsg(errno));
512		return (B_FALSE);
513	}
514	if (is_name_get)
515		name_print(buf);
516	else if (stri.ic_cmd == ND_GET) {
517		for (cp = buf; *cp != '\0'; cp += strlen(cp) + 1)
518			(void) puts(cp);
519	}
520	(void) fflush(stdout);
521	return (B_TRUE);
522}
523
524static int
525get_value(char *msg, char *buf, int buf_len)
526{
527	int	len;
528
529	(void) printf("%s", msg);
530	(void) fflush(stdout);
531
532	buf[buf_len-1] = '\0';
533	if (fgets(buf, buf_len-1, stdin) == NULL)
534		exit(EXIT_SUCCESS);
535	len = strlen(buf);
536	if (buf[len-1] == '\n')
537		buf[len - 1] = '\0';
538	else
539		len++;
540	return (len);
541}
542
543static void
544getset_interactive(int fd)
545{
546	int	cmd;
547	char	*cp;
548	int	len, buf_len;
549	char	len_buf[10];
550
551	for (;;) {
552		(void) memset(gbuf, '\0', sizeof (gbuf));
553		len = get_value("name to get/set ? ", gbuf, sizeof (gbuf));
554		if (len == 1 || (gbuf[0] == 'q' && gbuf[1] == '\0'))
555			return;
556		for (cp = gbuf; cp < &gbuf[len]; cp++) {
557			if (isspace(*cp))
558				*cp = '\0';
559		}
560		cmd = ND_GET;
561		if (gbuf[0] != '?' &&
562		    get_value("value ? ", &gbuf[len], sizeof (gbuf) - len) > 1)
563			cmd = ND_SET;
564		if (cmd == ND_GET && gbuf[0] != '?' &&
565		    get_value("length ? ", len_buf, sizeof (len_buf)) > 1) {
566			if (!isdigit(len_buf[0])) {
567				(void) printf("invalid length\n");
568				continue;
569			}
570			buf_len = atoi(len_buf);
571		} else
572			buf_len = sizeof (gbuf);
573		(void) do_getset(fd, cmd, gbuf, buf_len);
574	}
575}
576
577static void
578printe(boolean_t print_errno, char *fmt, ...)
579{
580	va_list	ap;
581	int error = errno;
582
583	va_start(ap, fmt);
584	(void) printf("*ERROR* ");
585	(void) vprintf(fmt, ap);
586	va_end(ap);
587
588	if (print_errno)
589		(void) printf(": %s\n", errmsg(error));
590	else
591		(void) printf("\n");
592}
593
594static int
595open_device()
596{
597	int	fd, len;
598	char	*mod;
599
600	for (;;) {
601		len = get_value("module to query ? ", modpath,
602		    sizeof (modpath));
603		if (len <= 1 ||
604		    (len == 2 && (modpath[0] == 'q' || modpath[0] == 'Q')))
605			return (-1);
606
607		mod = strrchr(modpath, '/');
608		if (mod != NULL)
609			mod++;
610		else
611			mod = modpath;
612		if (ndd_str2proto(mod) == MOD_PROTO_NONE) {
613			if ((fd = open(modpath, O_RDWR)) == -1) {
614				printe(B_TRUE, "open of %s failed", modpath);
615				continue;
616			}
617		} else {
618			return (0);
619		}
620
621		gldv3_warning(modpath);
622
623		if (isastream(fd))
624			return (fd);
625
626		(void) close(fd);
627		printe(B_FALSE, "%s is not a streams device", modpath);
628	}
629}
630
631static void
632fatal(char *fmt, ...)
633{
634	va_list	ap;
635
636	va_start(ap, fmt);
637	(void) vfprintf(stderr, fmt, ap);
638	va_end(ap);
639	(void) fprintf(stderr, "\n");
640
641	exit(EXIT_FAILURE);
642}
643
644static char *
645errmsg(int error)
646{
647	char *msg = strerror(error);
648
649	return (msg != NULL ? msg : "unknown error");
650}
651