xref: /titanic_52/usr/src/cmd/cmd-inet/usr.sbin/sppptun/sppptun.c (revision 6185db853e024a486ff8837e6784dd290d866112)
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