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  * sppptun.c - Solaris STREAMS PPP multiplexing tunnel driver
23  * installer.
24  *
25  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <signal.h>
38 #include <stropts.h>
39 #include <fcntl.h>
40 #include <locale.h>
41 #include <sys/fcntl.h>
42 #include <sys/stropts.h>
43 #include <sys/socket.h>
44 #include <net/if.h>
45 #include <netinet/in.h>
46 #include <netinet/if_ether.h>
47 #include <net/sppptun.h>
48 #include <libdlpi.h>
49 
50 static char *myname;		/* Copied from argv[0] */
51 static int verbose;		/* -v on command line */
52 
53 /* Data gathered during per-style attach routine. */
54 struct attach_data {
55 	ppptun_lname appstr;    /* String to append to interface name (PPA) */
56 	ppptun_atype localaddr; /* Local interface address */
57 	uint_t locallen;	/* Length of local address */
58 };
59 
60 /* Per-protocol plumbing data */
61 struct protos {
62 	const char *name;
63 	const char *desc;
64 	int (*attach)(struct protos *prot, char *linkname,
65 	    struct attach_data *adata);
66 	uint_t protval;
67 	int style;
68 };
69 
70 /*
71  * Print a usage string and terminate.  Used for command line argument
72  * errors.  Does not return.
73  */
74 static void
75 usage(void)
76 {
77 	(void) fprintf(stderr, gettext(
78 	    "Usage:\n\t%s plumb [<protocol> <device>]\n"
79 	    "\t%s unplumb <interface-name>\n"
80 	    "\t%s query\n"), myname, myname, myname);
81 	exit(1);
82 }
83 
84 /*
85  * General DLPI function.  This is called indirectly through
86  * the protos structure for the selected lower stream protocol.
87  */
88 static int
89 sppp_dlpi(struct protos *prot, char *linkname, struct attach_data *adata)
90 {
91 	int retv;
92 	uint_t ppa;
93 	dlpi_handle_t dh;
94 
95 	if (verbose)
96 		(void) printf(gettext("opening DLPI link %s\n"), linkname);
97 	if ((retv = dlpi_open(linkname, &dh, 0)) != DLPI_SUCCESS) {
98 		(void) fprintf(stderr, gettext("%s: failed opening %s: %s\n"),
99 		    myname, linkname, dlpi_strerror(retv));
100 		return (-1);
101 	}
102 
103 	if (verbose) {
104 		(void) printf(gettext("binding to Ethertype %04X\n"),
105 		    prot->protval);
106 	}
107 	if ((retv = dlpi_bind(dh, prot->protval, NULL)) != DLPI_SUCCESS) {
108 		(void) fprintf(stderr, gettext("%s: failed binding on %s: %s"),
109 		    myname, linkname, dlpi_strerror(retv));
110 		dlpi_close(dh);
111 		return (-1);
112 	}
113 
114 	adata->locallen = DLPI_PHYSADDR_MAX;
115 	if ((retv = dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, &adata->localaddr,
116 	    &adata->locallen)) != DLPI_SUCCESS) {
117 		(void) fprintf(stderr, gettext("%s: failed getting physical"
118 		    " address on %s: %s"), myname, linkname,
119 		    dlpi_strerror(retv));
120 		dlpi_close(dh);
121 		return (-1);
122 	}
123 
124 	/* Store ppa to append to interface name. */
125 	if ((retv = dlpi_parselink(linkname, NULL, &ppa)) != DLPI_SUCCESS) {
126 		(void) fprintf(stderr, gettext("%s: failed parsing linkname on"
127 		    " %s: %s"), myname, linkname, dlpi_strerror(retv));
128 		dlpi_close(dh);
129 		return (-1);
130 	}
131 
132 	(void) snprintf(adata->appstr, sizeof (adata->appstr), "%d", ppa);
133 
134 	return (dlpi_fd(dh));
135 }
136 
137 
138 static struct protos proto_list[] = {
139 	{ "pppoe", "RFC 2516 PPP over Ethernet", sppp_dlpi, ETHERTYPE_PPPOES,
140 	    PTS_PPPOE },
141 	{ "pppoed", "RFC 2516 PPP over Ethernet Discovery", sppp_dlpi,
142 	    ETHERTYPE_PPPOED, PTS_PPPOE },
143 	{ NULL }
144 };
145 
146 /*
147  * Issue a STREAMS I_STR ioctl and fetch the result.  Returns -1 on
148  * error, or length of returned data on success.
149  */
150 static int
151 strioctl(int fd, int cmd, void *ptr, int ilen, int olen, const char *iocname)
152 {
153 	struct strioctl	str;
154 
155 	str.ic_cmd = cmd;
156 	str.ic_timout = 0;
157 	str.ic_len = ilen;
158 	str.ic_dp = ptr;
159 
160 	if (ioctl(fd, I_STR, &str) == -1) {
161 		perror(iocname);
162 		return (-1);
163 	}
164 
165 	if (olen >= 0) {
166 		if (str.ic_len > olen && verbose > 1) {
167 			(void) printf(gettext("%s:%s: extra data received; "
168 			    "%d > %d\n"), myname, iocname, str.ic_len, olen);
169 		} else if (str.ic_len < olen) {
170 			(void) fprintf(stderr, gettext("%s:%s: expected %d "
171 			    "bytes, got %d\n"), myname, iocname, olen,
172 			    str.ic_len);
173 			return (-1);
174 		}
175 	}
176 
177 	return (str.ic_len);
178 }
179 
180 /*
181  * Handle user request to plumb a new lower stream under the sppptun
182  * driver.
183  */
184 static int
185 plumb_it(int argc, char **argv)
186 {
187 	int devfd, muxfd, muxid;
188 	struct ppptun_info pti;
189 	char *cp, *linkname;
190 	struct protos *prot;
191 	struct attach_data adata;
192 
193 	/* If no protocol requested, then list known protocols. */
194 	if (optind == argc) {
195 		(void) puts("Known tunneling protocols:");
196 		for (prot = proto_list; prot->name != NULL; prot++)
197 			(void) printf("\t%s\t%s\n", prot->name, prot->desc);
198 		return (0);
199 	}
200 
201 	/* If missing protocol or device, then abort. */
202 	if (optind != argc-2)
203 		usage();
204 
205 	/* Look up requested protocol. */
206 	cp = argv[optind++];
207 	for (prot = proto_list; prot->name != NULL; prot++)
208 		if (strcasecmp(cp, prot->name) == 0)
209 			break;
210 	if (prot->name == NULL) {
211 		(void) fprintf(stderr, gettext("%s: unknown protocol %s\n"),
212 		    myname, cp);
213 		return (1);
214 	}
215 
216 	/* Get interface. */
217 	linkname = argv[optind];
218 	/* Call per-protocol attach routine to open device */
219 	if (verbose)
220 		(void) printf(gettext("opening %s\n"), linkname);
221 	if ((devfd = (*prot->attach)(prot, linkname, &adata)) < 0)
222 		return (1);
223 
224 	/* Open sppptun driver */
225 	if (verbose)
226 		(void) printf(gettext("opening /dev/%s\n"), PPP_TUN_NAME);
227 	if ((muxfd = open("/dev/" PPP_TUN_NAME, O_RDWR)) < 0) {
228 		perror("/dev/" PPP_TUN_NAME);
229 		return (1);
230 	}
231 
232 	/* Push sppptun module on top of lower driver. */
233 	if (verbose)
234 		(void) printf(gettext("pushing %s on %s\n"), PPP_TUN_NAME,
235 		    linkname);
236 	if (ioctl(devfd, I_PUSH, PPP_TUN_NAME) == -1) {
237 		perror("I_PUSH " PPP_TUN_NAME);
238 		return (1);
239 	}
240 
241 	/* Get the name of the newly-created lower stream. */
242 	if (verbose)
243 		(void) printf(gettext("getting new interface name\n"));
244 	if (strioctl(devfd, PPPTUN_GNAME, pti.pti_name, 0,
245 	    sizeof (pti.pti_name), "PPPTUN_GNAME") < 0)
246 		return (1);
247 	if (verbose)
248 		(void) printf(gettext("got interface %s\n"), pti.pti_name);
249 
250 	/* Convert stream name to protocol-specific name. */
251 	if ((cp = strchr(pti.pti_name, ':')) != NULL)
252 		*cp = '\0';
253 	(void) snprintf(pti.pti_name+strlen(pti.pti_name),
254 	    sizeof (pti.pti_name)-strlen(pti.pti_name), "%s:%s", adata.appstr,
255 	    prot->name);
256 
257 	/* Change the lower stream name. */
258 	if (verbose)
259 		(void) printf(gettext("resetting interface name to %s\n"),
260 		    pti.pti_name);
261 	if (strioctl(devfd, PPPTUN_SNAME, pti.pti_name,
262 	    sizeof (pti.pti_name), 0, "PPPTUN_SNAME") < 0) {
263 		if (errno == EEXIST)
264 			(void) fprintf(stderr, gettext("%s: %s already "
265 			    "installed\n"), myname, pti.pti_name);
266 		return (1);
267 	}
268 
269 	/*
270 	 * Send down the local interface address to the lower stream
271 	 * so that it can originate packets.
272 	 */
273 	if (verbose)
274 		(void) printf(gettext("send down local address\n"));
275 	if (strioctl(devfd, PPPTUN_LCLADDR, &adata.localaddr, adata.locallen,
276 	    0, "PPPTUN_LCLADDR") < 0)
277 		return (1);
278 
279 	/* Link the lower stream under the tunnel device. */
280 	if (verbose)
281 		(void) printf(gettext("doing I_PLINK\n"));
282 	if ((muxid = ioctl(muxfd, I_PLINK, devfd)) == -1) {
283 		perror("I_PLINK");
284 		return (1);
285 	}
286 
287 	/*
288 	 * Give the tunnel driver the multiplex ID of the new lower
289 	 * stream.  This allows the unplumb function to find and
290 	 * disconnect the lower stream.
291 	 */
292 	if (verbose)
293 		(void) printf(gettext("sending muxid %d and style %d to "
294 		    "driver\n"), muxid, prot->style);
295 	pti.pti_muxid = muxid;
296 	pti.pti_style = prot->style;
297 	if (strioctl(muxfd, PPPTUN_SINFO, &pti, sizeof (pti), 0,
298 	    "PPPTUN_SINFO") < 0)
299 		return (1);
300 
301 	if (verbose)
302 		(void) printf(gettext("done; installed %s\n"), pti.pti_name);
303 	else
304 		(void) puts(pti.pti_name);
305 
306 	return (0);
307 }
308 
309 /*
310  * Handle user request to unplumb an existing lower stream from the
311  * sppptun driver.
312  */
313 static int
314 unplumb_it(int argc, char **argv)
315 {
316 	char *ifname;
317 	int muxfd;
318 	struct ppptun_info pti;
319 
320 	/*
321 	 * Need to have the name of the lower stream on the command
322 	 * line.
323 	 */
324 	if (optind != argc-1)
325 		usage();
326 
327 	ifname = argv[optind];
328 
329 	/* Open the tunnel driver. */
330 	if (verbose)
331 		(void) printf(gettext("opening /dev/%s\n"), PPP_TUN_NAME);
332 	if ((muxfd = open("/dev/" PPP_TUN_NAME, O_RDWR)) < 0) {
333 		perror("/dev/" PPP_TUN_NAME);
334 		return (1);
335 	}
336 
337 	/* Get lower stream information; including multiplex ID. */
338 	if (verbose)
339 		(void) printf(gettext("getting info from driver\n"));
340 	(void) strncpy(pti.pti_name, ifname, sizeof (pti.pti_name));
341 	if (strioctl(muxfd, PPPTUN_GINFO, &pti, sizeof (pti),
342 	    sizeof (pti), "PPPTUN_GINFO") < 0)
343 		return (1);
344 	if (verbose)
345 		(void) printf(gettext("got muxid %d from driver\n"),
346 		    pti.pti_muxid);
347 
348 	/* Unlink lower stream from driver. */
349 	if (verbose)
350 		(void) printf(gettext("doing I_PUNLINK\n"));
351 	if (ioctl(muxfd, I_PUNLINK, pti.pti_muxid) < 0) {
352 		perror("I_PUNLINK");
353 		return (1);
354 	}
355 	if (verbose)
356 		(void) printf(gettext("done!\n"));
357 
358 	return (0);
359 }
360 
361 /*
362  * Handle user request to list lower streams plumbed under the sppptun
363  * driver.
364  */
365 /*ARGSUSED*/
366 static int
367 query_interfaces(int argc, char **argv)
368 {
369 	int muxfd, i;
370 	union ppptun_name ptn;
371 
372 	/* No other arguments permitted. */
373 	if (optind != argc)
374 		usage();
375 
376 	/* Open the tunnel driver. */
377 	if (verbose)
378 		(void) printf(gettext("opening /dev/%s\n"), PPP_TUN_NAME);
379 	if ((muxfd = open("/dev/" PPP_TUN_NAME, O_RDWR)) < 0) {
380 		perror("/dev/" PPP_TUN_NAME);
381 		return (1);
382 	}
383 
384 	/* Read and print names of lower streams. */
385 	for (i = 0; ; i++) {
386 		ptn.ptn_index = i;
387 		if (strioctl(muxfd, PPPTUN_GNNAME, &ptn, sizeof (ptn),
388 		    sizeof (ptn), "PPPTUN_GNNAME") < 0) {
389 			perror("PPPTUN_GNNAME");
390 			break;
391 		}
392 		/* Stop when we index off the end of the list. */
393 		if (ptn.ptn_name[0] == '\0')
394 			break;
395 		(void) puts(ptn.ptn_name);
396 	}
397 	return (0);
398 }
399 
400 /*
401  * Invoked by SIGALRM -- timer prevents problems in driver from
402  * hanging the utility.
403  */
404 /*ARGSUSED*/
405 static void
406 toolong(int dummy)
407 {
408 	(void) fprintf(stderr, gettext("%s: time-out in driver\n"), myname);
409 	exit(1);
410 }
411 
412 int
413 main(int argc, char **argv)
414 {
415 	int opt, errflag = 0;
416 	char *arg;
417 
418 	myname = *argv;
419 
420 
421 	(void) setlocale(LC_ALL, "");
422 
423 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
424 #define	TEXT_DOMAIN "SYS_TEST"
425 #endif
426 	(void) textdomain(TEXT_DOMAIN);
427 
428 	/* Parse command line flags */
429 	while ((opt = getopt(argc, argv, "v")) != EOF)
430 		switch (opt) {
431 		case 'v':
432 			verbose++;
433 			break;
434 		default:
435 			errflag++;
436 			break;
437 		}
438 	if (errflag != 0 || optind >= argc)
439 		usage();
440 
441 	/* Set alarm to avoid stalling on any driver errors. */
442 	(void) signal(SIGALRM, toolong);
443 	(void) alarm(2);
444 
445 	/* Switch out based on user-requested function. */
446 	arg = argv[optind++];
447 	if (strcmp(arg, "plumb") == 0)
448 		return (plumb_it(argc, argv));
449 	if (strcmp(arg, "unplumb") == 0)
450 		return (unplumb_it(argc, argv));
451 	if (strcmp(arg, "query") == 0)
452 		return (query_interfaces(argc, argv));
453 
454 	usage();
455 	return (1);
456 }
457