llp.c revision b00044a2eb43864b8718585d21949611a2ee59ef
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* 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 is protected by the global llp_lock, which must be
* pthread_mutex_lock()'d before reading or writing the list. Only the main
* thread can write to the list; this allows the main thread to deal with read
* access to structure pointers without holding locks and without the
* complexity of reference counts. All other threads must hold llp_lock for
* the duration of any read access to the data, and must not deal directly in
* structure pointers. (A thread may also hold machine_lock to block the main
* thread entirely in order to manipulate the data; such use is isolated to the
* door interface.)
*
* Functions in this file have comments noting where the main thread alone is
* the caller. These functions do not need to acquire the lock.
*
* If you hold both ifs_lock and llp_lock, you must take ifs_lock first.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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;
/* Accessed only from main thread or with llp_lock held */
llp_t *link_layer_profile;
static struct qelem llp_list;
static llp_t *locked_llp;
/*
* Global variable to hold the highest priority. Need to use the atomic
* integer arithmetic functions to update it.
*/
static uint32_t llp_highest_pri;
static void print_llp_list(void);
void
initialize_llp(void)
{
llp_list.q_forw = llp_list.q_back = &llp_list;
}
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);
}
/*
* This function removes a given LLP from the global list and discards it.
* Called only from the main thread.
*/
void
llp_delete(llp_t *llp)
{
if (pthread_mutex_lock(&llp_lock) == 0) {
if (llp == locked_llp)
locked_llp = NULL;
assert(llp != link_layer_profile);
remque(&llp->llp_links);
(void) pthread_mutex_unlock(&llp_lock);
free(llp->llp_ipv6addrstr);
free(llp->llp_ipv4addrstr);
free(llp);
}
}
static void
llp_list_free(void)
{
int retv;
llp_t *llp;
locked_llp = NULL;
if ((retv = pthread_mutex_lock(&llp_lock)) != 0) {
/* Something very serious is wrong... */
syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %s",
strerror(retv));
return;
}
while (llp_list.q_forw != &llp_list) {
llp = (llp_t *)llp_list.q_forw;
remque(&llp->llp_links);
free(llp->llp_ipv6addrstr);
free(llp->llp_ipv4addrstr);
free(llp);
}
(void) pthread_mutex_unlock(&llp_lock);
}
/*
* Called either from main thread or with llp_lock held.
*/
llp_t *
llp_lookup(const char *link)
{
llp_t *llp;
if (link == NULL)
return (NULL);
for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list;
llp = (llp_t *)llp->llp_links.q_forw) {
if (strcmp(link, llp->llp_lname) == 0)
break;
}
if (llp == (llp_t *)&llp_list)
llp = NULL;
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 || a->llp_links.q_forw == NULL)
return (b);
else if (b == NULL || b->llp_links.q_forw == NULL)
return (a);
/* Check for locked LLP selection for user interface */
if (a == locked_llp)
return (a);
else if (b == locked_llp)
return (b);
if (a->llp_failed && !b->llp_failed)
return (b);
if (!a->llp_failed && b->llp_failed)
return (a);
/*
* Higher priority is represented by a lower number. This seems a
* bit backwards, but for now it makes assigning priorities very easy.
*/
return ((a->llp_pri <= b->llp_pri) ? a : b);
}
/*
* Chooses the highest priority link that corresponds to an available
* interface. Called only in the main thread.
*/
llp_t *
llp_best_avail(void)
{
llp_t *llp, *rtnllp;
if ((rtnllp = locked_llp) == NULL) {
for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list;
llp = (llp_t *)llp->llp_links.q_forw) {
if (is_interface_ok(llp->llp_lname))
rtnllp = llp_high_pri(llp, rtnllp);
}
}
return (rtnllp);
}
/*
* Called only by the main thread. Note that this leaves link_layer_profile
* set to NULL only in the case of abject failure, and then leaves llp_failed
* set.
*/
static void
llp_activate(llp_t *llp)
{
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";
assert(link_layer_profile == NULL);
host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? dhcpstr :
llp->llp_ipv4addrstr;
report_llp_selected(llp->llp_lname);
switch (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr,
llp->llp_ipv6onlink)) {
case SUCCESS:
llp->llp_failed = B_FALSE;
llp->llp_waiting = B_FALSE;
link_layer_profile = llp;
dprintf("llp_activate: activated llp for %s", llp_prnm(llp));
break;
case FAILURE:
llp->llp_failed = B_TRUE;
llp->llp_waiting = B_FALSE;
dprintf("llp_activate: failed to bring up %s", llp_prnm(llp));
report_llp_unselected(llp->llp_lname, dcFailed);
link_layer_profile = NULL;
break;
case WAITING:
llp->llp_failed = B_FALSE;
llp->llp_waiting = B_TRUE;
link_layer_profile = llp;
dprintf("llp_activate: waiting for %s", llp_prnm(llp));
}
}
/*
* 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.
*
* Called only by the main thread.
*/
void
llp_swap(llp_t *newllp, libnwam_diag_cause_t cause)
{
int minpri;
if (newllp == link_layer_profile)
return;
deactivate_upper_layer_profile();
if (link_layer_profile != NULL) {
dprintf("taking down current link layer profile (%s)",
llp_prnm(link_layer_profile));
report_llp_unselected(link_layer_profile->llp_lname, cause);
link_layer_profile->llp_waiting = B_FALSE;
link_layer_profile = NULL;
}
/*
* Establish the new link layer profile. If we have trouble setting
* it, then try to get another. Note that llp_activate sets llp_failed
* on failure, so this loop is guaranteed to terminate.
*/
while (newllp != NULL) {
dprintf("bringing up new link layer profile (%s)",
llp_prnm(newllp));
llp_activate(newllp);
newllp = NULL;
if (link_layer_profile == NULL &&
(newllp = llp_best_avail()) != NULL &&
newllp->llp_failed)
newllp = NULL;
}
/*
* Knock down all interfaces that are at a lower (higher-numbered)
* priority than the new one. If there isn't a new one, then leave
* everything as it is.
*/
if (link_layer_profile == NULL) {
minpri = -1;
if (locked_llp != NULL)
dprintf("taking down all but %s", llp_prnm(locked_llp));
} else {
minpri = link_layer_profile->llp_pri;
dprintf("taking down remaining interfaces below priority %d",
minpri);
}
for (newllp = (llp_t *)llp_list.q_forw; newllp != (llp_t *)&llp_list;
newllp = (llp_t *)newllp->llp_links.q_forw) {
if (newllp == link_layer_profile)
continue;
if ((link_layer_profile != NULL && newllp->llp_pri > minpri) ||
(locked_llp != NULL && newllp != locked_llp))
takedowninterface(newllp->llp_lname, cause);
else
clear_cached_address(newllp->llp_lname);
}
}
/*
* Create the named LLP with default settings. Called only in main thread.
*/
llp_t *
llp_add(const char *name)
{
int retv;
llp_t *llp;
if ((llp = calloc(1, sizeof (llp_t))) == NULL) {
syslog(LOG_ERR, "cannot allocate LLP: %m");
return (NULL);
}
if (strlcpy(llp->llp_lname, name, sizeof (llp->llp_lname)) >=
sizeof (llp->llp_lname)) {
syslog(LOG_ERR, "llp: link name '%s' too long; ignoring entry",
name);
free(llp);
return (NULL);
}
llp->llp_fileorder = llp->llp_pri =
atomic_add_32_nv(&llp_highest_pri, 1);
llp->llp_ipv4src = IPV4SRC_DHCP;
llp->llp_type = find_if_type(llp->llp_lname);
llp->llp_ipv6onlink = B_TRUE;
/*
* 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) {
syslog(LOG_ERR, "llp: wrong type of interface for %s", name);
free(llp);
return (NULL);
}
if ((retv = pthread_mutex_lock(&llp_lock)) != 0) {
/* Something very serious is wrong... */
syslog(LOG_ERR, "llp: cannot lock mutex: %s", strerror(retv));
free(llp);
return (NULL);
}
insque(&llp->llp_links, llp_list.q_back);
(void) pthread_mutex_unlock(&llp_lock);
dprintf("created llp for link %s, priority %d", llp->llp_lname,
llp->llp_pri);
return (llp);
}
/*
* 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.
*
* ifs_lock is held when this function is called. Called only in main thread.
*/
static void
find_and_add_llp(struct interface *ifp, void *arg)
{
FILE *fp = arg;
if (ifp->if_type != IF_TUN && llp_lookup(ifp->if_name) == NULL) {
switch (ifp->if_family) {
case AF_INET:
(void) fprintf(fp, "%s\tdhcp\n", ifp->if_name);
break;
case AF_INET6:
(void) fprintf(fp, "%s\tipv6\n", ifp->if_name);
break;
default:
syslog(LOG_ERR, "interface %s family %d?!",
ifp->if_name, ifp->if_family);
return;
}
dprintf("Added %s to %s", ifp->if_name, LLPFILE);
/* If we run out of memory, ignore this interface for now. */
(void) llp_add(ifp->if_name);
}
}
static void
print_llp_list(void)
{
llp_t *wp;
dprintf("Walking llp list");
for (wp = (llp_t *)llp_list.q_forw; wp != (llp_t *)&llp_list;
wp = (llp_t *)wp->llp_links.q_forw)
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, noipv6, or priority.
* 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.
* If the second field is priority, then the next field is an integer
* specifying the link priority.
*
* Called only in main thread.
*/
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";
static const char PRIORITY[] = "priority";
FILE *fp;
char line[LINE_MAX];
char *cp, *lasts, *lstr, *srcstr, *addrstr;
int lnum;
llp_t *llp;
/* Create the NWAM directory in case it does not exist. */
if (mkdir(LLPDIRNAME, LLPDIRMODE) != 0 &&
errno != EEXIST) {
syslog(LOG_ERR, "could not create %s: %m", LLPDIRNAME);
return;
}
fp = fopen(LLPFILE, "r+");
if (fp == NULL) {
if (errno != ENOENT) {
syslog(LOG_ERR, "open LLP config file: %m");
return;
}
if ((fp = fopen(LLPFILE, "w+")) == NULL) {
syslog(LOG_ERR, "create LLP config file: %m");
return;
}
}
llp_list_free();
for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) {
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 ((llp = llp_lookup(lstr)) == NULL &&
(llp = llp_add(lstr)) == NULL) {
syslog(LOG_ERR, "llp:%d: cannot add 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", lnum);
} else if ((addrstr = strdup(addrstr)) == NULL) {
syslog(LOG_ERR, "llp:%d: cannot save address",
lnum);
} else {
free(llp->llp_ipv4addrstr);
llp->llp_ipv4src = IPV4SRC_STATIC;
llp->llp_ipv4addrstr = addrstr;
}
} else if (strcasecmp(srcstr, DHCP) == 0) {
llp->llp_ipv4src = IPV4SRC_DHCP;
} else if (strcasecmp(srcstr, IPV6) == 0) {
llp->llp_ipv6onlink = B_TRUE;
if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) {
(void) 0;
} else if ((addrstr = strdup(addrstr)) == NULL) {
syslog(LOG_ERR, "llp:%d: cannot save address",
lnum);
} else {
free(llp->llp_ipv6addrstr);
llp->llp_ipv6addrstr = addrstr;
}
} else if (strcasecmp(srcstr, NOIPV6) == 0) {
llp->llp_ipv6onlink = B_FALSE;
} else if (strcasecmp(srcstr, PRIORITY) == 0) {
if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) {
syslog(LOG_ERR,
"llp:%d: missing priority value", lnum);
} else {
llp->llp_pri = atoi(addrstr);
}
} else {
syslog(LOG_ERR, "llp:%d: unrecognized field '%s'", lnum,
srcstr);
}
}
/*
* 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.
*/
walk_interface(find_and_add_llp, fp);
(void) fclose(fp);
print_llp_list();
}
/*
* Called only from the main thread.
*/
void
llp_add_file(const llp_t *llp)
{
FILE *fp;
if ((fp = fopen(LLPFILE, "a")) == NULL)
return;
(void) fprintf(fp, "%s\tdhcp\n", llp->llp_lname);
(void) fclose(fp);
}
/*
* This function rewrites the LLP configuration file entry for a given
* interface and keyword. If the keyword is present, then it is updated if
* removeonly is B_FALSE, otherwise it's removed. If the keyword is not
* present, then it is added immediately after the last entry for that
* interface if removeonly is B_FALSE, otherwise no action is taken. User
* comments are preserved.
*
* To preserve file integrity, this is called only from the main thread.
*/
static void
llp_update_config(const char *ifname, const char *keyword, const char *optval,
boolean_t removeonly)
{
FILE *fpin, *fpout;
char line[LINE_MAX];
char *cp, *lstr, *keystr, *valstr, *lasts;
boolean_t matched_if, copying;
long match_pos;
if ((fpin = fopen(LLPFILE, "r")) == NULL)
return;
if ((fpout = fopen(LLPFILETMP, "w")) == NULL) {
syslog(LOG_ERR, "create LLP temporary config file: %m");
(void) fclose(fpin);
return;
}
matched_if = copying = B_FALSE;
restart:
while (fgets(line, sizeof (line), fpin) != NULL) {
cp = line + strlen(line) - 1;
if (cp >= line && *cp == '\n')
*cp = '\0';
cp = line;
while (isspace(*cp))
cp++;
lstr = NULL;
if (copying || *cp == '#' ||
(lstr = strtok_r(cp, " \t", &lasts)) == NULL ||
strcmp(lstr, ifname) != 0) {
if (!matched_if || copying) {
/*
* It's ugly to write through the pointer
* returned as the third argument of strtok_r,
* but doing so saves a data copy.
*/
if (lstr != NULL && lasts != NULL)
lasts[-1] = '\t';
(void) fprintf(fpout, "%s\n", line);
}
continue;
}
if (lasts != NULL)
lasts[-1] = '\t';
/*
* If we've found the keyword, then process removal or update
* of the value.
*/
if ((keystr = strtok_r(NULL, " \t", &lasts)) != NULL &&
strcmp(keystr, keyword) == 0) {
matched_if = copying = B_TRUE;
if (removeonly)
continue;
valstr = strtok_r(NULL, " \t", &lasts);
if ((valstr == NULL && optval == NULL) ||
(valstr != NULL && optval != NULL &&
strcmp(valstr, optval) == 0)) {
/* Value identical; abort update */
goto no_change;
}
if (optval == NULL) {
(void) fprintf(fpout, "%s\t%s\n", ifname,
keyword);
} else {
(void) fprintf(fpout, "%s\t%s %s\n", ifname,
keyword, optval);
}
continue;
}
/* Otherwise, record the last possible insertion point */
matched_if = B_TRUE;
match_pos = ftell(fpin);
if (lasts != NULL)
lasts[-1] = '\t';
(void) fprintf(fpout, "%s\n", line);
}
if (!copying) {
/* keyword not encountered; we're done if deleting */
if (removeonly)
goto no_change;
/* need to add keyword and value */
if (optval == NULL) {
(void) fprintf(fpout, "%s\t%s\n", ifname, keyword);
} else {
(void) fprintf(fpout, "%s\t%s %s\n", ifname, keyword,
optval);
}
/* copy the rest of the file */
(void) fseek(fpin, match_pos, SEEK_SET);
copying = B_TRUE;
goto restart;
}
(void) fclose(fpin);
(void) fclose(fpout);
if (rename(LLPFILETMP, LLPFILE) != 0) {
syslog(LOG_ERR, "rename LLP temporary config file: %m");
(void) unlink(LLPFILETMP);
}
return;
no_change:
(void) fclose(fpin);
(void) fclose(fpout);
(void) unlink(LLPFILETMP);
}
/*
* This is called back from the main thread by the state machine.
*/
void
llp_write_changed_priority(llp_t *llp)
{
if (llp->llp_pri == llp->llp_fileorder) {
llp_update_config(llp->llp_lname, "priority", NULL, B_TRUE);
} else {
char prival[32];
(void) snprintf(prival, sizeof (prival), "%d", llp->llp_pri);
llp_update_config(llp->llp_lname, "priority", prival, B_FALSE);
}
}
/*
* Called by the door interface: set LLP priority and schedule an LLP update if
* this interface has changed.
*/
int
set_llp_priority(const char *ifname, int prio)
{
llp_t *llp;
int retv;
if (prio < 0)
return (EINVAL);
if ((retv = pthread_mutex_lock(&llp_lock)) != 0)
return (retv);
if ((llp = llp_lookup(ifname)) != NULL) {
llp->llp_failed = B_FALSE;
if (llp->llp_pri != prio) {
llp->llp_pri = prio;
(void) np_queue_add_event(EV_USER, ifname);
}
retv = 0;
} else {
retv = ENXIO;
}
(void) pthread_mutex_unlock(&llp_lock);
return (retv);
}
/*
* Called by the door interface: set a locked LLP and schedule an LLP update if
* the locked LLP has changed.
*/
int
set_locked_llp(const char *ifname)
{
llp_t *llp;
int retv;
if ((retv = pthread_mutex_lock(&llp_lock)) != 0)
return (retv);
if (ifname[0] == '\0') {
if (locked_llp != NULL) {
ifname = locked_llp->llp_lname;
locked_llp = NULL;
(void) np_queue_add_event(EV_USER, ifname);
}
} else if ((llp = llp_lookup(ifname)) != NULL) {
locked_llp = llp;
if (llp != link_layer_profile)
(void) np_queue_add_event(EV_USER, ifname);
} else {
retv = ENXIO;
}
(void) pthread_mutex_unlock(&llp_lock);
return (retv);
}
/* Copy string to pre-allocated buffer. */
static void
strinsert(char **dest, const char *src, char **buf)
{
if (*dest != NULL) {
*dest = strcpy(*buf, src);
*buf += strlen(src) + 1;
}
}
/*
* Sample the list of LLPs and copy to a single buffer for return through the
* door interface.
*/
llp_t *
get_llp_list(size_t *lsize, uint_t *countp, char *selected, char *locked)
{
llp_t *llplist, *llpl, *llp;
char *strptr;
uint_t nllp;
size_t strspace;
int retv;
*lsize = 0;
if ((retv = pthread_mutex_lock(&llp_lock)) != 0) {
errno = retv;
return (NULL);
}
(void) strlcpy(selected, link_layer_profile == NULL ? "" :
link_layer_profile->llp_lname, LIFNAMSIZ);
(void) strlcpy(locked, locked_llp == NULL ? "" :
locked_llp->llp_lname, LIFNAMSIZ);
nllp = 0;
strspace = 0;
for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list;
llp = (llp_t *)llp->llp_links.q_forw) {
nllp++;
if (llp->llp_ipv4addrstr != NULL)
strspace += strlen(llp->llp_ipv4addrstr) + 1;
if (llp->llp_ipv6addrstr != NULL)
strspace += strlen(llp->llp_ipv6addrstr) + 1;
}
*countp = nllp;
/* Note that malloc doesn't guarantee a NULL return for zero count */
llplist = nllp == 0 ? NULL :
malloc(sizeof (*llplist) * nllp + strspace);
if (llplist != NULL) {
*lsize = sizeof (*llplist) * nllp + strspace;
llpl = llplist;
strptr = (char *)(llplist + nllp);
for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list;
llp = (llp_t *)llp->llp_links.q_forw) {
*llpl = *llp;
strinsert(&llpl->llp_ipv4addrstr, llp->llp_ipv4addrstr,
&strptr);
strinsert(&llpl->llp_ipv6addrstr, llp->llp_ipv6addrstr,
&strptr);
llpl++;
}
}
(void) pthread_mutex_unlock(&llp_lock);
/* Add in the special door-only state flags */
llpl = llplist;
while (nllp-- > 0) {
get_interface_state(llpl->llp_lname, &llpl->llp_dhcp_failed,
&llpl->llp_link_up);
if (llpl->llp_type == IF_WIRELESS) {
get_wireless_state(llpl->llp_lname,
&llpl->llp_need_wlan, &llpl->llp_need_key);
}
llpl++;
}
return (llplist);
}
/*
* This is called for the special case when there are outstanding requests sent
* to the user interface, and the user interface disappears. We handle this
* case by re-running bringupinterface() without deselecting. That function
* will call the wireless and DHCP-related parts again, and they should proceed
* in automatic mode, because the UI is now gone.
*
* Called only by the main thread or by a thread holding machine_lock.
*/
void
llp_reselect(void)
{
llp_t *llp;
const char *host;
/*
* If there's no active profile, or if the active profile isn't waiting
* on the UI, then just return; nothing to do.
*/
if ((llp = link_layer_profile) == NULL || !llp->llp_waiting)
return;
host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? "dhcp" :
llp->llp_ipv4addrstr;
dprintf("llp_reselect: bringing up %s", llp_prnm(llp));
switch (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr,
llp->llp_ipv6onlink)) {
case SUCCESS:
llp->llp_failed = B_FALSE;
llp->llp_waiting = B_FALSE;
dprintf("llp_reselect: activated llp for %s", llp_prnm(llp));
break;
case FAILURE:
llp->llp_failed = B_TRUE;
llp->llp_waiting = B_FALSE;
dprintf("llp_reselect: failed to bring up %s", llp_prnm(llp));
report_llp_unselected(llp->llp_lname, dcFailed);
link_layer_profile = NULL;
break;
case WAITING:
llp->llp_failed = B_FALSE;
dprintf("llp_reselect: waiting for %s", llp_prnm(llp));
}
}
/*
* This is used by the wireless module to check on the selected LLP. We don't
* do periodic rescans if a wireless interface is current and if its connection
* state is good.
*/
void
llp_get_name_and_type(char *ifname, size_t ifnlen,
libnwam_interface_type_t *iftype)
{
*ifname = '\0';
*iftype = IF_UNKNOWN;
if (pthread_mutex_lock(&llp_lock) == 0) {
if (link_layer_profile != NULL) {
(void) strlcpy(ifname, link_layer_profile->llp_lname,
ifnlen);
*iftype = link_layer_profile->llp_type;
}
(void) pthread_mutex_unlock(&llp_lock);
}
}
/*
* This is called by the interface.c module to check if an interface needs to
* run DHCP. It's intentionally called without ifs_lock held.
*/
libnwam_ipv4src_t
llp_get_ipv4src(const char *ifname)
{
libnwam_ipv4src_t src = IPV4SRC_DHCP;
llp_t *llp;
if (pthread_mutex_lock(&llp_lock) == 0) {
if ((llp = llp_lookup(ifname)) != NULL)
src = llp->llp_ipv4src;
(void) pthread_mutex_unlock(&llp_lock);
}
return (src);
}
/*
* Dump out the LLP state via debug messages.
*/
void
print_llp_status(void)
{
llp_t *llp;
if (pthread_mutex_lock(&llp_lock) == 0) {
if (link_layer_profile == NULL)
dprintf("no LLP selected");
else
dprintf("LLP %s selected",
link_layer_profile->llp_lname);
if (locked_llp == NULL)
dprintf("no LLP locked");
else
dprintf("LLP %s locked", locked_llp->llp_lname);
for (llp = (llp_t *)llp_list.q_forw;
llp != (llp_t *)&llp_list;
llp = (llp_t *)llp->llp_links.q_forw) {
dprintf("LLP %s pri %d file order %d type %d "
"%sfailed %swaiting src %d v4addr %s v6addr %s "
"v6 %son-link",
llp->llp_lname, llp->llp_pri, llp->llp_fileorder,
(int)llp->llp_type, llp->llp_failed ? "" : "not ",
llp->llp_waiting ? "" : "not ",
(int)llp->llp_ipv4src,
STRING(llp->llp_ipv4addrstr),
STRING(llp->llp_ipv6addrstr),
llp->llp_ipv6onlink ? "not " : "");
}
(void) pthread_mutex_unlock(&llp_lock);
}
}