view usr/src/cmd/cmd-inet/lib/nwamd/llp.c @ 3938:670947f6c3f6

PSARC 2007/136 Network Auto-Magic (NWAM) Phase 0 6355747 /lib/svc/method/net-svc makes a mess of hosts and ipnodes 6366093 "ifconfig <wireless-lan-device> dhcp" not enough to surf with browser 6539574 _link_aton() underallocates a buffer
author jbeck
date Fri, 30 Mar 2007 17:01:13 -0700
parents
children cb22c3f38157
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This file contains the routines that manipulate Link Layer Profiles
 * (aka LLPs) and various support functions.  This includes parsing and
 * updating of the /etc/nwam/llp file.
 *
 * The daemon maintains a list of llp_t structures that represent the
 * provided configuration information for a link.  After the llp file
 * is read, entries are added to the LLP list for any known links
 * (identified by checking the interface list, which is based on the
 * v4 interfaces present after 'ifconfig -a plumb') which were not
 * represented in the llp file.  These entries contain the default
 * "automatic" settings: plumb both IPv4 and IPv6, use DHCP on the
 * v4 interface, and accept router- and DHCPv6-assigned addresses on
 * the v6 interface.  The default entries created by the daemon are
 * also added to the llp file.
 *
 * LLP priority is assigned based on two factors: the order within
 * the llp file, with earlier entries having higher priority; and
 * a preference for wired interfaces before wireless.  Entries that
 * are added to the file by the daemon are added *after* any existing
 * entries; within the added block, wired entries are added before
 * wireless.  Thus if the llp file is never modified externally, wired
 * will generally be ordered before wireless.  However, if the
 * administrator creates the file with wireless entries before wired,
 * that priority order will be respected.
 *
 * The llp list (pointed to by the global llp_head) is protected by
 * the global llp_lock, which should be pthread_mutex_lock()'d before
 * reading or writing the list.
 */

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <atomic.h>
#include <pthread.h>
#include <signal.h>

#include "defines.h"
#include "structures.h"
#include "functions.h"
#include "variables.h"

/* Lock to protect the llp list. */
static pthread_mutex_t llp_lock = PTHREAD_MUTEX_INITIALIZER;

llp_t *llp_head = NULL;
llp_t *link_layer_profile = NULL;

/*
 * Global variable to hold the highest priority.  Need to use the atomic
 * integer arithmetic functions to update it.
 */
static uint32_t llp_highest_pri = 0;

static void print_llp_list(void);

char *
llp_prnm(llp_t *llp)
{
	if (llp == NULL)
		return ("null_llp");
	else if (llp->llp_lname == NULL)
		return ("null_lname");
	else
		return (llp->llp_lname);
}

static void
llp_list_free(llp_t *head)
{
	llp_t **llpp;
	llp_t *llpfree;

	if (pthread_mutex_lock(&llp_lock) != 0) {
		/* Something very serious is wrong... */
		syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %m");
		return;
	}
	llpp = &head;
	while (*llpp != NULL) {
		llpfree = *llpp;
		*llpp = llpfree->llp_next;
		free(llpfree->llp_ipv4addrstr);
		free(llpfree);
	}
	(void) pthread_mutex_unlock(&llp_lock);
}

llp_t *
llp_lookup(const char *link)
{
	llp_t *llp;

	if (link == NULL)
		return (NULL);

	/* The name may change.  Better hold the lock. */
	if (pthread_mutex_lock(&llp_lock) != 0) {
		/* Something very serious is wrong... */
		syslog(LOG_ERR, "llp_lookup: cannot lock mutex: %m");
		return (NULL);
	}
	for (llp = llp_head; llp != NULL; llp = llp->llp_next) {
		if (strcmp(link, llp->llp_lname) == 0)
			break;
	}
	(void) pthread_mutex_unlock(&llp_lock);
	return (llp);
}

/*
 * Choose the higher priority llp of the two passed in.  If one is
 * NULL, the other will be higher priority.  If both are NULL, NULL
 * is returned.
 *
 * Assumes that both are available (i.e. doesn't check IFF_RUNNING
 * or IF_DHCPFAILED flag values).
 */
llp_t *
llp_high_pri(llp_t *a, llp_t *b)
{
	if (a == NULL)
		return (b);
	else if (b == NULL)
		return (a);

	/*
	 * Higher priority is represented by a lower number.  This seems a
	 * bit backwards, but for now it makes assigning priorities very easy.
	 *
	 * We shouldn't have ties right now, but just in case, tie goes to a.
	 */
	return ((a->llp_pri <= b->llp_pri) ? a : b);
}

/*
 * Chooses the highest priority link that corresponds to an
 * available interface.
 */
llp_t *
llp_best_avail(void)
{
	llp_t *p, *rtnllp = NULL;
	struct interface *ifp;

	/* The priority may change.  Better hold the lock. */
	if (pthread_mutex_lock(&llp_lock) != 0) {
		/* Something very serious is wrong... */
		syslog(LOG_ERR, "llp_best_avail: cannot lock mutex: %m");
		return (NULL);
	}
	for (p = llp_head; p != NULL; p = p->llp_next) {
		ifp = get_interface(p->llp_lname);
		if (ifp == NULL || !is_plugged_in(ifp) ||
		    (ifp->if_lflags & IF_DHCPFAILED) != 0)
			continue;
		rtnllp = llp_high_pri(p, rtnllp);
	}
	(void) pthread_mutex_unlock(&llp_lock);

	return (rtnllp);
}

/*
 * Returns B_TRUE if llp is successfully activated;
 * B_FALSE if activation fails.
 */
boolean_t
llp_activate(llp_t *llp)
{
	boolean_t rtn;
	char *host;
	/*
	 * Choosing "dhcp" as a hostname is unsupported right now.
	 * We use hostname="dhcp" as a keyword telling bringupinterface()
	 * to use dhcp on the interface.
	 */
	char *dhcpstr = "dhcp";

	llp_deactivate();

	host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? dhcpstr :
	    llp->llp_ipv4addrstr;

	if (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr,
	    llp->llp_ipv6onlink)) {
		link_layer_profile = llp;
		dprintf("llp_activate: activated llp for %s", llp_prnm(llp));
		rtn = B_TRUE;
	} else {
		dprintf("llp_activate: failed to bringup %s", llp_prnm(llp));
		link_layer_profile = NULL;
		rtn = B_FALSE;
	}

	return (rtn);
}

/*
 * Deactivate the current active llp (link_layer_profile)
 */
void
llp_deactivate(void)
{
	if (link_layer_profile == NULL)
		return;

	takedowninterface(link_layer_profile->llp_lname,
	    link_layer_profile->llp_ipv4src == IPV4SRC_DHCP, B_TRUE,
	    link_layer_profile->llp_ipv6onlink);

	dprintf("llp_deactivate: setting link_layer_profile(%p) to NULL",
	    (void *)link_layer_profile);
	link_layer_profile = NULL;
}

/*
 * Replace the currently active link layer profile with the one
 * specified.  And since we're changing the lower layer stuff,
 * we need to first deactivate the current upper layer profile.
 * An upper layer profile will be reactivated later, when we get
 * confirmation that the new llp is fully up (has an address
 * assigned).
 *
 * If the new llp is the same as the currently active one, don't
 * do anything.
 *
 * If the new llp is NULL, just take down the currently active one.
 */
void
llp_swap(llp_t *newllp)
{
	char *upifname;

	if (newllp == link_layer_profile)
		return;

	deactivate_upper_layer_profile();

	if (link_layer_profile == NULL) {
		/*
		 * there shouldn't be anything else running;
		 * make sure that's the case!
		 */
		upifname = (newllp == NULL) ? NULL : newllp->llp_lname;
		take_down_all_ifs(upifname);
	} else {
		dprintf("taking down current link layer profile (%s)",
		    llp_prnm(link_layer_profile));
		llp_deactivate();
	}
	if (newllp != NULL) {
		dprintf("bringing up new link layer profile (%s)",
		    llp_prnm(newllp));
		(void) llp_activate(newllp);
	}
}

/*
 *
 * ifp->if_family == AF_INET, addr_src == DHCP ==> addr == NULL
 * ifp->if_family == AF_INET, addr_src == STATIC ==> addr non null sockaddr_in
 * ifp->if_family == AF_INET6, ipv6onlink == FALSE ==> addr == NULL
 * ifp->if_family == AF_INET6, ipv6onlink == TRUE,
 *     if addr non NULL then it is the textual representation of the address
 *     and prefix.
 *
 * The above set of conditions describe what the inputs to this fuction are
 * expected to be.  Given input which meets those conditions this functions
 * then outputs a line of configuration describing the inputs.
 *
 * Note that it is assumed only one thread can call this function at
 * any time.  So there is no lock to protect the file writing.  This
 * is true as the only caller of this function should originate from
 * llp_parse_config(), which is done at program initialization time.
 */
static void
add_if_file(FILE *fp, struct interface *ifp, ipv4src_t addr_src,
    boolean_t ipv6onlink, void *addr)
{
	char addr_buf[INET6_ADDRSTRLEN];

	switch (ifp->if_family) {
	case AF_INET:
		switch (addr_src) {
		case IPV4SRC_STATIC:
			/* This is not supposed to happen... */
			if (addr == NULL) {
				(void) fprintf(fp, "%s\tdhcp\n", ifp->if_name);
				break;
			}
			(void) inet_ntop(AF_INET, addr, addr_buf,
			    INET6_ADDRSTRLEN);
			(void) fprintf(fp, "%s\tstatic\t%s\n", ifp->if_name,
			    addr_buf);
			break;
		case IPV4SRC_DHCP:
			/* Default is DHCP for now. */
		default:
			(void) fprintf(fp, "%s\tdhcp\n", ifp->if_name);
			break;
		}
		break;

	case AF_INET6:
		if (ipv6onlink)
			(void) fprintf(fp, "%s\tipv6\n", ifp->if_name);
		break;

	default:
		syslog(LOG_ERR, "interface %s of type %d?!", ifp->if_name,
		    ifp->if_family);
		break;
	}
}

/*
 * Walker function to pass to walk_interface() to add a default
 * interface description to the LLPFILE.
 *
 * Regarding IF_TUN interfaces: see comments before find_and_add_llp()
 * for an explanation of why we skip them.
 */
static void
add_if_default(struct interface *ifp, void *arg)
{
	FILE *fp = (FILE *)arg;

	if (ifp->if_type != IF_TUN)
		add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL);
}

/* Create the LLPFILE using info from the interface list. */
static void
create_llp_file(void)
{
	FILE *fp;
	int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;

	/* Create the NWAM directory in case it does not exist. */
	if (mkdir(LLPDIR, dirmode) != 0) {
		if (errno != EEXIST) {
			syslog(LOG_ERR, "create NWAM directory: %m");
			return;
		}
	}
	if ((fp = fopen(LLPFILE, "w")) == NULL) {
		syslog(LOG_ERR, "create LLP config file: %m");
		return;
	}
	syslog(LOG_INFO, "Creating %s", LLPFILE);
	walk_interface(add_if_default, fp);
	(void) fclose(fp);
}

/*
 * Append an llp struct to the end of the llp list.
 */
static void
llp_list_append(llp_t *llp)
{
	llp_t **wpp = &llp_head;

	/*
	 * should be a no-op, but for now, make sure we only
	 * create llps for wired and wireless interfaces.
	 */
	if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS)
		return;

	if (pthread_mutex_lock(&llp_lock) != 0) {
		/* Something very serious is wrong... */
		syslog(LOG_ERR, "llp_list_append: cannot lock mutex: %m");
		return;
	}

	while (*wpp != NULL)
		wpp = &(*wpp)->llp_next;
	*wpp = llp;
	llp->llp_next = NULL;

	(void) pthread_mutex_unlock(&llp_lock);
}

/*
 * Create a llp given the parameters and add it to the global list.
 */
static void
create_and_add_llp(const char *name, ipv4src_t ipv4src, const char *addrstr,
    boolean_t ipv6onlink, const char *ipv6addrstr)
{
	llp_t *newllp;
	int lnamelen;

	if ((newllp = llp_lookup(name)) != NULL) {
		if (ipv6addrstr != NULL) {
			newllp->llp_ipv6addrstr = strdup(ipv6addrstr);
			if (newllp->llp_ipv6addrstr == NULL) {
				syslog(LOG_ERR, "could not save ipv6 static "
				    "address for %s", name);
			}
		}
		newllp->llp_ipv6onlink = ipv6onlink;
		return;
	} else if ((newllp = calloc(1, sizeof (llp_t))) == NULL) {
		syslog(LOG_ERR, "calloc llp: %m");
		return;
	}

	lnamelen = sizeof (newllp->llp_lname);
	if (strlcpy(newllp->llp_lname, name, lnamelen) >= lnamelen) {
		syslog(LOG_ERR, "llp: link name too long; ignoring entry");
		free(newllp);
		return;
	}
	if (ipv4src == IPV4SRC_STATIC) {
		if ((newllp->llp_ipv4addrstr = strdup(addrstr)) == NULL) {
			syslog(LOG_ERR, "malloc ipaddrstr: %m");
			free(newllp);
			return;
		}
	} else {
		newllp->llp_ipv4addrstr = NULL;
	}
	newllp->llp_next = NULL;
	newllp->llp_pri = atomic_add_32_nv(&llp_highest_pri, 1);
	newllp->llp_ipv4src = ipv4src;
	newllp->llp_type = find_if_type(newllp->llp_lname);
	newllp->llp_ipv6onlink = ipv6onlink;

	if (ipv6onlink && ipv6addrstr != NULL) {
		newllp->llp_ipv6addrstr = strdup(ipv6addrstr);
		if (newllp->llp_ipv6addrstr == NULL)
			syslog(LOG_WARNING, "could not store static address %s"
			    "on interface %s", ipv6addrstr, newllp->llp_lname);
	} else {
		newllp->llp_ipv6addrstr = NULL;
	}

	llp_list_append(newllp);

	dprintf("created llp for link %s, pri %d", newllp->llp_lname,
	    newllp->llp_pri);
}

/*
 * Walker function to pass to walk_interface() to find out if
 * an interface description is missing from LLPFILE.  If it is,
 * add it.
 *
 * Currently, IF_TUN type interfaces are special-cased: they are
 * only handled as user-enabled, layered links (which may be created
 * as part of a higher-layer profile, for example).  Thus, they
 * shouldn't be considered when looking at the llp list, so don't
 * add them here.
 */
static void
find_and_add_llp(struct interface *ifp, void *arg)
{
	FILE *fp = (FILE *)arg;

	if (ifp->if_type != IF_TUN && (llp_lookup(ifp->if_name) == NULL)) {
		dprintf("Adding %s to %s", ifp->if_name, LLPFILE);
		add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL);
		/* If we run out of memory, ignore this interface for now. */
		create_and_add_llp(ifp->if_name, IPV4SRC_DHCP, NULL,
		    B_TRUE, NULL);
	}
}

/*
 * This is a very "slow" function.  It uses walk_interface() to find
 * out if any of the interface is missing from the LLPFILE.  For the
 * missing ones, add them to the LLPFILE.
 */
static void
add_missing_if_llp(FILE *fp)
{
	walk_interface(find_and_add_llp, fp);
}

static void
print_llp_list(void)
{
	llp_t *wp;

	dprintf("Walking llp list");
	for (wp = llp_head; wp != NULL; wp = wp->llp_next)
		dprintf("==> %s", wp->llp_lname);
}

/*
 * This function parses /etc/nwam/llp which describes the phase 0 link layer
 * profile.  The file is line oriented with each line containing tab or space
 * delimited fields.  Each address family (IPv4, IPv6) is described on a
 * separate line.
 * The first field is a link name.
 * The second field can be either static, dhcp, ipv6, or noipv6.
 * If the second field is static then the next field is an ipv4 address which
 *    can contain a prefix.  Previous versions of this file could contain a
 *    hostname in this field which is no longer supported.
 * If the second field is dhcp then dhcp will be used on the interface.
 * If the second field is ipv6 then an ipv6 interface is plumbed up.  The
 *    outcome of this is that if offered by the network in.ndpd and dhcp
 *    will conspire to put addresses on additional ipv6 logical interfaces.
 *    If the next field is non-null then it is taken to be an IPv6 address
 *    and possible prefix which are applied to the interface.
 * If the second field is noipv6 then no ipv6 interfaces will be put on that
 *    link.
 */
void
llp_parse_config(void)
{
	static const char STATICSTR[] = "static";
	static const char DHCP[] = "dhcp";
	static const char IPV6[] = "ipv6";
	static const char NOIPV6[] = "noipv6";
	FILE *fp;
	char line[LINE_MAX];
	char *cp, *lasts, *lstr, *srcstr, *addrstr, *v6addrstr;
	int lnum;
	ipv4src_t ipv4src;
	boolean_t ipv6onlink;

	fp = fopen(LLPFILE, "r+");
	if (fp == NULL) {
		if (errno != ENOENT) {
			/*
			 * XXX See comment before create_llp_file() re
			 * better error handling.
			 */
			syslog(LOG_ERR, "open LLP config file: %m");
			return;
		}

		/*
		 * If there is none, we should create one instead.
		 * For now, we will use the order of the interface list
		 * for the priority.  We should have a priority field
		 * in the llp file eventually...
		 */
		create_llp_file();

		/* Now we can try to reopen the file for processing. */
		fp = fopen(LLPFILE, "r+");
		if (fp == NULL) {
			syslog(LOG_ERR, "2nd open LLP config file: %m");
			return;
		}
	}

	if (llp_head != NULL)
		llp_list_free(llp_head);
	llp_head = NULL;

	for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) {
		ipv4src = IPV4SRC_DHCP;
		ipv6onlink = B_FALSE;
		addrstr = NULL;
		v6addrstr = NULL;

		if (line[strlen(line) - 1] == '\n')
			line[strlen(line) - 1] = '\0';

		cp = line;
		while (isspace(*cp))
			cp++;

		if (*cp == '#' || *cp == '\0')
			continue;

		dprintf("parsing llp conf file line %d...", lnum);

		if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) ||
		    ((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) {
			syslog(LOG_ERR, "llp:%d: not enough tokens; "
			    "ignoring entry", lnum);
			continue;
		}
		if (strcasecmp(srcstr, STATICSTR) == 0) {
			if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL ||
			    atoi(addrstr) == 0) { /* crude check for number */
				syslog(LOG_ERR, "llp:%d: missing ipaddr "
				    "for static config; ignoring entry",
				    lnum);
				continue;
			}
			ipv4src = IPV4SRC_STATIC;
		} else if (strcasecmp(srcstr, DHCP) == 0) {
			ipv4src = IPV4SRC_DHCP;
		} else if (strcasecmp(srcstr, IPV6) == 0) {
			ipv6onlink = B_TRUE;
			if ((addrstr = strtok_r(NULL, " \t", &lasts)) != NULL) {
				v6addrstr = strdup(addrstr);
				if (v6addrstr == NULL) {
					syslog(LOG_ERR, "could not store v6 "
					    "static address %s for %s",
					    v6addrstr, lstr);
				}
			} else {
				v6addrstr = NULL;
			}
		} else if (strcasecmp(srcstr, NOIPV6) == 0) {
			ipv6onlink = B_FALSE;
		} else {
			syslog(LOG_ERR, "llp:%d: unrecognized "
			    "field; ignoring entry", lnum);
			continue;
		}

		create_and_add_llp(lstr, ipv4src, addrstr, ipv6onlink,
		    v6addrstr);
	}

	/*
	 * So we have read in the llp file, is there an interface which
	 * it does not describe?  If yes, we'd better add it to the
	 * file for future reference.  Again, since we don't have a
	 * priority field yet, we will add the interface in the order
	 * in the interface list.
	 */
	add_missing_if_llp(fp);

	(void) fclose(fp);

	print_llp_list();
}