xref: /illumos-gate/usr/src/cmd/cmd-inet/lib/nwamd/llp.c (revision b0fe7b8fa79924061f3bdf7f240ea116c2c0b704)
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 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This file contains the routines that manipulate Link Layer Profiles
31  * (aka LLPs) and various support functions.  This includes parsing and
32  * updating of the /etc/nwam/llp file.
33  *
34  * The daemon maintains a list of llp_t structures that represent the
35  * provided configuration information for a link.  After the llp file
36  * is read, entries are added to the LLP list for any known links
37  * (identified by checking the interface list, which is based on the
38  * v4 interfaces present after 'ifconfig -a plumb') which were not
39  * represented in the llp file.  These entries contain the default
40  * "automatic" settings: plumb both IPv4 and IPv6, use DHCP on the
41  * v4 interface, and accept router- and DHCPv6-assigned addresses on
42  * the v6 interface.  The default entries created by the daemon are
43  * also added to the llp file.
44  *
45  * LLP priority is assigned based on two factors: the order within
46  * the llp file, with earlier entries having higher priority; and
47  * a preference for wired interfaces before wireless.  Entries that
48  * are added to the file by the daemon are added *after* any existing
49  * entries; within the added block, wired entries are added before
50  * wireless.  Thus if the llp file is never modified externally, wired
51  * will generally be ordered before wireless.  However, if the
52  * administrator creates the file with wireless entries before wired,
53  * that priority order will be respected.
54  *
55  * The llp list (pointed to by the global llp_head) is protected by
56  * the global llp_lock, which should be pthread_mutex_lock()'d before
57  * reading or writing the list.
58  */
59 
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <limits.h>
63 #include <strings.h>
64 #include <string.h>
65 #include <ctype.h>
66 #include <errno.h>
67 #include <assert.h>
68 #include <sys/types.h>
69 #include <sys/stat.h>
70 #include <syslog.h>
71 #include <netinet/in.h>
72 #include <arpa/inet.h>
73 #include <atomic.h>
74 #include <pthread.h>
75 #include <signal.h>
76 
77 #include "defines.h"
78 #include "structures.h"
79 #include "functions.h"
80 #include "variables.h"
81 
82 /* Lock to protect the llp list. */
83 static pthread_mutex_t llp_lock = PTHREAD_MUTEX_INITIALIZER;
84 
85 llp_t *llp_head = NULL;
86 llp_t *link_layer_profile = NULL;
87 
88 /*
89  * Global variable to hold the highest priority.  Need to use the atomic
90  * integer arithmetic functions to update it.
91  */
92 static uint32_t llp_highest_pri = 0;
93 
94 static void print_llp_list(void);
95 
96 char *
97 llp_prnm(llp_t *llp)
98 {
99 	if (llp == NULL)
100 		return ("null_llp");
101 	else if (llp->llp_lname == NULL)
102 		return ("null_lname");
103 	else
104 		return (llp->llp_lname);
105 }
106 
107 static void
108 llp_list_free(llp_t *head)
109 {
110 	llp_t **llpp;
111 	llp_t *llpfree;
112 
113 	if (pthread_mutex_lock(&llp_lock) != 0) {
114 		/* Something very serious is wrong... */
115 		syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %m");
116 		return;
117 	}
118 	llpp = &head;
119 	while (*llpp != NULL) {
120 		llpfree = *llpp;
121 		*llpp = llpfree->llp_next;
122 		free(llpfree->llp_ipv4addrstr);
123 		free(llpfree);
124 	}
125 	(void) pthread_mutex_unlock(&llp_lock);
126 }
127 
128 llp_t *
129 llp_lookup(const char *link)
130 {
131 	llp_t *llp;
132 
133 	if (link == NULL)
134 		return (NULL);
135 
136 	/* The name may change.  Better hold the lock. */
137 	if (pthread_mutex_lock(&llp_lock) != 0) {
138 		/* Something very serious is wrong... */
139 		syslog(LOG_ERR, "llp_lookup: cannot lock mutex: %m");
140 		return (NULL);
141 	}
142 	for (llp = llp_head; llp != NULL; llp = llp->llp_next) {
143 		if (strcmp(link, llp->llp_lname) == 0)
144 			break;
145 	}
146 	(void) pthread_mutex_unlock(&llp_lock);
147 	return (llp);
148 }
149 
150 /*
151  * Choose the higher priority llp of the two passed in.  If one is
152  * NULL, the other will be higher priority.  If both are NULL, NULL
153  * is returned.
154  *
155  * Assumes that both are available (i.e. doesn't check IFF_RUNNING
156  * or IF_DHCPFAILED flag values).
157  */
158 llp_t *
159 llp_high_pri(llp_t *a, llp_t *b)
160 {
161 	if (a == NULL)
162 		return (b);
163 	else if (b == NULL)
164 		return (a);
165 
166 	/*
167 	 * Higher priority is represented by a lower number.  This seems a
168 	 * bit backwards, but for now it makes assigning priorities very easy.
169 	 *
170 	 * We shouldn't have ties right now, but just in case, tie goes to a.
171 	 */
172 	return ((a->llp_pri <= b->llp_pri) ? a : b);
173 }
174 
175 /*
176  * Chooses the highest priority link that corresponds to an
177  * available interface.
178  */
179 llp_t *
180 llp_best_avail(void)
181 {
182 	llp_t *p, *rtnllp = NULL;
183 	struct interface *ifp;
184 
185 	/* The priority may change.  Better hold the lock. */
186 	if (pthread_mutex_lock(&llp_lock) != 0) {
187 		/* Something very serious is wrong... */
188 		syslog(LOG_ERR, "llp_best_avail: cannot lock mutex: %m");
189 		return (NULL);
190 	}
191 	for (p = llp_head; p != NULL; p = p->llp_next) {
192 		ifp = get_interface(p->llp_lname);
193 		if (ifp == NULL || !is_plugged_in(ifp) ||
194 		    (ifp->if_lflags & IF_DHCPFAILED) != 0)
195 			continue;
196 		rtnllp = llp_high_pri(p, rtnllp);
197 	}
198 	(void) pthread_mutex_unlock(&llp_lock);
199 
200 	return (rtnllp);
201 }
202 
203 /*
204  * Returns B_TRUE if llp is successfully activated;
205  * B_FALSE if activation fails.
206  */
207 boolean_t
208 llp_activate(llp_t *llp)
209 {
210 	boolean_t rtn;
211 	char *host;
212 	/*
213 	 * Choosing "dhcp" as a hostname is unsupported right now.
214 	 * We use hostname="dhcp" as a keyword telling bringupinterface()
215 	 * to use dhcp on the interface.
216 	 */
217 	char *dhcpstr = "dhcp";
218 
219 	llp_deactivate();
220 
221 	host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? dhcpstr :
222 	    llp->llp_ipv4addrstr;
223 
224 	if (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr,
225 	    llp->llp_ipv6onlink)) {
226 		link_layer_profile = llp;
227 		dprintf("llp_activate: activated llp for %s", llp_prnm(llp));
228 		rtn = B_TRUE;
229 	} else {
230 		dprintf("llp_activate: failed to bringup %s", llp_prnm(llp));
231 		link_layer_profile = NULL;
232 		rtn = B_FALSE;
233 	}
234 
235 	return (rtn);
236 }
237 
238 /*
239  * Deactivate the current active llp (link_layer_profile)
240  */
241 void
242 llp_deactivate(void)
243 {
244 	if (link_layer_profile == NULL)
245 		return;
246 
247 	takedowninterface(link_layer_profile->llp_lname,
248 	    link_layer_profile->llp_ipv4src == IPV4SRC_DHCP, B_TRUE,
249 	    link_layer_profile->llp_ipv6onlink);
250 
251 	dprintf("llp_deactivate: setting link_layer_profile(%p) to NULL",
252 	    (void *)link_layer_profile);
253 	link_layer_profile = NULL;
254 }
255 
256 /*
257  * Replace the currently active link layer profile with the one
258  * specified.  And since we're changing the lower layer stuff,
259  * we need to first deactivate the current upper layer profile.
260  * An upper layer profile will be reactivated later, when we get
261  * confirmation that the new llp is fully up (has an address
262  * assigned).
263  *
264  * If the new llp is the same as the currently active one, don't
265  * do anything.
266  *
267  * If the new llp is NULL, just take down the currently active one.
268  */
269 void
270 llp_swap(llp_t *newllp)
271 {
272 	char *upifname;
273 
274 	if (newllp == link_layer_profile)
275 		return;
276 
277 	deactivate_upper_layer_profile();
278 
279 	if (link_layer_profile == NULL) {
280 		/*
281 		 * there shouldn't be anything else running;
282 		 * make sure that's the case!
283 		 */
284 		upifname = (newllp == NULL) ? NULL : newllp->llp_lname;
285 		take_down_all_ifs(upifname);
286 	} else {
287 		dprintf("taking down current link layer profile (%s)",
288 		    llp_prnm(link_layer_profile));
289 		llp_deactivate();
290 	}
291 	if (newllp != NULL) {
292 		dprintf("bringing up new link layer profile (%s)",
293 		    llp_prnm(newllp));
294 		(void) llp_activate(newllp);
295 	}
296 }
297 
298 /*
299  *
300  * ifp->if_family == AF_INET, addr_src == DHCP ==> addr == NULL
301  * ifp->if_family == AF_INET, addr_src == STATIC ==> addr non null sockaddr_in
302  * ifp->if_family == AF_INET6, ipv6onlink == FALSE ==> addr == NULL
303  * ifp->if_family == AF_INET6, ipv6onlink == TRUE,
304  *     if addr non NULL then it is the textual representation of the address
305  *     and prefix.
306  *
307  * The above set of conditions describe what the inputs to this fuction are
308  * expected to be.  Given input which meets those conditions this functions
309  * then outputs a line of configuration describing the inputs.
310  *
311  * Note that it is assumed only one thread can call this function at
312  * any time.  So there is no lock to protect the file writing.  This
313  * is true as the only caller of this function should originate from
314  * llp_parse_config(), which is done at program initialization time.
315  */
316 static void
317 add_if_file(FILE *fp, struct interface *ifp, ipv4src_t addr_src,
318     boolean_t ipv6onlink, void *addr)
319 {
320 	char addr_buf[INET6_ADDRSTRLEN];
321 
322 	switch (ifp->if_family) {
323 	case AF_INET:
324 		switch (addr_src) {
325 		case IPV4SRC_STATIC:
326 			/* This is not supposed to happen... */
327 			if (addr == NULL) {
328 				(void) fprintf(fp, "%s\tdhcp\n", ifp->if_name);
329 				break;
330 			}
331 			(void) inet_ntop(AF_INET, addr, addr_buf,
332 			    INET6_ADDRSTRLEN);
333 			(void) fprintf(fp, "%s\tstatic\t%s\n", ifp->if_name,
334 			    addr_buf);
335 			break;
336 		case IPV4SRC_DHCP:
337 			/* Default is DHCP for now. */
338 		default:
339 			(void) fprintf(fp, "%s\tdhcp\n", ifp->if_name);
340 			break;
341 		}
342 		break;
343 
344 	case AF_INET6:
345 		if (ipv6onlink)
346 			(void) fprintf(fp, "%s\tipv6\n", ifp->if_name);
347 		break;
348 
349 	default:
350 		syslog(LOG_ERR, "interface %s of type %d?!", ifp->if_name,
351 		    ifp->if_family);
352 		break;
353 	}
354 }
355 
356 /*
357  * Walker function to pass to walk_interface() to add a default
358  * interface description to the LLPFILE.
359  *
360  * Regarding IF_TUN interfaces: see comments before find_and_add_llp()
361  * for an explanation of why we skip them.
362  */
363 static void
364 add_if_default(struct interface *ifp, void *arg)
365 {
366 	FILE *fp = (FILE *)arg;
367 
368 	if (ifp->if_type != IF_TUN)
369 		add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL);
370 }
371 
372 /* Create the LLPFILE using info from the interface list. */
373 static void
374 create_llp_file(void)
375 {
376 	FILE *fp;
377 	int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
378 
379 	/* Create the NWAM directory in case it does not exist. */
380 	if (mkdir(LLPDIR, dirmode) != 0) {
381 		if (errno != EEXIST) {
382 			syslog(LOG_ERR, "create NWAM directory: %m");
383 			return;
384 		}
385 	}
386 	if ((fp = fopen(LLPFILE, "w")) == NULL) {
387 		syslog(LOG_ERR, "create LLP config file: %m");
388 		return;
389 	}
390 	syslog(LOG_INFO, "Creating %s", LLPFILE);
391 	walk_interface(add_if_default, fp);
392 	(void) fclose(fp);
393 }
394 
395 /*
396  * Append an llp struct to the end of the llp list.
397  */
398 static void
399 llp_list_append(llp_t *llp)
400 {
401 	llp_t **wpp = &llp_head;
402 
403 	/*
404 	 * should be a no-op, but for now, make sure we only
405 	 * create llps for wired and wireless interfaces.
406 	 */
407 	if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS)
408 		return;
409 
410 	if (pthread_mutex_lock(&llp_lock) != 0) {
411 		/* Something very serious is wrong... */
412 		syslog(LOG_ERR, "llp_list_append: cannot lock mutex: %m");
413 		return;
414 	}
415 
416 	while (*wpp != NULL)
417 		wpp = &(*wpp)->llp_next;
418 	*wpp = llp;
419 	llp->llp_next = NULL;
420 
421 	(void) pthread_mutex_unlock(&llp_lock);
422 }
423 
424 /*
425  * Create a llp given the parameters and add it to the global list.
426  */
427 static void
428 create_and_add_llp(const char *name, ipv4src_t ipv4src, const char *addrstr,
429     boolean_t ipv6onlink, const char *ipv6addrstr)
430 {
431 	llp_t *newllp;
432 	int lnamelen;
433 
434 	if ((newllp = llp_lookup(name)) != NULL) {
435 		if (ipv6addrstr != NULL) {
436 			newllp->llp_ipv6addrstr = strdup(ipv6addrstr);
437 			if (newllp->llp_ipv6addrstr == NULL) {
438 				syslog(LOG_ERR, "could not save ipv6 static "
439 				    "address for %s", name);
440 			}
441 		}
442 		newllp->llp_ipv6onlink = ipv6onlink;
443 		return;
444 	} else if ((newllp = calloc(1, sizeof (llp_t))) == NULL) {
445 		syslog(LOG_ERR, "calloc llp: %m");
446 		return;
447 	}
448 
449 	lnamelen = sizeof (newllp->llp_lname);
450 	if (strlcpy(newllp->llp_lname, name, lnamelen) >= lnamelen) {
451 		syslog(LOG_ERR, "llp: link name too long; ignoring entry");
452 		free(newllp);
453 		return;
454 	}
455 	if (ipv4src == IPV4SRC_STATIC) {
456 		if ((newllp->llp_ipv4addrstr = strdup(addrstr)) == NULL) {
457 			syslog(LOG_ERR, "malloc ipaddrstr: %m");
458 			free(newllp);
459 			return;
460 		}
461 	} else {
462 		newllp->llp_ipv4addrstr = NULL;
463 	}
464 	newllp->llp_next = NULL;
465 	newllp->llp_pri = atomic_add_32_nv(&llp_highest_pri, 1);
466 	newllp->llp_ipv4src = ipv4src;
467 	newllp->llp_type = find_if_type(newllp->llp_lname);
468 	newllp->llp_ipv6onlink = ipv6onlink;
469 
470 	if (ipv6onlink && ipv6addrstr != NULL) {
471 		newllp->llp_ipv6addrstr = strdup(ipv6addrstr);
472 		if (newllp->llp_ipv6addrstr == NULL)
473 			syslog(LOG_WARNING, "could not store static address %s"
474 			    "on interface %s", ipv6addrstr, newllp->llp_lname);
475 	} else {
476 		newllp->llp_ipv6addrstr = NULL;
477 	}
478 
479 	llp_list_append(newllp);
480 
481 	dprintf("created llp for link %s, pri %d", newllp->llp_lname,
482 	    newllp->llp_pri);
483 }
484 
485 /*
486  * Walker function to pass to walk_interface() to find out if
487  * an interface description is missing from LLPFILE.  If it is,
488  * add it.
489  *
490  * Currently, IF_TUN type interfaces are special-cased: they are
491  * only handled as user-enabled, layered links (which may be created
492  * as part of a higher-layer profile, for example).  Thus, they
493  * shouldn't be considered when looking at the llp list, so don't
494  * add them here.
495  */
496 static void
497 find_and_add_llp(struct interface *ifp, void *arg)
498 {
499 	FILE *fp = (FILE *)arg;
500 
501 	if (ifp->if_type != IF_TUN && (llp_lookup(ifp->if_name) == NULL)) {
502 		dprintf("Adding %s to %s", ifp->if_name, LLPFILE);
503 		add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL);
504 		/* If we run out of memory, ignore this interface for now. */
505 		create_and_add_llp(ifp->if_name, IPV4SRC_DHCP, NULL,
506 		    B_TRUE, NULL);
507 	}
508 }
509 
510 /*
511  * This is a very "slow" function.  It uses walk_interface() to find
512  * out if any of the interface is missing from the LLPFILE.  For the
513  * missing ones, add them to the LLPFILE.
514  */
515 static void
516 add_missing_if_llp(FILE *fp)
517 {
518 	walk_interface(find_and_add_llp, fp);
519 }
520 
521 static void
522 print_llp_list(void)
523 {
524 	llp_t *wp;
525 
526 	dprintf("Walking llp list");
527 	for (wp = llp_head; wp != NULL; wp = wp->llp_next)
528 		dprintf("==> %s", wp->llp_lname);
529 }
530 
531 /*
532  * This function parses /etc/nwam/llp which describes the phase 0 link layer
533  * profile.  The file is line oriented with each line containing tab or space
534  * delimited fields.  Each address family (IPv4, IPv6) is described on a
535  * separate line.
536  * The first field is a link name.
537  * The second field can be either static, dhcp, ipv6, or noipv6.
538  * If the second field is static then the next field is an ipv4 address which
539  *    can contain a prefix.  Previous versions of this file could contain a
540  *    hostname in this field which is no longer supported.
541  * If the second field is dhcp then dhcp will be used on the interface.
542  * If the second field is ipv6 then an ipv6 interface is plumbed up.  The
543  *    outcome of this is that if offered by the network in.ndpd and dhcp
544  *    will conspire to put addresses on additional ipv6 logical interfaces.
545  *    If the next field is non-null then it is taken to be an IPv6 address
546  *    and possible prefix which are applied to the interface.
547  * If the second field is noipv6 then no ipv6 interfaces will be put on that
548  *    link.
549  */
550 void
551 llp_parse_config(void)
552 {
553 	static const char STATICSTR[] = "static";
554 	static const char DHCP[] = "dhcp";
555 	static const char IPV6[] = "ipv6";
556 	static const char NOIPV6[] = "noipv6";
557 	FILE *fp;
558 	char line[LINE_MAX];
559 	char *cp, *lasts, *lstr, *srcstr, *addrstr, *v6addrstr;
560 	int lnum;
561 	ipv4src_t ipv4src;
562 	boolean_t ipv6onlink;
563 
564 	fp = fopen(LLPFILE, "r+");
565 	if (fp == NULL) {
566 		if (errno != ENOENT) {
567 			/*
568 			 * XXX See comment before create_llp_file() re
569 			 * better error handling.
570 			 */
571 			syslog(LOG_ERR, "open LLP config file: %m");
572 			return;
573 		}
574 
575 		/*
576 		 * If there is none, we should create one instead.
577 		 * For now, we will use the order of the interface list
578 		 * for the priority.  We should have a priority field
579 		 * in the llp file eventually...
580 		 */
581 		create_llp_file();
582 
583 		/* Now we can try to reopen the file for processing. */
584 		fp = fopen(LLPFILE, "r+");
585 		if (fp == NULL) {
586 			syslog(LOG_ERR, "2nd open LLP config file: %m");
587 			return;
588 		}
589 	}
590 
591 	if (llp_head != NULL)
592 		llp_list_free(llp_head);
593 	llp_head = NULL;
594 
595 	for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) {
596 		ipv4src = IPV4SRC_DHCP;
597 		ipv6onlink = B_FALSE;
598 		addrstr = NULL;
599 		v6addrstr = NULL;
600 
601 		if (line[strlen(line) - 1] == '\n')
602 			line[strlen(line) - 1] = '\0';
603 
604 		cp = line;
605 		while (isspace(*cp))
606 			cp++;
607 
608 		if (*cp == '#' || *cp == '\0')
609 			continue;
610 
611 		dprintf("parsing llp conf file line %d...", lnum);
612 
613 		if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) ||
614 		    ((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) {
615 			syslog(LOG_ERR, "llp:%d: not enough tokens; "
616 			    "ignoring entry", lnum);
617 			continue;
618 		}
619 		if (strcasecmp(srcstr, STATICSTR) == 0) {
620 			if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL ||
621 			    atoi(addrstr) == 0) { /* crude check for number */
622 				syslog(LOG_ERR, "llp:%d: missing ipaddr "
623 				    "for static config; ignoring entry",
624 				    lnum);
625 				continue;
626 			}
627 			ipv4src = IPV4SRC_STATIC;
628 		} else if (strcasecmp(srcstr, DHCP) == 0) {
629 			ipv4src = IPV4SRC_DHCP;
630 		} else if (strcasecmp(srcstr, IPV6) == 0) {
631 			ipv6onlink = B_TRUE;
632 			if ((addrstr = strtok_r(NULL, " \t", &lasts)) != NULL) {
633 				v6addrstr = strdup(addrstr);
634 				if (v6addrstr == NULL) {
635 					syslog(LOG_ERR, "could not store v6 "
636 					    "static address %s for %s",
637 					    v6addrstr, lstr);
638 				}
639 			} else {
640 				v6addrstr = NULL;
641 			}
642 		} else if (strcasecmp(srcstr, NOIPV6) == 0) {
643 			ipv6onlink = B_FALSE;
644 		} else {
645 			syslog(LOG_ERR, "llp:%d: unrecognized "
646 			    "field; ignoring entry", lnum);
647 			continue;
648 		}
649 
650 		create_and_add_llp(lstr, ipv4src, addrstr, ipv6onlink,
651 		    v6addrstr);
652 	}
653 
654 	/*
655 	 * So we have read in the llp file, is there an interface which
656 	 * it does not describe?  If yes, we'd better add it to the
657 	 * file for future reference.  Again, since we don't have a
658 	 * priority field yet, we will add the interface in the order
659 	 * in the interface list.
660 	 */
661 	add_missing_if_llp(fp);
662 
663 	(void) fclose(fp);
664 
665 	print_llp_list();
666 }
667