interface.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
* 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 interfaces, the
* list of interfaces present on the system, and upper layer profiles;
* and various support functions. It also contains 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
* 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,
* /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
* 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.
*
* The ifs_head and associated list pointers are protected by ifs_lock. Only
* the main thread may modify the list (single writer), and it does so with the
* lock held. As a consequence, the main thread alone may read the list (and
* examine pointers) without holding any locks. All other threads must hold
* ifs_lock for the duration of any examination of the data structures, and
* must not deal directly in interface 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 <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <syslog.h>
#include <unistd.h>
#include <libscf.h>
#include <inetcfg.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 char upper_layer_profile[MAXHOSTNAMELEN];
#define LOOPBACK_IF "lo0"
void
show_if_status(const char *ifname)
{
struct sockaddr_in sin;
int prefixlen = 0;
/* We only display new addr info for v4 interfaces */
return;
}
B_TRUE) != ICFG_SUCCESS) {
icfg_close(h);
return;
}
icfg_close(h);
}
/*
* If this interface matches the currently active llp, return B_TRUE.
* Otherwise, return B_FALSE.
* Called only from main thread.
*/
{
return (B_FALSE);
}
/*
* Execute 'ifconfig ifname dhcp wait 0'.
*/
static void
{
int res;
dprintf("start_dhcp: already started; returning");
return;
}
/*
* If we need to use DHCP and DHCP is already controlling the
* interface, we don't need to do anything. Otherwise, start it now.
*/
NULL);
} else {
dprintf("DHCP already running on %s; resetting timer",
}
/* start dhcp timer */
if (res == -1)
}
static boolean_t
{
int i;
char *state;
for (i = 1; i <= wait_time; i++) {
} else {
return (B_TRUE);
}
}
(void) sleep(1);
}
return (B_FALSE);
}
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
{
}
/*
* 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
{
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
return (-1);
}
scf_strerror(scf_error()));
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
scf_strerror(scf_error()));
goto failure;
}
return (0);
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
{
int result = -1;
/*
* an error was already logged by get_property_value,
* and it released any resources assigned to res before
* returning.
*/
return (result);
}
scf_strerror(scf_error()));
goto cleanup;
}
result = 0;
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
{
int result = -1;
/*
* an error was already logged by get_property_value,
* and it released any resources assigned to res before
* returning.
*/
return (result);
}
scf_strerror(scf_error()));
goto cleanup;
}
*answer);
result = 0;
return (result);
}
void
{
FILE *f;
const char bringup[] = "/bringup";
int res;
/*
* exec the net-svc script to update local config with
* any DNS information learned from the DHCP server.
*/
if (do_dhcp) {
/*
* If the look-up failed, try anyway: only avoid this if we
* know for sure not to.
*/
NULL);
}
}
if (f == NULL) {
/* note that this doesn't happen if the file is missing */
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.
*/
f);
/* Need to check for script error before interpreting result */
if (res == -1) {
return;
}
if (WEXITSTATUS(res) == 0) {
"check-conditions returned no information");
} else {
(void) strlcpy(upper_layer_profile,
sizeof (upper_layer_profile));
"upper layer profile %s activated",
}
"check-conditions exited with status %d",
WEXITSTATUS(res));
} else {
}
} else if (WIFSIGNALED(res)) {
} else {
"check-conditions terminated in unknown manner");
}
}
void
{
char buffer[1024];
/*
* If ULP wasn't defined...
*/
if (!ulp_is_active())
return;
upper_layer_profile[0] = '\0';
}
/*
* Returns SUCCESS if the interface is successfully brought up,
* FAILURE if bringup fails, or WAITING if we'll need to wait on the GUI to run.
* Called only in the main thread or a thread holding machine_lock.
*/
{
ifname);
return (FAILURE);
}
/* check current state; no point going on if flags are 0 */
dprintf("bringupinterface(%s): get_ifflags() returned 0",
ifname);
return (FAILURE);
}
switch (handle_wireless_lan(ifname)) {
case WAITING:
return (WAITING);
case FAILURE:
"bringing %s up", ifname);
return (FAILURE);
}
}
/* physical level must now be up; bail out if not */
return (FAILURE);
}
/*
* 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");
NULL);
if (ipv6addr) {
}
}
} else {
}
return (SUCCESS);
}
/* Called only in the main thread */
void
{
dprintf("takedowninterface: can't find interface struct for %s",
ifname);
}
if (flags & IFF_DHCPRUNNING) {
/*
* 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 without a lease to release, so we have to drop in
* that case. So try release first, then fall back to drop.
*/
!= 0) {
NULL);
}
} else {
/* need to unset a statically configured addr */
}
/*
* Unplumbing the link local interface causes dhcp and ndpd to
* remove other addresses they have added.
*/
}
dprintf("takedown interface, zero cached ip address");
}
}
/* Called only in the main thread */
void
clear_cached_address(const char *ifname)
{
dprintf("clear_cached_address: can't find interface struct "
"for %s", ifname);
return;
}
}
/*
* 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
{
if (pthread_mutex_lock(&ifs_lock) != 0)
return;
case IF_WIRELESS:
/*
* Wireless entries are in the wireless list, and are chained
* after the wired entries. If there are no wired entries, then
* chain on main list.
*/
headpp = &ifs_wireless;
break;
case IF_WIRED:
/*
* Wired entries are on the wired list, and are chained before
* the wireless entries.
*/
lastpp = &ifs_wired_last;
break;
default:
/* don't add to the list */
(void) pthread_mutex_unlock(&ifs_lock);
return;
}
/* Connect into the correct list */
/*
* If there's a previous list, then wire to the end of
* that, as we're the new head here.
*/
} else {
}
/* Fix up the main list; it's always wired-first */
(void) pthread_mutex_unlock(&ifs_lock);
}
/*
* Returns the interface structure upon success. Returns NULL and sets
* errno upon error.
*/
struct interface *
{
struct interface *i;
return (NULL);
/*
* 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.
*/
return (NULL);
/*
* the classic "shouldn't happen"...
*/
return (NULL);
}
/*
* for now, we're ignoring tunnel interfaces (we expect
* them to be entirely manipulated by higher layer profile
* activation/deactivation scripts)
*/
return (NULL);
}
dprintf("add_interface: malloc failed");
return (NULL);
}
dprintf("added interface %s of type %s af %d; is %savailable",
if (iftype == IF_WIRELESS)
return (i);
}
/*
* This is called only by the main thread.
*/
void
remove_interface(const char *ifname)
{
if (pthread_mutex_lock(&ifs_lock) != 0)
return;
else
if (ifp == ifs_wired_last) {
}
if (ifp == ifs_wireless_last) {
ifs_wireless = NULL;
} else if (ifp == ifs_wireless) {
}
break;
}
}
(void) pthread_mutex_unlock(&ifs_lock);
}
}
/*
* Searches for an interface and returns the interface structure if found.
* Returns NULL otherwise. The caller must either be holding ifs_lock, or be
* in the main thread.
*/
struct interface *
get_interface(const char *name)
{
return (NULL);
break;
}
return (ifp);
}
/*
* Check to see whether the interface could be started. If the IFF_RUNNING
* flag is set, then we're in good shape. Otherwise, wireless interfaces are
* special: we'll attempt to connect to an Access Point as part of the start-up
* procedure, and IFF_RUNNING won't be present until that's done, so assume
* that all wireless interfaces are good to go. This is just an optimization;
* we could start everything.
*/
static boolean_t
{
return (B_TRUE);
}
/*
* 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.
*
* ifs_lock is not held on entry. The caller will cancel this thread and wait
* for it to exit if the interface is to be deleted.
*/
static void *
gather_interface_info(void *arg)
{
int retv;
switch (i->if_type) {
case IF_WIRELESS:
/* This generates EV_NEWAP when successful */
if (retv != 0)
dprintf("didn't launch wireless scan: %s",
break;
case IF_WIRED:
/*
* 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 (is_startable(i))
start_dhcp(i);
}
break;
}
i->if_thr = 0;
return (NULL);
}
/*
* 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, and with the ifs_lock held.
*/
void
{
if (pthread_mutex_lock(&ifs_lock) != 0)
return;
(void) pthread_mutex_unlock(&ifs_lock);
}
static void
print_interface_list(void)
{
dprintf("Walking interface list; starting with wired interfaces");
if (wp == ifs_wireless)
dprintf("Now wireless interfaces");
}
}
/*
* 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
{
/* We don't touch loopback interface. */
if (flags & IFF_LOOPBACK)
return (ICFG_SUCCESS);
/* If adding fails, just ignore that interface... */
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
{
/* We don't touch loopback interface. */
if (flags & IFF_LOOPBACK)
return (ICFG_SUCCESS);
return (ICFG_SUCCESS);
}
void
initialize_interfaces(void)
{
int numifs;
unsigned int wait_time = 1;
/*
* Bring down all interfaces bar lo0.
*/
/*
* 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");
/*
* Really we should walk the device tree instead of doing
* the 'ifconfig -a plumb'. On the first reconfigure boot
* (after install) 'ifconfig -a plumb' comes back quickly
* without any devices configured if we start before
* dependency loop through 'svc:/system/filesystem/usr'. So
*/
for (;;) {
/*
* There are cases where we get here and the devices list
* still isn't initialized yet. Hang out until we see
* something other than loopback.
*/
!= ICFG_SUCCESS) {
numifs = 0;
} else {
}
while (numifs > 0 && !found_nonlo_if) {
}
if (found_nonlo_if)
break;
wait_time *= 2;
if (wait_time > NWAM_IF_WAIT_DELTA_MAX)
}
}
/*
* Walker function used to start info gathering of each interface. Caller
* holds ifs_lock.
*/
void
{
int retv;
/*
* In certain cases we need to refresh the cached flags value as
* it may be stale. Notably, we can miss a DL_NOTE_LINK_DOWN
* event after we initialize interfaces before the routing thread
* is launched.
*/
/*
* Only if the cable of the wired interface is
* plugged in, start gathering info from it.
*/
if (!is_startable(ifp)) {
return;
}
/*
* This is a "fresh start" for the interface, so clear old DHCP flags.
*/
(void) pthread_attr_init(&attr);
ifp)) != 0) {
} else {
}
}
/*
* Walker function used to check timer for each interface.
* If timer has expired, generate a timer event for the
* interface.
*/
static void
{
if (ifp->if_timer_expire == 0)
return;
return;
}
ifp->if_timer_expire = 0;
}
void
{
}
find_if_type(const char *name)
{
dprintf("find_if_type: no ifname; returning IF_UNKNOWN");
return (IF_UNKNOWN);
}
/*
* We'll need to update our tunnel detection once
* names won't necessarily be ip.tunN.
*/
type = IF_WIRELESS;
}
return (type);
}
const char *
{
switch (type) {
case IF_WIRED:
return ("wired");
case IF_WIRELESS:
return ("wireless");
case IF_TUN:
return ("tunnel");
case IF_UNKNOWN:
default:
return ("unknown type");
}
}
/*
* This is called by the routing socket thread to update the IPv4 address on an
* interface. The routing socket thread cannot touch the interface structures
* without holding the global lock, because interface structures can be
* deleted.
*/
void
{
if (pthread_mutex_lock(&ifs_lock) == 0) {
dprintf("no interface struct for %s; ignoring message",
ifname);
dprintf("cached new address %s for link %s",
} else {
}
(void) pthread_mutex_unlock(&ifs_lock);
}
}
/*
* This is called by the routing socket thread to update the flags on a given
* IPv4 interface. If the interface has changed state, then we launch an event
* or a thread as appropriate.
*/
void
{
int oldflags;
if (pthread_mutex_lock(&ifs_lock) == 0) {
dprintf("no interface data for %s; ignoring message",
ifname);
} else {
/*
* Check for toggling of the IFF_RUNNING flag.
*
* On any change in the flag value, we turn off the
* DHCP flags; the change in the RUNNING state
* indicates a "fresh start" for the interface, so we
* should try dhcp again.
*
* If the interface was not plugged in and now it is,
* start info collection.
*
* If it was plugged in and now it is unplugged,
* generate an event.
*/
if ((oldflags & IFF_RUNNING) !=
(newflags & IFF_RUNNING)) {
}
if (!(newflags & IFF_DHCPRUNNING))
if (!(oldflags & IFF_RUNNING) &&
(newflags & IFF_RUNNING)) {
} else if ((oldflags & IFF_RUNNING) &&
!(newflags & IFF_RUNNING)) {
} else {
dprintf("no-event flag change on %s: %x -> %x",
}
}
(void) pthread_mutex_unlock(&ifs_lock);
}
}
/*
* Called only in main thread. Note that wireless interfaces are considered
* "ok" even if the IFF_RUNNING bit isn't set. This is because AP attach
* occurs as part of the LLP selection process.
*/
is_interface_ok(const char *ifname)
{
return (is_ok);
}
/*
* Return the interface type for a given interface name.
*/
get_if_type(const char *ifname)
{
if (pthread_mutex_lock(&ifs_lock) == 0) {
(void) pthread_mutex_unlock(&ifs_lock);
}
return (ift);
}
/*
* Get the interface state for storing in llp_t. This is used only with the
* doors interface to return status flags.
*/
void
{
if (pthread_mutex_lock(&ifs_lock) == 0) {
*dhcp_failed = B_TRUE;
}
(void) pthread_mutex_unlock(&ifs_lock);
}
}
/*
* Dump out the interface state via debug messages.
*/
void
print_interface_status(void)
{
if (pthread_mutex_lock(&ifs_lock) == 0) {
if (upper_layer_profile[0] != '\0')
dprintf("upper layer profile %s active",
else
dprintf("no upper layer profile active");
dprintf("I/F %s af %d flags %llX lflags %X type %d "
"expire %u v6 %son-link up %sattempted addr %s",
}
(void) pthread_mutex_unlock(&ifs_lock);
}
}