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