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