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