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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * sppptun.c - Solaris STREAMS PPP multiplexing tunnel driver 24 * installer. 25 * 26 * Copyright (c) 2000-2001 by Sun Microsystems, Inc. 27 * All rights reserved. 28 */ 29 30 #pragma ident "%Z%%M% %I% %E% SMI" 31 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <string.h> 36 #include <ctype.h> 37 #include <errno.h> 38 #include <signal.h> 39 #include <alloca.h> 40 #include <stropts.h> 41 #include <fcntl.h> 42 #include <locale.h> 43 #include <sys/dlpi.h> 44 #include <sys/fcntl.h> 45 #include <sys/stropts.h> 46 #include <sys/socket.h> 47 #include <net/if.h> 48 #include <netinet/in.h> 49 #include <netinet/if_ether.h> 50 #include <net/sppptun.h> 51 52 static char *myname; /* Copied from argv[0] */ 53 static int verbose; /* -v on command line */ 54 55 /* Data gathered during per-style attach routine. */ 56 struct attach_data { 57 ppptun_lname appstr; /* String to append to interface name (PPA) */ 58 ppptun_atype localaddr; /* Local interface address */ 59 int locallen; /* Length of local address */ 60 }; 61 62 /* Per-protocol plumbing data */ 63 struct protos { 64 const char *name; 65 const char *desc; 66 int (*attach)(struct protos *prot, char *ifname, 67 struct attach_data *adata); 68 int protval; 69 int style; 70 }; 71 72 /* 73 * Print a usage string and terminate. Used for command line argument 74 * errors. Does not return. 75 */ 76 static void 77 usage(void) 78 { 79 (void) fprintf(stderr, gettext( 80 "Usage:\n\t%s plumb [<protocol> <device>]\n" 81 "\t%s unplumb <interface-name>\n" 82 "\t%s query\n"), myname, myname, myname); 83 exit(1); 84 } 85 86 /* 87 * Await a DLPI response to a previous driver command. "etype" is set 88 * to the expected response primitive. "rptr" and "rlen" may point to 89 * a buffer to hold returned data, if desired. Otherwise, "rptr" is 90 * NULL. Returns -1 on error, 0 on success. 91 * 92 * If "rlen" is a positive number, then it indicates the number of 93 * bytes expected in return, and any longer response is truncated to 94 * that value, and any shorter response generates a warning message. 95 * If it's a negative number, then it indicates the maximum number of 96 * bytes expected, and no warning is printed if fewer are received. 97 */ 98 static int 99 dlpi_reply(int fd, int etype, void *rptr, int rlen) 100 { 101 /* Align 'buf' on natural boundary for aggregates. */ 102 uintptr_t buf[BUFSIZ/sizeof (uintptr_t)]; 103 int flags; 104 union DL_primitives *dlp = (union DL_primitives *)buf; 105 struct strbuf ctl; 106 107 /* read reply */ 108 ctl.buf = (caddr_t)dlp; 109 ctl.len = 0; 110 ctl.maxlen = BUFSIZ; 111 flags = 0; 112 if (getmsg(fd, &ctl, NULL, &flags) < 0) { 113 perror("getmsg"); 114 return (-1); 115 } 116 117 /* Validate reply. */ 118 if (ctl.len < sizeof (t_uscalar_t)) { 119 (void) fprintf(stderr, gettext("%s: request: short reply\n"), 120 myname); 121 return (-1); 122 } 123 124 if (dlp->dl_primitive == DL_ERROR_ACK) { 125 (void) fprintf(stderr, 126 gettext("%s: request: dl_errno %lu errno %lu\n"), myname, 127 dlp->error_ack.dl_errno, dlp->error_ack.dl_unix_errno); 128 return (-1); 129 } 130 if (dlp->dl_primitive != etype) { 131 (void) fprintf(stderr, gettext("%s: request: unexpected " 132 "dl_primitive %lu received\n"), myname, dlp->dl_primitive); 133 return (-1); 134 } 135 if (rptr == NULL) 136 return (0); 137 if (ctl.len < rlen) { 138 (void) fprintf(stderr, gettext("%s: request: short information" 139 " received %d < %d\n"), myname, ctl.len, rlen); 140 return (-1); 141 } 142 if (rlen < 0) 143 rlen = -rlen; 144 (void) memcpy(rptr, buf, rlen); 145 return (0); 146 } 147 148 /* 149 * Send a DLPI Info-Request message and return the response in the 150 * provided buffer. Returns -1 on error, 0 on success. 151 */ 152 static int 153 dlpi_info_req(int fd, dl_info_ack_t *info_ack) 154 { 155 dl_info_req_t info_req; 156 struct strbuf ctl; 157 int flags; 158 159 (void) memset(&info_req, '\0', sizeof (info_req)); 160 info_req.dl_primitive = DL_INFO_REQ; 161 162 ctl.maxlen = 0; 163 ctl.len = DL_INFO_REQ_SIZE; 164 ctl.buf = (char *)&info_req; 165 166 flags = 0; 167 if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) { 168 perror("putmsg DL_INFO_REQ"); 169 return (-1); 170 } 171 return (dlpi_reply(fd, DL_INFO_ACK, info_ack, sizeof (*info_ack))); 172 } 173 174 /* 175 * Send a DLPI Attach-Request message for the indicated PPA. Returns 176 * -1 on error, 0 for success. 177 */ 178 static int 179 dlpi_attach_req(int fd, int ppa) 180 { 181 dl_attach_req_t attach_req; 182 struct strbuf ctl; 183 int flags; 184 185 (void) memset(&attach_req, '\0', sizeof (attach_req)); 186 attach_req.dl_primitive = DL_ATTACH_REQ; 187 attach_req.dl_ppa = ppa; 188 189 ctl.maxlen = 0; 190 ctl.len = DL_ATTACH_REQ_SIZE; 191 ctl.buf = (char *)&attach_req; 192 193 flags = 0; 194 if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) { 195 perror("putmsg DL_ATTACH_REQ"); 196 return (-1); 197 } 198 return (dlpi_reply(fd, DL_OK_ACK, NULL, 0)); 199 } 200 201 /* 202 * Send a DLPI Bind-Request message for the requested SAP and set the 203 * local address. Returns -1 for error. Otherwise, the length of the 204 * local address is returned. 205 */ 206 static int 207 dlpi_bind_req(int fd, int sap, uint8_t *localaddr, int maxaddr) 208 { 209 dl_bind_req_t bind_req; 210 dl_bind_ack_t *back; 211 struct strbuf ctl; 212 int flags, repsize, rsize; 213 214 (void) memset(&bind_req, '\0', sizeof (*&bind_req)); 215 bind_req.dl_primitive = DL_BIND_REQ; 216 /* DLPI SAPs are in host byte order! */ 217 bind_req.dl_sap = sap; 218 bind_req.dl_service_mode = DL_CLDLS; 219 220 ctl.maxlen = 0; 221 ctl.len = DL_BIND_REQ_SIZE; 222 ctl.buf = (char *)&bind_req; 223 224 flags = 0; 225 if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) { 226 perror("putmsg DL_BIND_REQ"); 227 return (-1); 228 } 229 230 repsize = sizeof (*back) + maxaddr; 231 back = (dl_bind_ack_t *)alloca(repsize); 232 if (dlpi_reply(fd, DL_BIND_ACK, (void *)back, -repsize) < 0) 233 return (-1); 234 rsize = back->dl_addr_length; 235 if (rsize > maxaddr || back->dl_addr_offset+rsize > repsize) { 236 (void) fprintf(stderr, gettext("%s: Bad hardware address size " 237 "from driver; %d > %d or %lu+%d > %d\n"), myname, 238 rsize, maxaddr, back->dl_addr_offset, rsize, repsize); 239 return (-1); 240 } 241 (void) memcpy(localaddr, (char *)back + back->dl_addr_offset, rsize); 242 return (rsize); 243 } 244 245 /* 246 * Return a printable string for a DLPI style number. (Unfortunately, 247 * these style numbers aren't just simple integer values, and printing 248 * with %d gives ugly output.) 249 */ 250 static const char * 251 styleof(int dlstyle) 252 { 253 static char buf[32]; 254 255 switch (dlstyle) { 256 case DL_STYLE1: 257 return ("1"); 258 case DL_STYLE2: 259 return ("2"); 260 } 261 (void) snprintf(buf, sizeof (buf), gettext("Unknown (0x%04X)"), 262 dlstyle); 263 return ((const char *)buf); 264 } 265 266 /* 267 * General DLPI attach function. This is called indirectly through 268 * the protos structure for the selected lower stream protocol. 269 */ 270 static int 271 dlpi_attach(struct protos *prot, char *ifname, struct attach_data *adata) 272 { 273 int devfd, ppa, dlstyle, retv; 274 dl_info_ack_t dl_info; 275 char tname[MAXPATHLEN], *cp; 276 277 cp = ifname + strlen(ifname) - 1; 278 while (cp > ifname && isdigit(*cp)) 279 cp--; 280 cp++; 281 ppa = strtol(cp, NULL, 10); 282 283 /* 284 * Try once for the exact device name as a node. If it's 285 * there, then this should be a DLPI style 1 driver (one node 286 * per instance). If it's not, then it should be a style 2 287 * driver (attach specifies instance number). 288 */ 289 dlstyle = DL_STYLE1; 290 (void) strlcpy(tname, ifname, MAXPATHLEN-1); 291 if ((devfd = open(tname, O_RDWR)) < 0) { 292 if (cp < ifname + MAXPATHLEN) 293 tname[cp - ifname] = '\0'; 294 if ((devfd = open(tname, O_RDWR)) < 0) { 295 perror(ifname); 296 return (-1); 297 } 298 dlstyle = DL_STYLE2; 299 } 300 301 if (verbose) 302 (void) printf(gettext("requesting device info on %s\n"), 303 tname); 304 if (dlpi_info_req(devfd, &dl_info)) 305 return (-1); 306 if (dl_info.dl_provider_style != dlstyle) { 307 (void) fprintf(stderr, gettext("%s: unexpected DLPI provider " 308 "style on %s: got %s, "), myname, tname, 309 styleof(dl_info.dl_provider_style)); 310 (void) fprintf(stderr, gettext("expected %s\n"), 311 styleof(dlstyle)); 312 if (ifname[0] != '\0' && 313 !isdigit(ifname[strlen(ifname) - 1])) { 314 (void) fprintf(stderr, gettext("(did you forget an " 315 "instance number?)\n")); 316 } 317 (void) close(devfd); 318 return (-1); 319 } 320 321 if (dlstyle == DL_STYLE2) { 322 if (verbose) 323 (void) printf(gettext("attaching to ppa %d\n"), ppa); 324 if (dlpi_attach_req(devfd, ppa)) { 325 (void) close(devfd); 326 return (-1); 327 } 328 } 329 330 if (verbose) 331 (void) printf(gettext("binding to Ethertype %04X\n"), 332 prot->protval); 333 retv = dlpi_bind_req(devfd, prot->protval, 334 (uint8_t *)&adata->localaddr, sizeof (adata->localaddr)); 335 if (retv < 0) { 336 (void) close(devfd); 337 return (-1); 338 } 339 adata->locallen = retv; 340 341 (void) snprintf(adata->appstr, sizeof (adata->appstr), "%d", ppa); 342 return (devfd); 343 } 344 345 346 static struct protos proto_list[] = { 347 { "pppoe", "RFC 2516 PPP over Ethernet", dlpi_attach, ETHERTYPE_PPPOES, 348 PTS_PPPOE }, 349 { "pppoed", "RFC 2516 PPP over Ethernet Discovery", dlpi_attach, 350 ETHERTYPE_PPPOED, PTS_PPPOE }, 351 { NULL } 352 }; 353 354 /* 355 * Issue a STREAMS I_STR ioctl and fetch the result. Returns -1 on 356 * error, or length of returned data on success. 357 */ 358 static int 359 strioctl(int fd, int cmd, void *ptr, int ilen, int olen, const char *iocname) 360 { 361 struct strioctl str; 362 363 str.ic_cmd = cmd; 364 str.ic_timout = 0; 365 str.ic_len = ilen; 366 str.ic_dp = ptr; 367 368 if (ioctl(fd, I_STR, &str) == -1) { 369 perror(iocname); 370 return (-1); 371 } 372 373 if (olen >= 0) { 374 if (str.ic_len > olen && verbose > 1) { 375 (void) printf(gettext("%s:%s: extra data received; " 376 "%d > %d\n"), myname, iocname, str.ic_len, olen); 377 } else if (str.ic_len < olen) { 378 (void) fprintf(stderr, gettext("%s:%s: expected %d " 379 "bytes, got %d\n"), myname, iocname, olen, 380 str.ic_len); 381 return (-1); 382 } 383 } 384 385 return (str.ic_len); 386 } 387 388 /* 389 * Handle user request to plumb a new lower stream under the sppptun 390 * driver. 391 */ 392 static int 393 plumb_it(int argc, char **argv) 394 { 395 int devfd, muxfd, muxid; 396 struct ppptun_info pti; 397 char *cp, *ifname; 398 struct protos *prot; 399 char dname[MAXPATHLEN]; 400 struct attach_data adata; 401 402 /* If no protocol requested, then list known protocols. */ 403 if (optind == argc) { 404 (void) puts("Known tunneling protocols:"); 405 for (prot = proto_list; prot->name != NULL; prot++) 406 (void) printf("\t%s\t%s\n", prot->name, prot->desc); 407 return (0); 408 } 409 410 /* If missing protocol or device, then abort. */ 411 if (optind != argc-2) 412 usage(); 413 414 /* Look up requested protocol. */ 415 cp = argv[optind++]; 416 for (prot = proto_list; prot->name != NULL; prot++) 417 if (strcasecmp(cp, prot->name) == 0) 418 break; 419 if (prot->name == NULL) { 420 (void) fprintf(stderr, gettext("%s: unknown protocol %s\n"), 421 myname, cp); 422 return (1); 423 } 424 425 /* Get interface and make relative to /dev/ if necessary. */ 426 ifname = argv[optind]; 427 if (ifname[0] != '.' && ifname[0] != '/') { 428 (void) snprintf(dname, sizeof (dname), "/dev/%s", ifname); 429 ifname = dname; 430 } 431 432 /* Call per-protocol attach routine to open device */ 433 if (verbose) 434 (void) printf(gettext("opening %s\n"), ifname); 435 devfd = (*prot->attach)(prot, ifname, &adata); 436 if (devfd < 0) 437 return (1); 438 439 /* Open sppptun driver */ 440 if (verbose) 441 (void) printf(gettext("opening /dev/%s\n"), PPP_TUN_NAME); 442 if ((muxfd = open("/dev/" PPP_TUN_NAME, O_RDWR)) < 0) { 443 perror("/dev/" PPP_TUN_NAME); 444 return (1); 445 } 446 447 /* Push sppptun module on top of lower driver. */ 448 if (verbose) 449 (void) printf(gettext("pushing %s on %s\n"), PPP_TUN_NAME, 450 ifname); 451 if (ioctl(devfd, I_PUSH, PPP_TUN_NAME) == -1) { 452 perror("I_PUSH " PPP_TUN_NAME); 453 return (1); 454 } 455 456 /* Get the name of the newly-created lower stream. */ 457 if (verbose) 458 (void) printf(gettext("getting new interface name\n")); 459 if (strioctl(devfd, PPPTUN_GNAME, pti.pti_name, 0, 460 sizeof (pti.pti_name), "PPPTUN_GNAME") < 0) 461 return (1); 462 if (verbose) 463 (void) printf(gettext("got interface %s\n"), pti.pti_name); 464 465 /* Convert stream name to protocol-specific name. */ 466 if ((cp = strchr(pti.pti_name, ':')) != NULL) 467 *cp = '\0'; 468 (void) snprintf(pti.pti_name+strlen(pti.pti_name), 469 sizeof (pti.pti_name)-strlen(pti.pti_name), "%s:%s", adata.appstr, 470 prot->name); 471 472 /* Change the lower stream name. */ 473 if (verbose) 474 (void) printf(gettext("resetting interface name to %s\n"), 475 pti.pti_name); 476 if (strioctl(devfd, PPPTUN_SNAME, pti.pti_name, 477 sizeof (pti.pti_name), 0, "PPPTUN_SNAME") < 0) { 478 if (errno == EEXIST) 479 (void) fprintf(stderr, gettext("%s: %s already " 480 "installed\n"), myname, pti.pti_name); 481 return (1); 482 } 483 484 /* 485 * Send down the local interface address to the lower stream 486 * so that it can originate packets. 487 */ 488 if (verbose) 489 (void) printf(gettext("send down local address\n")); 490 if (strioctl(devfd, PPPTUN_LCLADDR, &adata.localaddr, adata.locallen, 491 0, "PPPTUN_LCLADDR") < 0) 492 return (1); 493 494 /* Link the lower stream under the tunnel device. */ 495 if (verbose) 496 (void) printf(gettext("doing I_PLINK\n")); 497 if ((muxid = ioctl(muxfd, I_PLINK, devfd)) == -1) { 498 perror("I_PLINK"); 499 return (1); 500 } 501 502 /* 503 * Give the tunnel driver the multiplex ID of the new lower 504 * stream. This allows the unplumb function to find and 505 * disconnect the lower stream. 506 */ 507 if (verbose) 508 (void) printf(gettext("sending muxid %d and style %d to " 509 "driver\n"), muxid, prot->style); 510 pti.pti_muxid = muxid; 511 pti.pti_style = prot->style; 512 if (strioctl(muxfd, PPPTUN_SINFO, &pti, sizeof (pti), 0, 513 "PPPTUN_SINFO") < 0) 514 return (1); 515 516 if (verbose) 517 (void) printf(gettext("done; installed %s\n"), pti.pti_name); 518 else 519 (void) puts(pti.pti_name); 520 521 return (0); 522 } 523 524 /* 525 * Handle user request to unplumb an existing lower stream from the 526 * sppptun driver. 527 */ 528 static int 529 unplumb_it(int argc, char **argv) 530 { 531 char *ifname; 532 int muxfd; 533 struct ppptun_info pti; 534 535 /* 536 * Need to have the name of the lower stream on the command 537 * line. 538 */ 539 if (optind != argc-1) 540 usage(); 541 542 ifname = argv[optind]; 543 544 /* Open the tunnel driver. */ 545 if (verbose) 546 (void) printf(gettext("opening /dev/%s\n"), PPP_TUN_NAME); 547 if ((muxfd = open("/dev/" PPP_TUN_NAME, O_RDWR)) < 0) { 548 perror("/dev/" PPP_TUN_NAME); 549 return (1); 550 } 551 552 /* Get lower stream information; including multiplex ID. */ 553 if (verbose) 554 (void) printf(gettext("getting info from driver\n")); 555 (void) strncpy(pti.pti_name, ifname, sizeof (pti.pti_name)); 556 if (strioctl(muxfd, PPPTUN_GINFO, &pti, sizeof (pti), 557 sizeof (pti), "PPPTUN_GINFO") < 0) 558 return (1); 559 if (verbose) 560 (void) printf(gettext("got muxid %d from driver\n"), 561 pti.pti_muxid); 562 563 /* Unlink lower stream from driver. */ 564 if (verbose) 565 (void) printf(gettext("doing I_PUNLINK\n")); 566 if (ioctl(muxfd, I_PUNLINK, pti.pti_muxid) < 0) { 567 perror("I_PUNLINK"); 568 return (1); 569 } 570 if (verbose) 571 (void) printf(gettext("done!\n")); 572 573 return (0); 574 } 575 576 /* 577 * Handle user request to list lower streams plumbed under the sppptun 578 * driver. 579 */ 580 /*ARGSUSED*/ 581 static int 582 query_interfaces(int argc, char **argv) 583 { 584 int muxfd, i; 585 union ppptun_name ptn; 586 587 /* No other arguments permitted. */ 588 if (optind != argc) 589 usage(); 590 591 /* Open the tunnel driver. */ 592 if (verbose) 593 (void) printf(gettext("opening /dev/%s\n"), PPP_TUN_NAME); 594 if ((muxfd = open("/dev/" PPP_TUN_NAME, O_RDWR)) < 0) { 595 perror("/dev/" PPP_TUN_NAME); 596 return (1); 597 } 598 599 /* Read and print names of lower streams. */ 600 for (i = 0; ; i++) { 601 ptn.ptn_index = i; 602 if (strioctl(muxfd, PPPTUN_GNNAME, &ptn, sizeof (ptn), 603 sizeof (ptn), "PPPTUN_GNNAME") < 0) { 604 perror("PPPTUN_GNNAME"); 605 break; 606 } 607 /* Stop when we index off the end of the list. */ 608 if (ptn.ptn_name[0] == '\0') 609 break; 610 (void) puts(ptn.ptn_name); 611 } 612 return (0); 613 } 614 615 /* 616 * Invoked by SIGALRM -- timer prevents problems in driver from 617 * hanging the utility. 618 */ 619 /*ARGSUSED*/ 620 static void 621 toolong(int dummy) 622 { 623 (void) fprintf(stderr, gettext("%s: time-out in driver\n"), myname); 624 exit(1); 625 } 626 627 int 628 main(int argc, char **argv) 629 { 630 int opt, errflag = 0; 631 char *arg; 632 633 myname = *argv; 634 635 636 (void) setlocale(LC_ALL, ""); 637 638 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 639 #define TEXT_DOMAIN "SYS_TEST" 640 #endif 641 (void) textdomain(TEXT_DOMAIN); 642 643 /* Parse command line flags */ 644 while ((opt = getopt(argc, argv, "v")) != EOF) 645 switch (opt) { 646 case 'v': 647 verbose++; 648 break; 649 default: 650 errflag++; 651 break; 652 } 653 if (errflag != 0 || optind >= argc) 654 usage(); 655 656 /* Set alarm to avoid stalling on any driver errors. */ 657 (void) signal(SIGALRM, toolong); 658 (void) alarm(2); 659 660 /* Switch out based on user-requested function. */ 661 arg = argv[optind++]; 662 if (strcmp(arg, "plumb") == 0) 663 return (plumb_it(argc, argv)); 664 if (strcmp(arg, "unplumb") == 0) 665 return (unplumb_it(argc, argv)); 666 if (strcmp(arg, "query") == 0) 667 return (query_interfaces(argc, argv)); 668 669 usage(); 670 return (1); 671 } 672