view usr/src/cmd/cmd-inet/lib/nwamd/interface.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 20cddb15a804
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 interfaces, the
 * list of interfaces present on the system, and upper layer profiles;
 * and various support functions.  It also contains the functions used
 * to display various bits of informations and queries for the user
 * using /usr/bin/zenity, and a set of functions to read property
 * values stored in the SMF repository.  Finally, it contains the
 * functions required for the "gather info" threads.
 *
 * The daemon maintains a list of structures that represent each IPv4
 * interface found on the system (after doing 'ifconfig -a plumb').
 * This list represents the objects manipulated by the daemon; while
 * the list of llp_t structures represents the configuration details
 * requested by the user (either the automatic defaults or entries in
 * /etc/nwam/llp).  IPv6 interfaces are not tracked in the interfaces
 * list; rather, when the decision is made to make an interface active,
 * IPv6 is brought up in addition to IPv4 (assuming the LLP configuration
 * includes IPv6; this is the default for automatic configuration).
 *
 * Interfaces are brought up and torn down by a sequence of ifconfig
 * commands (currently posix_spawn'd() by nwamd; the longer-term direction
 * here is to use libinetcfg).
 *
 * Upper Layer Profile management is controlled by user-provided scripts,
 * which should be created in /etc/nwam/ulp.  One script,
 * /etc/nwam/ulp/check-conditions, checks the current network setup and
 * returns the name of the ULP which should be active under the current
 * conditions.  A ULP is specified by two scripts, found in
 * /etc/nwam/ulp/<ulp name>: bringup and teardown.  All scripts are
 * optional; if they do not exist or are not executable, nwamd will
 * simply move on.
 *
 * When an interface has been successfully brought up (signalled by the
 * assignment of an IP address to the interface), the daemon will first
 * teardown the existing ULP (if there is one) by running the teardown
 * script for that ULP.  It will then run the check-conditions script;
 * if the name of a ULP is returned, it runs the bringup script for that
 * ULP.
 *
 * A "gather info" thread is initiated for an interface when it becomes
 * available.  For a wired interface, "available" means the IFF_RUNNING
 * flag is set; wireless interfaces are considered to always be available,
 * so a wireless interface's gather info thread will run once, when it is
 * found at startup.  This thread will do a scan on a wireless interface,
 * and initiate DHCP on a wired interface.  It will then generate an event
 * for the state machine that indicates the availability of a new interface.
 */

#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/sockio.h>
#include <syslog.h>
#include <unistd.h>
#include <libscf.h>
#include <utmpx.h>
#include <pwd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <inetcfg.h>
#include <locale.h>
#include <libintl.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/sysmacros.h>
#include <libdllink.h>

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

static struct interface *ifs_head = NULL;
static struct interface *ifs_wired = NULL;
static struct interface *ifs_wireless = NULL;

static char upper_layer_profile[MAXHOSTNAMELEN];

static void print_interface_list();
static struct interface *get_next_interface(struct interface *);

#define	IPFILTER_FMRI	"svc:/network/ipfilter:default"
#define	LOOPBACK_IF	"lo0"

void
display(const char *msg)
{
	char cmd[1024];

	dprintf("display('%s')", STRING(msg));
	if (valid_graphical_user(B_FALSE)) {
		(void) snprintf(cmd, sizeof (cmd), "--text=\"%s\"", msg);
		(void) start_child(ZENITY, "--info", cmd, NULL);
	} else {
		syslog(LOG_INFO, "%s", msg);
	}
}

void
show_if_status(const char *ifname)
{
	char msg[128];
	icfg_if_t intf;
	icfg_handle_t h;
	struct sockaddr_in sin;
	socklen_t addrlen = sizeof (struct sockaddr_in);
	int prefixlen = 0;

	(void) strlcpy(intf.if_name, ifname, sizeof (intf.if_name));
	/* We only display new addr info for v4 interfaces */
	intf.if_protocol = AF_INET;
	if (icfg_open(&h, &intf) != ICFG_SUCCESS) {
		syslog(LOG_ERR, "icfg_open failed on interface %s", ifname);
		return;
	}
	if (icfg_get_addr(h, (struct sockaddr *)&sin, &addrlen, &prefixlen,
	    B_TRUE) != ICFG_SUCCESS) {
		syslog(LOG_ERR, "icfg_get_addr failed on interface %s", ifname);
		icfg_close(h);
		return;
	}
	icfg_close(h);
	(void) snprintf(msg, sizeof (msg),
	    gettext("brought interface %s up, got address %s"), ifname,
	    inet_ntoa(sin.sin_addr));
	display(msg);
}

/*
 * If this interface matches the currently active llp, return B_TRUE.
 * Otherwise, return B_FALSE.
 */
boolean_t
interface_is_active(const struct interface *ifp)
{
	if (link_layer_profile == NULL || ifp == NULL)
		return (B_FALSE);

	return (strcmp(ifp->if_name, link_layer_profile->llp_lname) == 0);
}

/*
 * Execute 'ifconfig ifname dhcp wait 0'.
 */
static void
start_dhcp(struct interface *ifp)
{
	int res;
	uint32_t now_s;
	uint64_t timer_s;

	if ((ifp->if_lflags & IF_DHCPSTARTED) != 0) {
		dprintf("start_dhcp: already started; returning");
		return;
	}
	ifp->if_lflags |= IF_DHCPSTARTED;

	(void) start_child(IFCONFIG, ifp->if_name, "dhcp", "wait", "0", NULL);

	/* start dhcp timer */
	res = lookup_count_property(OUR_PG, "dhcp_wait_time", &timer_s);
	if (res == -1)
		timer_s = NWAM_DEFAULT_DHCP_WAIT_TIME;

	now_s = NSEC_TO_SEC(gethrtime());
	ifp->if_timer_expire = now_s + timer_s;

	start_timer(now_s, timer_s);
}

static boolean_t
check_svc_up(const char *fmri, int wait_time)
{
	int i;
	char *state;

	for (i = 1; i <= wait_time; i++) {
		state = smf_get_state(fmri);
		if (strcmp(SCF_STATE_STRING_ONLINE, state) == 0) {
			free(state);
			return (B_TRUE);
		}
		free(state);
		(void) sleep(1);
	}
	return (B_FALSE);
}

boolean_t
ulp_is_active(void)
{
	return (upper_layer_profile[0] != '\0');
}

/*
 * Inputs:
 *   res is a pointer to the scf_resources_t to be released.
 */
static void
release_scf_resources(scf_resources_t *res)
{
	scf_value_destroy(res->sr_val);
	scf_property_destroy(res->sr_prop);
	scf_pg_destroy(res->sr_pg);
	scf_snapshot_destroy(res->sr_snap);
	scf_instance_destroy(res->sr_inst);
	(void) scf_handle_unbind(res->sr_handle);
	scf_handle_destroy(res->sr_handle);
}

/*
 * Inputs:
 *   lpg is the property group to look up
 *   lprop is the property within that group to look up
 * Outputs:
 *   res is a pointer to an scf_resources_t.  This is an internal
 *   structure that holds all the handles needed to get a specific
 *   property from the running snapshot; on a successful return it
 *   contains the scf_value_t that should be passed to the desired
 *   scf_value_get_foo() function, and must be freed after use by
 *   calling release_scf_resources().  On a failure return, any
 *   resources that may have been assigned to res are released, so
 *   the caller does not need to do any cleanup in the failure case.
 * Returns:
 *    0 on success
 *   -1 on failure
 */
static int
get_property_value(const char *lpg, const char *lprop, scf_resources_t *res)
{
	res->sr_inst = NULL;
	res->sr_snap = NULL;
	res->sr_pg = NULL;
	res->sr_prop = NULL;
	res->sr_val = NULL;

	if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) {
		syslog(LOG_ERR, "scf_handle_create() failed: %s",
		    scf_strerror(scf_error()));
		return (-1);
	}

	if (scf_handle_bind(res->sr_handle) != 0) {
		scf_handle_destroy(res->sr_handle);
		syslog(LOG_ERR, "scf_handle_destroy() failed: %s",
		    scf_strerror(scf_error()));
		return (-1);
	}
	if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) {
		syslog(LOG_ERR, "scf_instance_create() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if (scf_handle_decode_fmri(res->sr_handle, OUR_FMRI, NULL, NULL,
	    res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) {
		syslog(LOG_ERR, "scf_handle_decode_fmri() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if ((res->sr_snap = scf_snapshot_create(res->sr_handle)) == NULL) {
		syslog(LOG_ERR, "scf_snapshot_create() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if (scf_instance_get_snapshot(res->sr_inst, "running",
	    res->sr_snap) != 0) {
		syslog(LOG_ERR, "scf_instance_get_snapshot() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) {
		syslog(LOG_ERR, "scf_pg_create() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if (scf_instance_get_pg_composed(res->sr_inst, res->sr_snap, lpg,
	    res->sr_pg) != 0) {
		syslog(LOG_ERR, "scf_instance_get_pg_composed(%s) failed: %s",
		    lpg, scf_strerror(scf_error()));
		goto failure;
	}
	if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) {
		syslog(LOG_ERR, "scf_property_create() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if (scf_pg_get_property(res->sr_pg, lprop, res->sr_prop) != 0) {
		syslog(LOG_ERR, "scf_pg_get_property(%s) failed: %s",
		    lprop, scf_strerror(scf_error()));
		goto failure;
	}
	if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) {
		syslog(LOG_ERR, "scf_value_create() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	if (scf_property_get_value(res->sr_prop, res->sr_val) != 0) {
		syslog(LOG_ERR, "scf_property_get_value() failed: %s",
		    scf_strerror(scf_error()));
		goto failure;
	}
	return (0);

failure:
	release_scf_resources(res);
	return (-1);
}

/*
 * Inputs:
 *   lpg is the property group to look up
 *   lprop is the property within that group to look up
 * Outputs:
 *   answer is a pointer to the property value
 * Returns:
 *    0 on success
 *   -1 on failure
 * If successful, the property value is retured in *answer.
 * Otherwise, *answer is undefined, and it is up to the caller to decide
 * how to handle that case.
 */
int
lookup_boolean_property(const char *lpg, const char *lprop, boolean_t *answer)
{
	int result = -1;
	scf_resources_t res;
	uint8_t prop_val;

	if (get_property_value(lpg, lprop, &res) != 0) {
		/*
		 * an error was already logged by get_property_value,
		 * and it released any resources assigned to res before
		 * returning.
		 */
		return (result);
	}
	if (scf_value_get_boolean(res.sr_val, &prop_val) != 0) {
		syslog(LOG_ERR, "scf_value_get_boolean() failed: %s",
		    scf_strerror(scf_error()));
		goto cleanup;
	}
	*answer = (boolean_t)prop_val;
	dprintf("lookup_boolean_property(%s, %s) returns %s", lpg, lprop,
	    *answer ? "TRUE" : "FALSE");
	result = 0;
cleanup:
	release_scf_resources(&res);
	return (result);
}

/*
 * Inputs:
 *   lpg is the property group to look up
 *   lprop is the property within that group to look up
 * Outputs:
 *   answer is a pointer to the property value
 * Returns:
 *    0 on success
 *   -1 on failure
 * If successful, the property value is retured in *answer.
 * Otherwise, *answer is undefined, and it is up to the caller to decide
 * how to handle that case.
 */
int
lookup_count_property(const char *lpg, const char *lprop, uint64_t *answer)
{
	int result = -1;
	scf_resources_t res;

	if (get_property_value(lpg, lprop, &res) != 0) {
		/*
		 * an error was already logged by get_property_value,
		 * and it released any resources assigned to res before
		 * returning.
		 */
		return (result);
	}
	if (scf_value_get_count(res.sr_val, answer) != 0) {
		syslog(LOG_ERR, "scf_value_get_count() failed: %s",
		    scf_strerror(scf_error()));
		goto cleanup;
	}
	dprintf("lookup_count_property(%s, %s) returns %lld", lpg, lprop,
	    *answer);
	result = 0;
cleanup:
	release_scf_resources(&res);
	return (result);
}

void
activate_upper_layer_profile(boolean_t do_dhcp, const char *ifname)
{
	FILE *f;
	char buffer[1024];
	size_t buflen;
	size_t offset;
	const char bringup[] = "/bringup";

	if (do_dhcp) {
		boolean_t should;
		int res;

		res = lookup_boolean_property(OUR_PG, "use_net_svc", &should);
		/*
		 * If the look-up failed, try anyway: only avoid this if we
		 * know for sure not to.
		 */
		if ((res == 0 && should) || (res == -1)) {
			if (check_svc_up(NET_SVC_FMRI, 5)) {
				(void) start_child(NET_SVC_METHOD, "start",
				    ifname, NULL);
			} else {
				syslog(LOG_WARNING, "timed out when waiting "
				    "for %s to come up, start method %s not "
				    "executed", NET_SVC_FMRI, NET_SVC_METHOD);
			}
		}
	}
	f = popen(ULP_DIR "/check-conditions", "r");
	if (f == NULL)
		return;
	/*
	 * We want to build a path to the user's upper layer profile script
	 * that looks like ULP_DIR "/<string we read here>/bringup".  If we
	 * leave some space at the beginning of this buffer for ULP_DIR "/"
	 * that saves us some shuffling later.
	 */
	offset = sizeof (ULP_DIR);
	if (fgets(buffer + offset,
	    MIN(sizeof (upper_layer_profile), sizeof (buffer) - offset),
	    f) == NULL) {
		(void) pclose(f);
		return; /* EOF before anything read */
	}
	(void) pclose(f);
	(void) memcpy(buffer, ULP_DIR "/", sizeof (ULP_DIR));
	buflen = strlen(buffer);
	if (buffer[buflen - 1] == '\n')
		buffer[--buflen] = '\0';
	(void) memcpy(upper_layer_profile, buffer + offset,
	    buflen + 1 - offset);
	(void) strlcpy(buffer + buflen, bringup, sizeof (buffer) - buflen);
	(void) start_child(PFEXEC, "-P", "basic", buffer, NULL);

	syslog(LOG_NOTICE, "upper layer profile %s activated",
	    upper_layer_profile);
}

void
deactivate_upper_layer_profile(void)
{
	char buffer[1024];

	/*
	 * If ULP wasn't defined...
	 */
	if (!ulp_is_active())
		return;

	(void) snprintf(buffer, sizeof (buffer), ULP_DIR "/%s/teardown",
	    upper_layer_profile);
	(void) start_child(PFEXEC, "-P", "basic", buffer, NULL);

	syslog(LOG_NOTICE, "upper layer profile %s deactivated",
	    upper_layer_profile);

	upper_layer_profile[0] = '\0';
}

/*
 * Returns B_TRUE if the interface is successfully brought up;
 * B_FALSE if bringup fails.
 */
boolean_t
bringupinterface(const char *ifname, const char *host, const char *ipv6addr,
    boolean_t ipv6onlink)
{
	boolean_t do_dhcp;
	struct interface *intf;
	uint64_t ifflags;

	intf = get_interface(ifname);
	if (intf == NULL) {
		syslog(LOG_ERR, "could not bring up interface %s: not in list",
		    ifname);
		return (B_FALSE);
	}

	/* check current state; no point going on if flags are 0 */
	if ((ifflags = get_ifflags(ifname, intf->if_family)) == 0) {
		dprintf("bringupinterface(%s): get_ifflags() returned 0",
		    ifname);
		return (B_FALSE);
	}

	/*
	 * If the link layer profile says that we want v6 then plumb it and
	 * bring it up; if there's a static address, configure it as well.
	 */
	if (ipv6onlink) {
		dprintf("bringupinterface: configuring ipv6");
		(void) start_child(IFCONFIG, ifname, "inet6", "plumb", "up",
		    NULL);
		if (ipv6addr) {
			(void) start_child(IFCONFIG, ifname, "inet6", "addif",
			    ipv6addr, "up", NULL);
		}
	}

	do_dhcp = (strcmp(host, "dhcp") == 0);

	/*
	 * If we need to use DHCP and DHCP is already controlling
	 * the interface, we don't need to do anything.
	 */
	if (do_dhcp && (ifflags & IFF_DHCPRUNNING) != 0) {
		dprintf("bringupinterface: nothing to do");
		return (B_TRUE);
	}

	if (intf->if_type == IF_WIRELESS) {
		if (!handle_wireless_lan(ifname)) {
			syslog(LOG_INFO, "Could not connect to any WLAN, not "
			    "bringing %s up", ifname);
			return (B_FALSE);
		}
	}

	if (do_dhcp) {
		start_dhcp(intf);
	} else {
		(void) start_child(IFCONFIG, ifname, host, NULL);
		(void) start_child(IFCONFIG, ifname, "up", NULL);
	}

	return (B_TRUE);
}

void
takedowninterface(const char *ifname, boolean_t dhcp, boolean_t popup,
    boolean_t v6onlink)
{
	uint64_t flags;
	struct interface *ifp;

	dprintf("takedowninterface(%s, %s, %s, %s)", ifname,
	    BOOLEAN_TO_STRING(dhcp), BOOLEAN_TO_STRING(popup),
	    BOOLEAN_TO_STRING(v6onlink));

	if ((ifp = get_interface(ifname)) == NULL) {
		dprintf("takedowninterface: can't find interface struct for %s",
		    ifname);
	} else {
		if (ifp->if_lflags & IF_DHCPFAILED) {
			/*
			 * We're here because of a dhcp failure, and
			 * we actually want dhcp to keep trying.  So
			 * don't take the interface down.
			 */
			dprintf("takedowninterface: still trying for dhcp on "
			    "%s, so will not take down interface", ifname);
			return;
		}
	}

	flags = get_ifflags(ifname, AF_INET);

	if (dhcp) {
		if ((flags & IFF_DHCPRUNNING) != 0) {
			/*
			 * We generally prefer doing a release, as that
			 * tells the server that it can relinquish the
			 * lease, whereas drop is just a client-side
			 * operation.  But if we never came up, release
			 * will fail, because dhcpagent does not allow
			 * an interface which is selecting to release,
			 * so we have to drop in that circumstance.  So
			 * try release first, then fall back to drop.
			 */
			if (start_child(IFCONFIG, ifname, "dhcp", "release",
			    NULL) != 0) {
				(void) start_child(IFCONFIG, ifname, "dhcp",
				    "drop", NULL);
			}
		}
	} else {
		if ((flags & IFF_UP) != 0)
			(void) start_child(IFCONFIG, ifname, "down", NULL);
		/* need to unset a statically configured addr */
		(void) start_child(IFCONFIG, ifname, "0.0.0.0", "netmask",
		    "0", "broadcast", "0.0.0.0", NULL);
	}

	if (v6onlink) {
		/*
		 * Unplumbing the link local interface causes dhcp and ndpd to
		 * remove other addresses they have added.
		 */
		(void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL);
	}

	if (find_if_type(ifname) == IF_WIRELESS)
		(void) dladm_wlan_disconnect(ifname);

	dprintf("takedown interface, free cached ip address");
	free(ifp->if_ipaddr);
	ifp->if_ipaddr = NULL;
	if (popup) {
		char msg[64]; /* enough to hold this string */

		(void) snprintf(msg, sizeof (msg),
		    gettext("took interface %s down"), ifname);
		display(msg);
	}
}

/*
 * Take down all known interfaces.  If ignore_if is non-null, an
 * active (IFF_UP) interface whose name matches ignore_if will *not*
 * be taken down.
 */
void
take_down_all_ifs(const char *ignore_if)
{
	struct interface *ifp;
	uint64_t flags;
	boolean_t ignore_set = (ignore_if != NULL);

	deactivate_upper_layer_profile();

	for (ifp = get_next_interface(NULL); ifp != NULL;
	    ifp = get_next_interface(ifp)) {
		if (ignore_set && strcmp(ifp->if_name, ignore_if) == 0)
			continue;
		flags = get_ifflags(ifp->if_name, ifp->if_family);
		if ((flags & IFF_UP) != 0) {
			takedowninterface(ifp->if_name,
			    flags & IFF_DHCPRUNNING, B_FALSE,
			    ifp->if_family == AF_INET6);
		}
	}
}

static struct interface *
get_next_interface(struct interface *ifp)
{
	return (ifp == NULL ? ifs_head : ifp->if_next);
}

/*
 * Add an interface struct to the interface list.  The list is
 * partially ordered; all the wired interfaces appear first,
 * followed by all the wireless interfaces.  New interfaces are
 * added at the end of the appropriate list section.
 */
static void
interface_list_insert(struct interface *ifp)
{
	struct interface **wpp;
	struct interface *endp;
	boolean_t first_wireless = B_FALSE;
	boolean_t first_wired = B_FALSE;

	switch (ifp->if_type) {
	case IF_WIRELESS:
		first_wireless = (ifs_wireless == NULL);
		wpp = &ifs_wireless;
		endp = NULL;
		break;

	case IF_WIRED:
		first_wired = (ifs_wired == NULL);
		wpp = &ifs_wired;
		endp = ifs_wireless;
		break;

	default:
		/* don't add to the list */
		return;
	}

	/* set list head if this is the first entry */
	if (ifs_head == NULL) {
		ifs_head = *wpp = ifp;
		ifp->if_next = NULL;
		return;
	}

	if (*wpp != NULL) {
		while (*wpp != endp)
			wpp = &(*wpp)->if_next;
	}
	*wpp = ifp;
	ifp->if_next = endp;

	/* update list head if we just inserted the first wired interface */
	if (first_wired)
		ifs_head = ifs_wired;

	/* link sections if we just inserted the first wireless interface */
	if (first_wireless) {
		wpp = &ifs_wired;
		while (*wpp != NULL)
			wpp = &(*wpp)->if_next;
		*wpp = ifs_wireless;
	}
}

/*
 * Returns the interface structure upon success.  Returns NULL and sets
 * errno upon error.  If lr is null then it will look up the information
 * needed.
 *
 * Note that given the MT nature of this program we are almost certainly
 * racing for this structure.  That needs to be fixed.
 */
struct interface *
add_interface(sa_family_t family, const char *name, uint64_t flags)
{
	struct interface *i;
	enum interface_type iftype;

	if (name == NULL)
		return (NULL);

	dprintf("add_interface: found interface %s", name);
	if (family == AF_INET6) {
		/*
		 * we don't track IPv6 interfaces separately from their
		 * v4 counterparts; a link either has v4 only, or both
		 * v4 and v6, so we only maintain a v4 interface struct.
		 */
		dprintf("not adding v6 interface for %s", name);
		return (NULL);
	} else if (family != AF_INET) {
		/*
		 * the classic "shouldn't happen"...
		 */
		dprintf("not adding af %d interface for %s", family, name);
		return (NULL);
	}

	if ((iftype = find_if_type(name)) == IF_TUN) {
		/*
		 * for now, we're ignoring tunnel interfaces (we expect
		 * them to be entirely manipulated by higher layer profile
		 * activation/deactivation scripts)
		 */
		dprintf("%s is a tunnel interface; ignoring", name);
		return (NULL);
	}

	if ((i = malloc(sizeof (*i))) == NULL) {
		dprintf("add_interface: malloc failed");
		return (NULL);
	}

	i->if_name = strdup(name);
	if (i->if_name == NULL) {
		free(i);
		dprintf("add_interface: malloc failed");
		return (NULL);
	}
	i->if_ipaddr = NULL;
	i->if_family = family;
	i->if_type = iftype;
	i->if_flags = flags == 0 ? get_ifflags(name, family) : flags;
	i->if_lflags = 0;
	i->if_timer_expire = 0;

	dprintf("added interface %s of type %s af %d; is %savailable",
	    i->if_name, if_type_str(i->if_type), i->if_family,
	    ((i->if_type == IF_WIRELESS) ||
	    ((i->if_flags & IFF_RUNNING) != 0)) ? "" : "not ");

	interface_list_insert(i);

	return (i);
}

/*
 * Searches for an interface and returns the interface structure if found.
 * Returns NULL otherwise.  errno is set upon error exit.
 */
struct interface *
get_interface(const char *name)
{
	struct interface *i;

	if (name == NULL)
		return (NULL);

	for (i = ifs_head; i != NULL; i = i->if_next) {
		if (strcmp(name, i->if_name) == 0) {
			return (i);
		}
	}

	return (NULL);
}

/*
 * Checks interface flags and, if IFF_DHCPRUNNING and !IFF_UP, does
 * an 'ifconfig ifname dhcp drop'.
 */
void
check_drop_dhcp(struct interface *ifp)
{
	uint64_t flags = get_ifflags(ifp->if_name, ifp->if_family);

	if (!(flags & IFF_DHCPRUNNING) || (flags & IFF_UP)) {
		dprintf("check_drop_dhcp: nothing to do (flags=0x%llx)", flags);
		return;
	}

	(void) start_child(IFCONFIG, ifp->if_name, "dhcp", "drop", NULL);
}

/*
 * For wireless interface, we will try to find out available wireless
 * network; for wired, if dhcp should be used, start it now to try to
 * avoid delays there.
 *
 * For the real code, we should pass back the network information
 * gathered.  Note that the state engine will then use the llp to
 * determine which interface should be set up...
 */
static void *
gather_interface_info(void *arg)
{
	struct interface *i = arg;
	llp_t *llp;

	assert(i != NULL);

	dprintf("Start gathering info for %s", i->if_name);

	switch (i->if_type) {
	case IF_WIRELESS:
		(void) scan_wireless_nets(i);
		break;
	case IF_WIRED:
		/*
		 * It should not happen as the llp list should be done when
		 * this function is called.  But let the state engine decide
		 * what to do.
		 */
		if ((llp = llp_lookup(i->if_name)) == NULL)
			break;
		/*
		 * The following is to avoid locking up the state machine
		 * as it is currently the choke point.  We start dhcp with
		 * a wait time of 0; later, if we see the link go down
		 * (IFF_RUNNING is cleared), we will drop the attempt.
		 */
		if (llp->llp_ipv4src == IPV4SRC_DHCP && is_plugged_in(i))
			start_dhcp(i);
		break;
	default:
		/* For other types, do not do anything. */
		return (NULL);
	}

	gen_newif_event(i);

	dprintf("Done gathering info for %s", i->if_name);
	return (NULL);
}

void
gen_newif_event(struct interface *i)
{
	struct np_event *e;

	e = calloc(1, sizeof (struct np_event));
	if (e == NULL) {
		dprintf("gen_newif_event: calloc failed");
		return;
	}
	e->npe_name = strdup(i->if_name);
	if (e->npe_name == NULL) {
		dprintf("gen_newif_event: strdup failed");
		free(e);
		return;
	}
	e->npe_type = EV_ROUTING;

	/*
	 * This event notifies the state machine that a new interface is
	 * (at least nominally) available to be brought up.  When the state
	 * machine processes the event, it will look at the entire list of
	 * interfaces and corresponding LLPs, and make a determination about
	 * the best available LLP under current conditions.
	 */
	np_queue_add_event(e);
	dprintf("gen_newif_event: generated event for if %s", i->if_name);
}

/*
 * Caller uses this function to walk through the whole interface list.
 * For each interface, the caller provided walker is called with
 * the interface and arg as parameters.
 *
 * XXX There is no lock held right now for accessing the interface
 * list.  We probably need that in future.
 */
void
walk_interface(void (*walker)(struct interface *, void *), void *arg)
{
	struct interface *i;

	for (i = ifs_head; i != NULL; i = i->if_next)
		walker(i, arg);
}

static void
print_interface_list(void)
{
	struct interface *wp;

	dprintf("Walking interface list; starting with wired interfaces");
	for (wp = ifs_head; wp != NULL; wp = wp->if_next) {
		if (wp == ifs_wireless)
			dprintf("Now wireless interfaces");
		dprintf("==> %s", wp->if_name);
	}
}

/*
 * Walker function passed to icfg_iterate_if() below - the icfg_if_it *
 * argument is guaranteed to be non-NULL by icfg_iterate_if(),
 * since the function it uses to generate the list - icfg_get_if_list()) -
 * guarantees this.
 */
/* ARGSUSED */
static int
do_add_interface(icfg_if_t *intf, void *arg)
{
	uint64_t flags = get_ifflags(intf->if_name, intf->if_protocol);

	/* We don't touch loopback interface. */
	if (flags & IFF_LOOPBACK)
		return (ICFG_SUCCESS);

	/* If adding fails, just ignore that interface... */
	(void) add_interface(intf->if_protocol, intf->if_name, flags);

	return (ICFG_SUCCESS);
}

/*
 * Walker function passed to icfg_iterate_if() below - the icfg_if_it *
 * argument is guaranteed to be non-NULL by icfg_iterate_if(),
 * since the function it uses to generate the list - icfg_get_if_list()) -
 * guarantees this.
 */
/* ARGSUSED */
static int
do_unplumb_if(icfg_if_t *intf, void *arg)
{
	uint64_t flags = get_ifflags(intf->if_name, intf->if_protocol);

	/* We don't touch loopback interface. */
	if (flags & IFF_LOOPBACK)
		return (ICFG_SUCCESS);

	(void) start_child(IFCONFIG, intf->if_name,
	    intf->if_protocol == AF_INET6 ? "inet6" : "inet", "unplumb", NULL);

	return (ICFG_SUCCESS);
}

void
initialize_interfaces(void)
{
	int times;

	dprintf("initialize_interfaces: setting link_layer_profile(%p) to NULL",
	    (void *)link_layer_profile);
	link_layer_profile = NULL;
	upper_layer_profile[0] = '\0';

	/*
	 * Bring down all interfaces bar lo0.
	 */
	(void) icfg_iterate_if(AF_INET, ICFG_PLUMBED, NULL, do_unplumb_if);
	(void) icfg_iterate_if(AF_INET6, ICFG_PLUMBED, NULL, do_unplumb_if);

	/*
	 * In case dhcpagent is running...  If it is running, when
	 * we do another DHCP command on the same interface later, it may
	 * be confused.  Just kill dhcpagent to simplify handling.
	 */
	dprintf("killing dhcpagent");
	(void) start_child(PKILL, "-z", zonename, "dhcpagent", NULL);

	/*
	 * Really we should walk the device tree instead of doing
	 * the 'ifconfig -a plumb'.
	 */
	for (times = 1; times <= 30; times++) {
		if (start_child(IFCONFIG, "-a", "plumb", NULL) == 0)
			break;

		/*
		 * Assume the di_init problem is the cause: sleep and try
		 * again, as the DDI has probably not been initialized yet.
		 */
		(void) sleep(1);
	}
	if (times > 30)
		syslog(LOG_ERR, IFCONFIG "-a plumb failed %d times", times);

	(void) dladm_init_linkprop();

	(void) icfg_iterate_if(AF_INET, ICFG_PLUMBED, NULL, do_add_interface);

	print_interface_list();

}

/*
 * Walker function used to start info gathering of each interface.
 */
/* ARGSUSED */
void
start_if_info_collect(struct interface *ifp, void *arg)
{
	pthread_t if_thr;
	pthread_attr_t attr;

	/*
	 * Only if the cable of the wired interface is
	 * plugged in, start gathering info from it.
	 */
	if (!is_plugged_in(ifp))
		return;

	/*
	 * This is a "fresh start" for the interface; if dhcp
	 * previously failed, the flag can now be cleared.
	 */
	ifp->if_lflags &= ~IF_DHCPFAILED;

	(void) pthread_attr_init(&attr);
	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	if (pthread_create(&if_thr, &attr, gather_interface_info,
	    (void *)ifp) != 0) {
		syslog(LOG_ERR, "create interface gathering thread: %m");
		exit(EXIT_FAILURE);
	} else {
		dprintf("interface info thread: %d", if_thr);
	}
}

/*
 * Walker function used to check timer for each interface.
 * If timer has expired, generate a timer event for the
 * interface.
 */
/* ARGSUSED */
void
check_interface_timer(struct interface *ifp, void *arg)
{
	uint32_t now = *(uint32_t *)arg;
	struct np_event *ev;

	if (ifp->if_timer_expire == 0)
		return;

	if (ifp->if_timer_expire > now) {
		start_timer(now, ifp->if_timer_expire - now);
		return;
	}

	ifp->if_timer_expire = 0;

	if ((ev = calloc(1, sizeof (*ev))) == NULL) {
		dprintf("could not allocate timer event for %s; ignoring timer",
		    ifp->if_name);
		return;
	}
	ev->npe_type = EV_TIMER;
	ev->npe_name = strdup(ifp->if_name);
	if (ev->npe_name == NULL) {
		dprintf("could not strdup name for timer event on %s; ignoring",
		    ifp->if_name);
		free(ev);
		return;
	}
	np_queue_add_event(ev);
}

enum interface_type
find_if_type(const char *name)
{
	enum interface_type type;

	if (name == NULL) {
		dprintf("find_if_type: no ifname; returning IF_UNKNOWN");
		return (IF_UNKNOWN);
	}

	if (strncmp(name, "ip.tun", 6) == 0) {
		/*
		 * We'll need to update our tunnel detection once
		 * clearview/uv and clearview/tun driver projects
		 * go back; tunnel names won't necessarily be ip.tunN
		 */
		type = IF_TUN;
	} else {
		/*
		 * We didn't recognize it.  Try the libdladm function
		 * to decide if it is wireless or not; if not, assume
		 * that it's wired.
		 */
		type = dladm_wlan_is_valid(name) ? IF_WIRELESS : IF_WIRED;
	}

	return (type);
}

const char *
if_type_str(enum interface_type type)
{
	switch (type) {
	case IF_WIRED:
		return ("wired");
	case IF_WIRELESS:
		return ("wireless");
	case IF_TUN:
		return ("tunnel");
	case IF_UNKNOWN:
	default:
		return ("unknown type");
	}
}