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