xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/soconfig.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
24  */
25 
26 #include <ctype.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #include <locale.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/socket.h>
34 #include <sys/socketvar.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 
38 #define	MAXLINELEN	4096
39 
40 /*
41  * Usage:
42  *	soconfig -d <dir>
43  *		Reads input from files in dir.
44  *
45  *	soconfig -f <file>
46  *		Reads input from file. The file is structured as
47  *			 <fam> <type> <protocol> <path|module>
48  *			 <fam> <type> <protocol>
49  *		with the first line registering and the second line
50  *		deregistering.
51  *
52  *	soconfig <fam> <type> <protocol> <path|module>
53  *		registers
54  *
55  *	soconfig <fam> <type> <protocol>
56  *		deregisters
57  *
58  *	soconfig -l
59  *		print the in-kernel socket configuration table
60  *
61  * Filter Operations (Consolidation Private):
62  *
63  *	soconfig -F <name> <modname> {auto [top | bottom | before:filter |
64  *		after:filter] | prog} <fam>:<type>:<proto>,...
65  *		configure filter
66  *
67  *	soconfig -F <name>
68  *		unconfigures filter
69  */
70 
71 static int	parse_files_in_dir(const char *dir);
72 
73 static int	parse_file(char *filename);
74 
75 static int	split_line(char *line, char *argvec[], int maxargvec);
76 
77 static int	parse_params(char *famstr, char *typestr, char *protostr,
78 				char *path, const char *file, int line);
79 
80 static int	parse_int(char *str);
81 
82 static void	usage(void);
83 
84 static int	parse_filter_params(int argc, char **argv);
85 
86 static int	print_socktable();
87 
88 int
89 main(argc, argv)
90 	int argc;
91 	char *argv[];
92 {
93 	int ret;
94 
95 	argc--; argv++;
96 
97 	(void) setlocale(LC_ALL, "");
98 #if !defined(TEXT_DOMAIN)
99 #define	TEXT_DOMAIN "SYS_TEST"
100 #endif
101 	(void) textdomain(TEXT_DOMAIN);
102 
103 	if (argc == 1 && strcmp(argv[0], "-l") == 0) {
104 		ret = print_socktable();
105 		exit(ret);
106 	}
107 
108 	if (argc >= 2 && strcmp(argv[0], "-F") == 0) {
109 		argc--; argv++;
110 		ret = parse_filter_params(argc, argv);
111 		exit(ret);
112 	}
113 	if (argc == 2 && strcmp(argv[0], "-d") == 0) {
114 		ret = parse_files_in_dir(argv[1]);
115 		exit(ret);
116 	}
117 	if (argc == 2 && strcmp(argv[0], "-f") == 0) {
118 		ret = parse_file(argv[1]);
119 		exit(ret);
120 	}
121 	if (argc == 3) {
122 		ret = parse_params(argv[0], argv[1], argv[2], NULL, NULL, -1);
123 		exit(ret);
124 	}
125 	if (argc == 4) {
126 		ret = parse_params(argv[0], argv[1], argv[2], argv[3],
127 		    NULL, -1);
128 		exit(ret);
129 	}
130 	usage();
131 	exit(1);
132 	/* NOTREACHED */
133 }
134 
135 static void
136 usage(void)
137 {
138 	fprintf(stderr, gettext(
139 	    "Usage:	soconfig -d <dir>\n"
140 	    "\tsoconfig -f <file>\n"
141 	    "\tsoconfig <fam> <type> <protocol> <path|module>\n"
142 	    "\tsoconfig <fam> <type> <protocol>\n"
143 	    "\tsoconfig -l\n"));
144 }
145 
146 /*
147  * Parse all files in the given directory.
148  */
149 static int
150 parse_files_in_dir(const char *dirname)
151 {
152 	DIR		*dp;
153 	struct dirent 	*dirp;
154 	struct stat	stats;
155 	char		buf[MAXPATHLEN];
156 
157 	if ((dp = opendir(dirname)) == NULL) {
158 		fprintf(stderr, gettext("failed to open directory '%s': %s\n"),
159 		    dirname, strerror(errno));
160 		return (1);
161 	}
162 
163 	while ((dirp = readdir(dp)) != NULL) {
164 		if (dirp->d_name[0] == '.')
165 			continue;
166 
167 		if (snprintf(buf, sizeof (buf), "%s/%s", dirname,
168 		    dirp->d_name) >= sizeof (buf)) {
169 			fprintf(stderr,
170 			    gettext("path name is too long: %s/%s\n"),
171 			    dirname, dirp->d_name);
172 			continue;
173 		}
174 		if (stat(buf, &stats) == -1) {
175 			fprintf(stderr,
176 			    gettext("failed to stat '%s': %s\n"), buf,
177 			    strerror(errno));
178 			continue;
179 		}
180 		if (!S_ISREG(stats.st_mode))
181 			continue;
182 
183 		(void) parse_file(buf);
184 	}
185 
186 	closedir(dp);
187 
188 	return (0);
189 }
190 
191 /*
192  * Open the specified file and parse each line. Skip comments (everything
193  * after a '#'). Return 1 if at least one error was encountered; otherwise 0.
194  */
195 static int
196 parse_file(char *filename)
197 {
198 	char line[MAXLINELEN];
199 	char pline[MAXLINELEN];
200 	int argcount;
201 	char *argvec[20];
202 	FILE *fp;
203 	int linecount = 0;
204 	int numerror = 0;
205 
206 	fp = fopen(filename, "r");
207 	if (fp == NULL) {
208 		perror("soconfig: open");
209 		fprintf(stderr, "\n");
210 		usage();
211 		return (1);
212 	}
213 
214 	while (fgets(line, sizeof (line) - 1, fp) != NULL) {
215 		linecount++;
216 		strcpy(pline, line);
217 		argcount = split_line(pline, argvec,
218 		    sizeof (argvec) / sizeof (argvec[0]));
219 #ifdef DEBUG
220 		{
221 			int i;
222 
223 			printf("scanned %d args\n", argcount);
224 			for (i = 0; i < argcount; i++)
225 				printf("arg[%d]: %s\n", i, argvec[i]);
226 		}
227 #endif /* DEBUG */
228 		switch (argcount) {
229 		case 0:
230 			/* Empty line - or comment only line */
231 			break;
232 		case 3:
233 			numerror += parse_params(argvec[0], argvec[1],
234 			    argvec[2], NULL, filename, linecount);
235 			break;
236 		case 4:
237 			numerror += parse_params(argvec[0], argvec[1],
238 			    argvec[2], argvec[3], filename, linecount);
239 			break;
240 		default:
241 			numerror++;
242 			fprintf(stderr,
243 			    gettext("Malformed line: <%s>\n"), line);
244 			fprintf(stderr,
245 			    gettext("\ton line %d in %s\n"), linecount,
246 			    filename);
247 			break;
248 		}
249 	}
250 	(void) fclose(fp);
251 
252 	if (numerror > 0)
253 		return (1);
254 	else
255 		return (0);
256 }
257 
258 /*
259  * Parse a line splitting it off at whitspace characters.
260  * Modifies the content of the string by inserting NULLs.
261  */
262 static int
263 split_line(char *line, char *argvec[], int maxargvec)
264 {
265 	int i = 0;
266 	char *cp;
267 
268 	/* Truncate at the beginning of a comment */
269 	cp = strchr(line, '#');
270 	if (cp != NULL)
271 		*cp = NULL;
272 
273 	/* CONSTCOND */
274 	while (1) {
275 		/* Skip any whitespace */
276 		while (isspace(*line) && *line != NULL)
277 			line++;
278 
279 		if (i >= maxargvec)
280 			return (i);
281 
282 		argvec[i] = line;
283 		if (*line == NULL)
284 			return (i);
285 		i++;
286 		/* Skip until next whitespace */
287 		while (!isspace(*line) && *line != NULL)
288 			line++;
289 		if (*line != NULL) {
290 			/* Break off argument */
291 			*line++ = NULL;
292 		}
293 	}
294 	/* NOTREACHED */
295 }
296 
297 /*
298  * Parse the set of parameters and issues the sockconfig syscall.
299  * If line is not -1 it is assumed to be the line number in the file.
300  */
301 static int
302 parse_params(char *famstr, char *typestr, char *protostr, char *path,
303     const char *file, int line)
304 {
305 	int cmd, fam, type, protocol;
306 
307 	fam = parse_int(famstr);
308 	if (fam == -1) {
309 		fprintf(stderr, gettext("Bad family number: %s\n"), famstr);
310 		if (line != -1)
311 			fprintf(stderr,
312 			    gettext("\ton line %d in %s\n"), line, file);
313 		else {
314 			fprintf(stderr, "\n");
315 			usage();
316 		}
317 		return (1);
318 	}
319 
320 	type = parse_int(typestr);
321 	if (type == -1) {
322 		fprintf(stderr,
323 		    gettext("Bad socket type number: %s\n"), typestr);
324 		if (line != -1)
325 			fprintf(stderr,
326 			    gettext("\ton line %d in %s\n"), line, file);
327 		else {
328 			fprintf(stderr, "\n");
329 			usage();
330 		}
331 		return (1);
332 	}
333 
334 	protocol = parse_int(protostr);
335 	if (protocol == -1) {
336 		fprintf(stderr,
337 		    gettext("Bad protocol number: %s\n"), protostr);
338 		if (line != -1)
339 			fprintf(stderr,
340 			    gettext("\ton line %d in %s\n"), line, file);
341 		else {
342 			fprintf(stderr, "\n");
343 			usage();
344 		}
345 		return (1);
346 	}
347 
348 
349 	if (path != NULL) {
350 		struct stat stats;
351 
352 		if (strncmp(path, "/dev", strlen("/dev")) == 0 &&
353 		    stat(path, &stats) == -1) {
354 			perror(path);
355 			if (line != -1)
356 				fprintf(stderr,
357 				    gettext("\ton line %d in %s\n"), line,
358 				    file);
359 			else {
360 				fprintf(stderr, "\n");
361 				usage();
362 			}
363 			return (1);
364 		}
365 
366 		cmd = SOCKCONFIG_ADD_SOCK;
367 	} else {
368 		cmd = SOCKCONFIG_REMOVE_SOCK;
369 	}
370 
371 #ifdef DEBUG
372 	printf("not calling sockconfig(%d, %d, %d, %d, %s)\n",
373 	    cmd, fam, type, protocol, path == NULL ? "(null)" : path);
374 #else
375 	if (_sockconfig(cmd, fam, type, protocol, path) == -1) {
376 		char *s;
377 
378 		switch (errno) {
379 		case EEXIST:
380 			s = gettext("Mapping exists");
381 			break;
382 		default:
383 			s = strerror(errno);
384 			break;
385 		}
386 
387 		fprintf(stderr,
388 		    gettext("warning: socket configuration failed "
389 		    "for family %d type %d protocol %d: %s\n"),
390 		    fam, type, protocol, s);
391 		if (line != -1) {
392 			fprintf(stderr,
393 			    gettext("\ton line %d in %s\n"), line, file);
394 		}
395 		return (1);
396 	}
397 #endif
398 	return (0);
399 }
400 
401 static int
402 parse_int(char *str)
403 {
404 	char *end;
405 	int res;
406 
407 	res = strtol(str, &end, 0);
408 	if (end == str)
409 		return (-1);
410 	return (res);
411 }
412 
413 /*
414  * Add and remove socket filters.
415  */
416 static int
417 parse_filter_params(int argc, char **argv)
418 {
419 	struct sockconfig_filter_props filprop;
420 	sof_socktuple_t *socktuples;
421 	size_t tupcnt, nalloc;
422 	char *hintarg, *socktup, *tupstr;
423 	int i;
424 
425 	if (argc == 1) {
426 		if (_sockconfig(SOCKCONFIG_REMOVE_FILTER, argv[0], 0,
427 		    0, 0) < 0) {
428 			switch (errno) {
429 			case ENXIO:
430 				fprintf(stderr,
431 				    gettext("socket filter is not configured "
432 				    "'%s'\n"), argv[0]);
433 				break;
434 			default:
435 				perror("sockconfig");
436 				break;
437 			}
438 			return (1);
439 		}
440 		return (0);
441 	}
442 
443 	if (argc < 4 || argc > 5)
444 		return (1);
445 
446 
447 	if (strlen(argv[1]) >= MODMAXNAMELEN) {
448 		fprintf(stderr,
449 		    gettext("invalid module name '%s': name too long\n"),
450 		    argv[1]);
451 		return (1);
452 	}
453 	filprop.sfp_modname = argv[1];
454 
455 	/* Check the attach semantics */
456 	if (strcmp(argv[2], "auto") == 0) {
457 		filprop.sfp_autoattach = B_TRUE;
458 		if (argc == 5) {
459 			/* placement hint */
460 			if (strcmp(argv[3], "top") == 0) {
461 				filprop.sfp_hint = SOF_HINT_TOP;
462 			} else if (strcmp(argv[3], "bottom") == 0) {
463 				filprop.sfp_hint = SOF_HINT_BOTTOM;
464 			} else {
465 				if (strncmp(argv[3], "before", 6) == 0) {
466 					filprop.sfp_hint = SOF_HINT_BEFORE;
467 				} else if (strncmp(argv[3], "after", 5) == 0) {
468 					filprop.sfp_hint = SOF_HINT_AFTER;
469 				} else {
470 					fprintf(stderr,
471 					    gettext("invalid placement hint "
472 					    "'%s'\n"), argv[3]);
473 					return (1);
474 				}
475 
476 				hintarg = strchr(argv[3], ':');
477 				if (hintarg == NULL ||
478 				    (strlen(++hintarg) == 0) ||
479 				    (strlen(hintarg) >= FILNAME_MAX)) {
480 					fprintf(stderr,
481 					    gettext("invalid placement hint "
482 					    "argument '%s': name too long\n"),
483 					    argv[3]);
484 					return (1);
485 				}
486 
487 				filprop.sfp_hintarg = hintarg;
488 			}
489 		} else {
490 			filprop.sfp_hint = SOF_HINT_NONE;
491 		}
492 	} else if (strcmp(argv[2], "prog") == 0) {
493 		filprop.sfp_autoattach = B_FALSE;
494 		filprop.sfp_hint = SOF_HINT_NONE;
495 		/* cannot specify placement hint for programmatic filter */
496 		if (argc == 5) {
497 			fprintf(stderr,
498 			    gettext("placement hint specified for programmatic "
499 			    "filter\n"));
500 			return (1);
501 		}
502 	} else {
503 		fprintf(stderr, gettext("invalid attach semantic '%s'\n"),
504 		    argv[2]);
505 		return (1);
506 	}
507 
508 	/* parse the socket tuples */
509 	nalloc = 4;
510 	socktuples = calloc(nalloc, sizeof (sof_socktuple_t));
511 	if (socktuples == NULL) {
512 		perror("calloc");
513 		return (1);
514 	}
515 
516 	tupcnt = 0;
517 	tupstr = argv[(argc == 4) ? 3 : 4];
518 	while ((socktup = strsep(&tupstr, ",")) != NULL) {
519 		int val;
520 		char *valstr;
521 
522 		if (tupcnt == nalloc) {
523 			sof_socktuple_t *new;
524 
525 			nalloc *= 2;
526 			new = realloc(socktuples,
527 			    nalloc * sizeof (sof_socktuple_t));
528 			if (new == NULL) {
529 				perror("realloc");
530 				free(socktuples);
531 				return (1);
532 			}
533 			socktuples = new;
534 		}
535 		i = 0;
536 		while ((valstr = strsep(&socktup, ":")) != NULL && i < 3) {
537 			val = parse_int(valstr);
538 			if (val == -1) {
539 				fprintf(stderr, gettext("bad socket tuple\n"));
540 				free(socktuples);
541 				return (1);
542 			}
543 			switch (i) {
544 			case 0:	socktuples[tupcnt].sofst_family = val; break;
545 			case 1:	socktuples[tupcnt].sofst_type = val; break;
546 			case 2:	socktuples[tupcnt].sofst_protocol = val; break;
547 			}
548 			i++;
549 		}
550 		if (i != 3) {
551 			fprintf(stderr, gettext("bad socket tuple\n"));
552 			free(socktuples);
553 			return (1);
554 		}
555 		tupcnt++;
556 	}
557 	if (tupcnt == 0) {
558 		fprintf(stderr, gettext("no socket tuples specified\n"));
559 		free(socktuples);
560 		return (1);
561 	}
562 	filprop.sfp_socktuple_cnt = tupcnt;
563 	filprop.sfp_socktuple = socktuples;
564 
565 	if (_sockconfig(SOCKCONFIG_ADD_FILTER, argv[0], &filprop, 0, 0) < 0) {
566 		switch (errno) {
567 		case EINVAL:
568 			fprintf(stderr,
569 			    gettext("invalid socket filter configuration\n"));
570 			break;
571 		case EEXIST:
572 			fprintf(stderr,
573 			    gettext("socket filter is already configured "
574 			    "'%s'\n"), argv[0]);
575 			break;
576 		case ENOSPC:
577 			fprintf(stderr, gettext("unable to satisfy placement "
578 			    "constraint\n"));
579 			break;
580 		default:
581 			perror("sockconfig");
582 			break;
583 		}
584 		free(socktuples);
585 		return (1);
586 	}
587 	free(socktuples);
588 	return (0);
589 }
590 
591 /*
592  *  Print the in-kernel socket configuration table
593  */
594 
595 static int
596 print_socktable()
597 {
598 	sockconfig_socktable_t sc_table;
599 	int i;
600 
601 	(void) memset(&sc_table, 0, sizeof (sockconfig_socktable_t));
602 
603 	/* get number of entries */
604 	if (_sockconfig(SOCKCONFIG_GET_SOCKTABLE, &sc_table) == -1) {
605 		fprintf(stderr,
606 		    gettext("cannot get in-kernel socket table: %s\n"),
607 		    strerror(errno));
608 		return (-1);
609 	}
610 	if (sc_table.num_of_entries == 0)
611 		return (0);
612 
613 	sc_table.st_entries = calloc(sc_table.num_of_entries,
614 	    sizeof (sockconfig_socktable_entry_t));
615 	if (sc_table.st_entries == NULL) {
616 		fprintf(stderr, gettext("out of memory\n"));
617 		return (-1);
618 	}
619 
620 	/* get socket table entries */
621 	if (_sockconfig(SOCKCONFIG_GET_SOCKTABLE, &sc_table) == -1) {
622 		fprintf(stderr,
623 		    gettext("cannot get in-kernel socket table: %s\n"),
624 		    strerror(errno));
625 		return (-1);
626 	}
627 
628 	printf("%6s %4s %5s %15s %15s %6s %6s\n",
629 	    "FAMILY", "TYPE", "PROTO", "STRDEV", "SOCKMOD",
630 	    "REFS", "FLAGS");
631 	for (i = 0; i < sc_table.num_of_entries; i++) {
632 		printf("%6u %4u %5u %15s %15s %6u %#6x\n",
633 		    sc_table.st_entries[i].se_family,
634 		    sc_table.st_entries[i].se_type,
635 		    sc_table.st_entries[i].se_protocol,
636 		    (strcmp(sc_table.st_entries[i].se_modname,
637 		    "socktpi") == 0) ?
638 		    sc_table.st_entries[i].se_strdev : "-",
639 		    sc_table.st_entries[i].se_modname,
640 		    sc_table.st_entries[i].se_refcnt,
641 		    sc_table.st_entries[i].se_flags);
642 	}
643 	free(sc_table.st_entries);
644 	return (0);
645 }
646