Home | History | Annotate | Download | only in nwamd
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 /*
     28  * This file contains the core logic of nwamd.
     29  *
     30  * This functionality is built around state_machine() which consumes an event.
     31  * The events which are input to this function are generated by either a change
     32  * in interface state or a signal.  The events which correspond to signals are
     33  * either external requests to shutdown or a timer event.  The interface events
     34  * indicate if an interface has acquired a new address (via DHCP) or if a new
     35  * interface has appeared on the system.  The latter event is used to detect new
     36  * links.
     37  *
     38  * state_machine() calls high level routines in llp.c and interface.c to act on
     39  * the state of the machine in response to events.
     40  *
     41  * This function is called by the main thread in the program with machine_lock
     42  * held.  This is the only thread that can add or remove interface and LLP
     43  * structures, and thus it's safe for this function to use those structures
     44  * without locks.  See also the locking comments in the interface.c and llp.c
     45  * block comments.
     46  */
     47 
     48 #include <stdarg.h>
     49 #include <stdio.h>
     50 #include <arpa/inet.h>
     51 #include <libsysevent.h>
     52 #include <net/if.h>
     53 #include <net/route.h>
     54 #include <netinet/in.h>
     55 #include <sys/nvpair.h>
     56 #include <sys/socket.h>
     57 #include <sys/types.h>
     58 #include <syslog.h>
     59 
     60 #include "defines.h"
     61 #include "structures.h"
     62 #include "functions.h"
     63 #include "variables.h"
     64 
     65 void
     66 state_machine(struct np_event *e)
     67 {
     68 	struct interface *evif;
     69 	llp_t *evllp, *prefllp;
     70 	uint64_t flags;
     71 	boolean_t dhcp_restored = B_FALSE;
     72 
     73 	dprintf("state_machine(event type: %s, name: %s)",
     74 	    npe_type_str(e->npe_type), STRING(e->npe_name));
     75 	switch (e->npe_type) {
     76 	case EV_TIMER:
     77 		/* Our timer popped; check our dhcp status. */
     78 		if ((evif = get_interface(e->npe_name)) == NULL) {
     79 			dprintf("couldn't find waiting interface; "
     80 			    "ignoring EV_TIMER event");
     81 			break;
     82 		}
     83 		flags = get_ifflags(evif->if_name, evif->if_family);
     84 		if ((!(evif->if_lflags & IF_DHCPSTARTED) &&
     85 		    !(flags & IFF_DHCPRUNNING)) || ((flags & IFF_UP) &&
     86 		    evif->if_ipv4addr != INADDR_ANY)) {
     87 			/*
     88 			 * Either DHCP came up successfully, or we're no
     89 			 * longer trying to do DHCP on this interface;
     90 			 * so no need to worry about the timer expiring.
     91 			 */
     92 			dprintf("timer popped for %s, but dhcp state is okay "
     93 			    "(ifflags 0x%llx)", evif->if_name, flags);
     94 			break;
     95 		}
     96 		if (evif->if_lflags & IF_DHCPFAILED) {
     97 			dprintf("ignoring timer; interface already failed");
     98 			break;
     99 		}
    100 		/*
    101 		 * dhcp has not yet completed; give up on it for
    102 		 * now and, if it is the currently active llp,
    103 		 * switch to the next best.
    104 		 */
    105 		dprintf("giving up on dhcp on %s (ifflags 0x%llx)",
    106 		    evif->if_name, flags);
    107 		evif->if_lflags |= IF_DHCPFAILED;
    108 		if (interface_is_active(evif)) {
    109 			if ((prefllp = llp_best_avail()) != NULL) {
    110 				llp_swap(prefllp, dcTimer);
    111 			} else {
    112 				dprintf("DHCP timed out, but no better link is "
    113 				    "available");
    114 				report_interface_down(evif->if_name, dcTimer);
    115 			}
    116 		} else {
    117 			dprintf("DHCP failed on inactive link");
    118 			report_interface_down(evif->if_name, dcTimer);
    119 		}
    120 
    121 		break;
    122 
    123 	case EV_NEWAP:
    124 		if ((evllp = llp_lookup(e->npe_name)) == NULL) {
    125 			dprintf("state_machine: no llp for %s; ignoring "
    126 			    "EV_NEWAP event", STRING(e->npe_name));
    127 			break;
    128 		}
    129 
    130 		if (evllp == link_layer_profile) {
    131 			llp_reselect();
    132 		} else {
    133 			evllp->llp_waiting = B_FALSE;
    134 			evllp->llp_failed = B_FALSE;
    135 			prefllp = llp_best_avail();
    136 			if (prefllp == NULL) {
    137 				dprintf("new APs on %s, but no best link is "
    138 				    "available", llp_prnm(evllp));
    139 			} else if (prefllp != link_layer_profile) {
    140 				dprintf("state_machine: new APs on link %s "
    141 				    "caused new preferred llp: %s (was %s)",
    142 				    llp_prnm(evllp), llp_prnm(prefllp),
    143 				    llp_prnm(link_layer_profile));
    144 				llp_swap(prefllp, dcNewAP);
    145 			}
    146 		}
    147 		break;
    148 
    149 	case EV_LINKDROP:
    150 	case EV_LINKUP:
    151 	case EV_LINKFADE:
    152 	case EV_LINKDISC:
    153 	case EV_USER:
    154 		if ((evif = get_interface(e->npe_name)) == NULL ||
    155 		    (evllp = llp_lookup(e->npe_name)) == NULL) {
    156 			dprintf("state_machine: either no intf (%p) or no llp "
    157 			    "(%p) for %s; ignoring EV_%s event",
    158 			    (void *)evif, (void *)evllp, STRING(e->npe_name),
    159 			    npe_type_str(e->npe_type));
    160 			break;
    161 		}
    162 
    163 		if (e->npe_type == EV_LINKUP || e->npe_type == EV_USER)
    164 			evllp->llp_failed = B_FALSE;
    165 
    166 		/*
    167 		 * If we're here because wireless has disconnected, then clear
    168 		 * the DHCP failure flag on this interface; this is a "fresh
    169 		 * start" for the interface, so we should retry DHCP.
    170 		 */
    171 		if (e->npe_type == EV_LINKFADE || e->npe_type == EV_LINKDISC)
    172 			evif->if_lflags &= ~IF_DHCPFAILED;
    173 
    174 		prefllp = llp_best_avail();
    175 		if (prefllp == NULL) {
    176 			dprintf("state changed on %s, but no best link is "
    177 			    "available", llp_prnm(evllp));
    178 		} else if (prefllp != link_layer_profile) {
    179 			dprintf("state_machine: change in state of link %s "
    180 			    "resulted in new preferred llp: %s (was %s)",
    181 			    llp_prnm(evllp), llp_prnm(prefllp),
    182 			    llp_prnm(link_layer_profile));
    183 			llp_swap(prefllp,
    184 			    e->npe_type == EV_LINKDROP ? dcUnplugged :
    185 			    e->npe_type == EV_LINKFADE ? dcFaded :
    186 			    e->npe_type == EV_LINKDISC ? dcGone :
    187 			    e->npe_type == EV_USER ? dcUser :
    188 			    dcBetter);
    189 		} else if (prefllp == evllp) {
    190 			/*
    191 			 * If this is a negative event on our preferred link,
    192 			 * then we need to pay closer attention.  (We can
    193 			 * ignore negative events on other links.)
    194 			 */
    195 			switch (e->npe_type) {
    196 			case EV_LINKFADE:
    197 			case EV_LINKDISC:
    198 				/*
    199 				 * If the link has faded or disconnected, then
    200 				 * it's a wireless link, and something has gone
    201 				 * wrong with the connection to the AP.  The
    202 				 * above tests mean that we do intend to stay
    203 				 * with this link for now, so we have to
    204 				 * recover it by attempting to reconnect.
    205 				 * Invoking reselect will do that by calling
    206 				 * bringupinterface.
    207 				 */
    208 				dprintf("disconnect on preferred llp; "
    209 				    "attempting reconnect");
    210 				prefllp->llp_waiting = B_TRUE;
    211 				llp_reselect();
    212 				break;
    213 			case EV_LINKDROP:
    214 				/*
    215 				 * If link status has dropped on a wireless
    216 				 * interface, then we need to check whether
    217 				 * we're still connected.  We're probably not,
    218 				 * and this will cause us to attempt
    219 				 * reconnection.
    220 				 */
    221 				if (prefllp->llp_type == IF_WIRELESS)
    222 					wireless_verify(e->npe_name);
    223 				break;
    224 			}
    225 		}
    226 		if (e->npe_type == EV_USER)
    227 			llp_write_changed_priority(evllp);
    228 		break;
    229 
    230 	case EV_NEWADDR:
    231 		if ((evif = get_interface(e->npe_name)) == NULL ||
    232 		    (evllp = llp_lookup(e->npe_name)) == NULL) {
    233 			dprintf("state_machine: either no intf (%p) or no llp "
    234 			    "(%p) for %s; ignoring EV_NEWADDR event",
    235 			    (void *)evif, (void *)evllp, STRING(e->npe_name));
    236 			break;
    237 		}
    238 		evllp->llp_failed = B_FALSE;
    239 		if (evllp->llp_ipv4src == IPV4SRC_DHCP) {
    240 			flags = get_ifflags(evif->if_name, evif->if_family);
    241 			if (!(flags & IFF_DHCPRUNNING) || !(flags & IFF_UP) ||
    242 			    evif->if_ipv4addr == INADDR_ANY) {
    243 				/*
    244 				 * We don't have a DHCP lease.  If we used to
    245 				 * have one, then switch to another profile.
    246 				 * Note that if *we* took the interface down
    247 				 * (which happens if this isn't the one
    248 				 * preferred interface), then this doesn't
    249 				 * signal a DHCP failure.
    250 				 */
    251 				if (!(flags & IFF_DHCPRUNNING))
    252 					evif->if_lflags &= ~IF_DHCPSTARTED;
    253 				if (evif->if_lflags & IF_DHCPACQUIRED) {
    254 					evif->if_lflags &= ~IF_DHCPACQUIRED;
    255 					if (interface_is_active(evif)) {
    256 						evif->if_lflags |=
    257 						    IF_DHCPFAILED;
    258 						prefllp = llp_best_avail();
    259 						if (prefllp != NULL) {
    260 							dprintf("DHCP has "
    261 							    "failed, switch "
    262 							    "interfaces");
    263 							llp_swap(prefllp,
    264 							    dcDHCP);
    265 						} else {
    266 							dprintf("DHCP failed, "
    267 							    "but no better link"
    268 							    " is available");
    269 							report_interface_down(
    270 							    evif->if_name,
    271 							    dcDHCP);
    272 						}
    273 					} else {
    274 						dprintf("DHCP not acquired and "
    275 						    "not active");
    276 					}
    277 				}
    278 				break;
    279 			}
    280 
    281 			/*
    282 			 * We have a DHCP lease.  If we'd previously failed
    283 			 * to get one, record that DHCP has been restored.
    284 			 */
    285 			evif->if_timer_expire = 0;
    286 			evif->if_lflags |= IF_DHCPACQUIRED;
    287 			if (evif->if_lflags & IF_DHCPFAILED) {
    288 				evif->if_lflags &= ~IF_DHCPFAILED;
    289 				dhcp_restored = B_TRUE;
    290 			}
    291 		}
    292 		if (evllp != link_layer_profile) {
    293 			if (dhcp_restored &&
    294 			    llp_high_pri(evllp, link_layer_profile) == evllp) {
    295 				dprintf("state_machine: dhcp completed on "
    296 				    "higher priority llp (%s); swapping",
    297 				    llp_prnm(evllp));
    298 				llp_swap(evllp, dcBetter);
    299 			} else {
    300 				dprintf("state_machine: newaddr event was for "
    301 				    "%s, not for current active link (%s); "
    302 				    "taking down %s", evllp->llp_lname,
    303 				    llp_prnm(link_layer_profile),
    304 				    evllp->llp_lname);
    305 				takedowninterface(evllp->llp_lname, dcUnwanted);
    306 				break;
    307 			}
    308 		}
    309 		/*
    310 		 * An address has been assigned to the current active link.
    311 		 * Notify the user, and activate the upper layer profile.
    312 		 *
    313 		 * Since other changes to the link (netmask change, broadcast
    314 		 * addr change, etc.) can cause a NEWADDR event (XXX would
    315 		 * be good if our event generator could do a better job
    316 		 * filtering!), only do this if there is not currently an
    317 		 * active ulp.
    318 		 */
    319 		if (!ulp_is_active()) {
    320 			show_if_status(evllp->llp_lname);
    321 			activate_upper_layer_profile(evllp->llp_ipv4src ==
    322 			    IPV4SRC_DHCP, evllp->llp_lname);
    323 		}
    324 		break;
    325 
    326 	case EV_ADDIF:
    327 		/* Plumb the interface */
    328 		if (start_child(IFCONFIG, e->npe_name, "plumb", NULL) != 0) {
    329 			syslog(LOG_ERR, "could not plumb interface %s",
    330 			    e->npe_name);
    331 			return;
    332 		}
    333 		report_interface_added(e->npe_name);
    334 		evllp = llp_lookup(e->npe_name);
    335 		/* Add interface to interface list. */
    336 		evif = add_interface(AF_INET, e->npe_name, 0);
    337 		if (evllp == NULL) {
    338 			/*
    339 			 * Create a new llp entry, and add it to llp list
    340 			 * and /etc/nwam/llp. By default, the llp
    341 			 * has a DHCP IPv4 address source, and IPv6 is
    342 			 * used on the link.  We don't plumb the
    343 			 * IPv6 link yet - this is done for us by
    344 			 * bringupinterface().
    345 			 */
    346 			if ((evllp = llp_add(e->npe_name)) != NULL)
    347 				llp_add_file(evllp);
    348 		}
    349 
    350 		/*
    351 		 * start_if_info_collect will launch the gather_interface_info
    352 		 * thread, which will start a scan for wireless interfaces, or
    353 		 * start DHCP on wired interfaces.
    354 		 */
    355 		start_if_info_collect(evif, "check");
    356 		break;
    357 
    358 	case EV_REMIF:
    359 		/* Unplumb the interface */
    360 		if (start_child(IFCONFIG, e->npe_name, "unplumb", NULL) != 0) {
    361 			syslog(LOG_ERR, "could not unplumb interface %s",
    362 			    e->npe_name);
    363 			return;
    364 		}
    365 		evllp = llp_lookup(e->npe_name);
    366 		remove_interface(e->npe_name);
    367 		if (evllp != NULL) {
    368 			/* Unplumb IPv6 interface if IPv6 is used on the link */
    369 			if (evllp->llp_ipv6onlink) {
    370 				(void) start_child(IFCONFIG, e->npe_name,
    371 				    "inet6", "unplumb", NULL);
    372 			}
    373 			/* If this llp is active, deactivate it. */
    374 			if (evllp == link_layer_profile)
    375 				llp_swap(NULL, dcRemoved);
    376 			llp_delete(evllp);
    377 		}
    378 		report_interface_removed(e->npe_name);
    379 		break;
    380 
    381 	case EV_TAKEDOWN:
    382 		takedowninterface(e->npe_name, dcSelect);
    383 		break;
    384 
    385 	case EV_RESELECT:
    386 		if (link_layer_profile == NULL &&
    387 		    (prefllp = llp_best_avail()) != NULL) {
    388 			dprintf("reselect: activating LLP %s",
    389 			    llp_prnm(prefllp));
    390 			llp_swap(prefllp, dcNone);
    391 		} else {
    392 			llp_reselect();
    393 		}
    394 		break;
    395 
    396 	case EV_DOOR_TIME:
    397 		check_door_life(NSEC_TO_SEC(gethrtime()));
    398 		break;
    399 
    400 	case EV_SHUTDOWN:
    401 		/* Cleanup not expecting to see any more events after this */
    402 		cleanup();
    403 		return;
    404 		/* NOTREACHED */
    405 
    406 	default:
    407 		dprintf("unknown event");
    408 		break;
    409 	}
    410 }
    411 
    412 void
    413 cleanup(void)
    414 {
    415 	terminate_wireless();
    416 	deactivate_upper_layer_profile();
    417 	if (link_layer_profile != NULL)
    418 		takedowninterface(link_layer_profile->llp_lname, dcShutdown);
    419 
    420 	/*
    421 	 * Since actions taken in nwamd result in dhcpagent being
    422 	 * launched, it's under our contract.  Thus, it needs to be
    423 	 * stopped when our stop method is executed.  But it needs
    424 	 * to stick around long enough for us to release any leases
    425 	 * we might have; thus, we don't want the stop method to
    426 	 * explicitly kill it.  We do it here, when we know we've
    427 	 * finished any dhcp cleanup that needed to be done.
    428 	 */
    429 	dprintf("killing dhcpagent");
    430 	(void) start_child(PKILL, "-z", zonename, "dhcpagent", NULL);
    431 }
    432