[802.11] Revamped probe, fully asynchronous association, better error handling, much...
[people/oremanj/gpxe.git] / src / net / net80211.c
index 20e9936..b3a5bc8 100644 (file)
@@ -75,7 +75,7 @@ static struct list_head net80211_devices = LIST_HEAD_INIT ( net80211_devices );
 
 /** The network name to associate with
  *
- * If this is blank, we scan for all entworks and use the one with the
+ * If this is blank, we scan for all networks and use the one with the
  * greatest signal strength.
  */
 struct setting net80211_ssid_setting __setting = {
@@ -109,6 +109,51 @@ struct net80211_rx_info {
        struct list_head list;
 };
 
+/** Context for a probe operation */
+struct net80211_probe_ctx {
+       /** 802.11 device to probe on */
+       struct net80211_device *dev;
+
+       /** Value of keep_mgmt before probe was started */
+       int old_keep_mgmt;
+
+       /** If scanning actively, pointer to probe packet to send */
+       struct io_buffer *probe;
+
+       /** If non-"", the ESSID to limit ourselves to */
+       const char *essid;
+
+       /** Time probe was started */
+       u32 ticks_start;
+
+       /** Time last useful beacon was received */
+       u32 ticks_beacon;
+
+       /** Time channel was last changed */
+       u32 ticks_channel;
+
+       /** Time to stay on each channel */
+       u32 hop_time;
+
+       /** Channels to hop by when changing channel */
+       int hop_step;
+
+       /** List of best beacons for each network found so far */
+       struct list_head *beacons;
+};
+
+/** Context for the association task */
+struct net80211_assoc_ctx {
+       /** Next authentication method to try using */
+       int method;
+
+       /** Time (in ticks) of the last sent association-related packet */
+       int last_packet;
+
+       /** Number of times we have tried sending it */
+       int times_tried;
+};
+
 static u16 net80211_duration ( struct net80211_device *dev, int bytes );
 static int net80211_ll_push ( struct net_device *netdev,
                              struct io_buffer *iobuf, const void *ll_dest,
@@ -137,6 +182,7 @@ static void net80211_handle_auth ( struct net80211_device *dev,
                                   struct io_buffer *iob );
 static void net80211_handle_assoc_reply ( struct net80211_device *dev,
                                          struct io_buffer *iob );
+static int net80211_send_disassoc ( struct net80211_device *dev, int reason );
 static void net80211_handle_mgmt ( struct net80211_device *dev,
                                   struct io_buffer *iob, int signal );
 
@@ -166,9 +212,11 @@ struct settings_applicator net80211_ssid_applicator __settings_applicator = {
  * @ret rc     Return status code
  *
  * This sets up a default conservative set of channels for probing,
- * and starts the auto-association task.
+ * and starts the auto-association task unless the @c
+ * NET80211_NO_ASSOC flag is set in the wrapped 802.11 device's @c
+ * state field.
  */
-int net80211_netdev_open ( struct net_device *netdev )
+static int net80211_netdev_open ( struct net_device *netdev )
 {
        struct net80211_device *dev = netdev->priv;
        int rc = 0;
@@ -176,12 +224,17 @@ int net80211_netdev_open ( struct net_device *netdev )
        /* In case someone tries to transmit before we set link-up, we
           need to at least be in a consistent enough state not to
           crash. */
-       net80211_prepare_default ( dev, dev->hw->bands, 0 );
+       net80211_prepare_probe ( dev, dev->hw->bands, 0 );
 
        if ( dev->op->open )
                rc = dev->op->open ( dev );
 
-       net80211_autoassociate ( dev );
+       if ( rc < 0 )
+               return rc;
+
+       if ( ! ( dev->state & NET80211_NO_ASSOC ) )
+               net80211_autoassociate ( dev );
+
        return 0;
 }
 
@@ -192,15 +245,19 @@ int net80211_netdev_open ( struct net_device *netdev )
  *
  * If the association task is running, this will stop it.
  */
-void net80211_netdev_close ( struct net_device *netdev )
+static void net80211_netdev_close ( struct net_device *netdev )
 {
        struct net80211_device *dev = netdev->priv;
 
        if ( dev->state & NET80211_WORKING )
                process_del ( &dev->proc_assoc );
 
+       /* Send disassociation frame to AP, to be polite */
+       if ( dev->state & NET80211_ASSOCIATED )
+               net80211_send_disassoc ( dev, IEEE80211_REASON_LEAVING );
+
+       netdev_link_down ( netdev );
        dev->state = 0;
-       dev->netdev->state &= ~NETDEV_LINK_UP;
 
        if ( dev->op->close )
                dev->op->close ( dev );
@@ -216,8 +273,8 @@ void net80211_netdev_close ( struct net_device *netdev )
  * If encryption is enabled for the currently associated network, the
  * packet will be encrypted prior to transmission.
  */
-int net80211_netdev_transmit ( struct net_device *netdev,
-                              struct io_buffer *iobuf )
+static int net80211_netdev_transmit ( struct net_device *netdev,
+                                     struct io_buffer *iobuf )
 {
        struct net80211_device *dev = netdev->priv;
        int rc = -ENOSYS;
@@ -243,7 +300,7 @@ int net80211_netdev_transmit ( struct net_device *netdev,
  *
  * @v netdev   Wrapping network device
  */
-void net80211_netdev_poll ( struct net_device *netdev )
+static void net80211_netdev_poll ( struct net_device *netdev )
 {
        struct net80211_device *dev = netdev->priv;
 
@@ -257,7 +314,7 @@ void net80211_netdev_poll ( struct net_device *netdev )
  * @v netdev   Wrapping network device
  * @v enable   Whether to enable interrupts
  */
-void net80211_netdev_irq ( struct net_device *netdev, int enable )
+static void net80211_netdev_irq ( struct net_device *netdev, int enable )
 {
        struct net80211_device *dev = netdev->priv;
 
@@ -266,7 +323,7 @@ void net80211_netdev_irq ( struct net_device *netdev, int enable )
 }
 
 /** Network device operations for a wrapped 802.11 device */
-struct net_device_operations net80211_netdev_ops = {
+static struct net_device_operations net80211_netdev_ops = {
        .open = net80211_netdev_open,
        .close = net80211_netdev_close,
        .transmit = net80211_netdev_transmit,
@@ -385,12 +442,9 @@ static int net80211_ll_push ( struct net_device *netdev,
                ( void * ) hdr + IEEE80211_TYP_FRAME_HEADER_LEN;
 
        /* We can't send data packets if we're not associated. */
-       if ( ! ( dev->state & NET80211_ASSOCIATED ) ) {
-               if ( dev->state & NET80211_STATUS_MASK ) {
-                       if ( dev->state & NET80211_IS_REASON )
-                               return -E80211_REASON ( dev->state );
-                       return -E80211_STATUS ( dev->state );
-               }
+       if ( ! netdev_link_ok ( netdev ) ) {
+               if ( dev->assoc_rc )
+                       return dev->assoc_rc;
                return -ENETUNREACH;
        }
 
@@ -508,7 +562,7 @@ static struct ll_protocol net80211_ll_protocol __ll_protocol = {
        .pull = net80211_ll_pull,
        .ntoa = eth_ntoa,
        .mc_hash = net80211_ll_mc_hash,
-       .ll_proto = htons ( ARPHRD_ETHER ),     /* it's "encapsulated Ethernet" */
+       .ll_proto = htons ( ARPHRD_ETHER ),     /* "encapsulated Ethernet" */
        .ll_addr_len = ETH_ALEN,
        .ll_header_len = IEEE80211_TYP_FRAME_HEADER_LEN +
                                IEEE80211_LLC_HEADER_LEN,
@@ -753,9 +807,12 @@ static inline void net80211_set_state ( struct net80211_device *dev,
                                        u16 status )
 {
        /* The conditions in this function are deliberately formulated
-          to be decidable at compile-time. */
+          to be decidable at compile-time in most cases. */
        const int statmsk = NET80211_STATUS_MASK | NET80211_IS_REASON;
 
+       if ( clear & NET80211_PROBED )
+               clear |= NET80211_AUTHENTICATED;
+
        if ( clear & NET80211_AUTHENTICATED )
                clear |= NET80211_ASSOCIATED;
 
@@ -766,10 +823,18 @@ static inline void net80211_set_state ( struct net80211_device *dev,
        dev->state = ( dev->state & ~statmsk ) | ( status & statmsk );
 
        if ( clear & NET80211_ASSOCIATED )
-               dev->netdev->state &= ~NETDEV_LINK_UP;
+               netdev_link_down ( dev->netdev );
 
        if ( ( clear | set ) & NET80211_ASSOCIATED )
                dev->op->config ( dev, NET80211_CFG_ASSOC );
+
+       if ( status != 0 ) {
+               if ( status & NET80211_IS_REASON )
+                       dev->assoc_rc = -E80211_REASON ( status );
+               else
+                       dev->assoc_rc = -E80211_STATUS ( status );
+               netdev_link_err ( dev->netdev, dev->assoc_rc );
+       }
 }
 
 /**
@@ -1038,62 +1103,64 @@ net80211_marshal_request_info ( struct net80211_device *dev,
        return ie;
 }
 
-/** Seconds to always take when probing, to gather better signal strengths */
-#define NET80211_PROBE_GATHER    2
+/** Seconds to wait after finding a network, to possibly find better APs
+ *
+ * This is used when a specific SSID to scan for is specified.
+ */
+#define NET80211_PROBE_GATHER    1
 
-/** Seconds to allow a probe to take, if no usable AP has yet been found */
+/** Seconds to wait after finding a network, to possibly find others
+ *
+ * This is used when an empty SSID is specified, to scan for all
+ * networks.
+ */
+#define NET80211_PROBE_GATHER_ALL 2
+
+/** Seconds to allow a probe to take if no network has been found */
 #define NET80211_PROBE_TIMEOUT   6
 
 /**
- * Probe 802.11 networks
+ * Begin probe of 802.11 networks
  *
  * @v dev      802.11 device
  * @v essid    SSID to probe for, or "" to accept any (may not be NULL)
  * @v active   Whether to use active scanning
- * @ret wlan   WLAN structure for best detected network
+ * @ret ctx    Probe context
  *
  * Active scanning may only be used on channels 1-11 in the 2.4GHz
  * band, due to gPXE's lack of a complete regulatory database. If
  * active scanning is used, probe packets will be sent on each
  * channel; this can allow association with hidden-SSID networks if
  * the SSID is properly specified.
+ *
+ * A @c NULL return indicates an out-of-memory condition.
+ *
+ * The returned context must be periodically passed to
+ * net80211_probe_step() until that function returns zero.
  */
-struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
-                                       const char *essid, int active )
+struct net80211_probe_ctx * net80211_probe_start ( struct net80211_device *dev,
+                                                  const char *essid,
+                                                  int active ) 
 {
-       int old_keep = net80211_keep_mgmt ( dev, 1 );
-       u32 start_ticks = currticks(); /* started scanning */
-       u32 change_ticks = currticks(); /* changed channel */
-       u32 start_timeout = NET80211_PROBE_TIMEOUT * ticks_per_sec();
-       u32 gather_timeout = NET80211_PROBE_GATHER * ticks_per_sec();
-       u32 change_timeout = ticks_per_sec() / ( active ? 2 : 6 );
-       int hop = 5;
-       struct net80211_wlan *wlan = NULL;
-       void *probe = NULL;
-       int probe_len = 0;
-       struct io_buffer *iob;
-       union ieee80211_ie *ie;
-       int rc;
+       struct net80211_probe_ctx *ctx = zalloc ( sizeof ( *ctx ) );
 
-       /* Channels on 2.4GHz overlap, and the most commonly used
-          are 1, 6, and 11. We'll get a result faster if we check
-          every 5 channels, but in order to hit all of them the
-          number of channels must be relatively prime to 5. If it's
-          not, tweak the hop. */
-       while ( dev->nr_channels % hop == 0 && hop > 1 )
-               hop--;  
+       if ( ! ctx )
+               return NULL;
 
-       dev->channel = 0;
+       ctx->dev = dev;
+       ctx->old_keep_mgmt = net80211_keep_mgmt ( dev, 1 );
+       ctx->essid = essid;
+       if ( dev->essid != ctx->essid )
+               strcpy ( dev->essid, ctx->essid );
 
-       /* If we're scanning actively, make a probe packet. */
        if ( active ) {
                struct ieee80211_probe_req *probe_req;
+               union ieee80211_ie *ie;
 
-               probe = malloc ( 128 );
-               probe_req = probe;
+               ctx->probe = alloc_iob ( 128 );
+               iob_reserve ( ctx->probe, IEEE80211_TYP_FRAME_HEADER_LEN );
+               probe_req = ctx->probe->data;
 
-               /* Put the standard SSID/rate info in the probe, and
-                  then add our requests: country, ERP, RSN info */
                ie = net80211_marshal_request_info ( dev,
                                                     probe_req->info_element );
                ie->id = IEEE80211_IE_REQUEST;
@@ -1102,53 +1169,115 @@ struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
                ie->request[1] = IEEE80211_IE_ERP_INFO;
                ie->request[2] = IEEE80211_IE_RSN;
 
-               probe_len = ( void * ) ie + ie->len + 2 - probe;
+               ie = ieee80211_next_ie ( ie, NULL );
+
+               iob_put ( ctx->probe, ( void * ) ie - ctx->probe->data );
        }
 
-       while ( currticks() < start_ticks + start_timeout ) {
-               struct ieee80211_frame *hdr;
-               struct ieee80211_beacon *beacon; /* == iee80211_probe_resp */
-               union ieee80211_ie *ie;
-               int signal;
-               u16 type;
+       ctx->ticks_start = currticks();
+       ctx->ticks_beacon = 0;
+       ctx->ticks_channel = currticks();
+       ctx->hop_time = ticks_per_sec() / ( active ? 2 : 6 );
+
+       /*
+        * Channels on 2.4GHz overlap, and the most commonly used
+        * are 1, 6, and 11. We'll get a result faster if we check
+        * every 5 channels, but in order to hit all of them the
+        * number of channels must be relatively prime to 5. If it's
+        * not, tweak the hop.
+        */
+       ctx->hop_step = 5;
+       while ( dev->nr_channels % ctx->hop_step == 0 && ctx->hop_step > 1 )
+               ctx->hop_step--;
+
+       ctx->beacons = malloc ( sizeof ( *ctx->beacons ) );
+       INIT_LIST_HEAD ( ctx->beacons );
 
-               if ( currticks() < change_ticks + change_timeout ) {
-                       dev->channel = ( dev->channel + hop ) % dev->nr_channels;
-                       dev->op->config ( dev, NET80211_CFG_CHANNEL );
-                       udelay ( dev->hw->channel_change_time );
-
-                       change_ticks = currticks();
-
-                       if ( active ) {
-                               int hdr_len = IEEE80211_TYP_FRAME_HEADER_LEN;
-                               struct io_buffer *piob =
-                                       alloc_iob ( probe_len + hdr_len );
-                               iob_reserve ( piob, hdr_len );
-                               memcpy ( iob_put ( piob, probe_len ), probe,
-                                        probe_len );
-
-                               rc = net80211_tx_mgmt ( dev,
-                                                       IEEE80211_STYPE_PROBE_REQ,
-                                                       net80211_ll_broadcast,
-                                                       iob_disown ( piob ) );
-                               if ( rc ) {
-                                       DBGC ( dev, "802.11 %p send probe "
-                                              "failed: %s\n", dev,
-                                              strerror ( rc ) );
-                               }
+       dev->channel = 0;
+       dev->op->config ( dev, NET80211_CFG_CHANNEL );
+
+       return ctx;
+}
+
+/**
+ * Continue probe of 802.11 networks
+ *
+ * @v ctx      Probe context returned by net80211_probe_start()
+ * @ret rc     Probe status
+ *
+ * The return code will be 0 if the probe is still going on (and this
+ * function should be called again), a positive number if the probe
+ * completed successfully, or a negative error code if the probe
+ * failed for that reason.
+ *
+ * Whether the probe succeeded or failed, you must call
+ * net80211_probe_finish_all() or net80211_probe_finish_best()
+ * (depending on whether you want information on all networks or just
+ * the best-signal one) in order to release the probe context. A
+ * failed probe may still have acquired some valid data.
+ */
+int net80211_probe_step ( struct net80211_probe_ctx *ctx ) 
+{
+       struct net80211_device *dev = ctx->dev;
+       u32 start_timeout = NET80211_PROBE_TIMEOUT * ticks_per_sec();
+       u32 gather_timeout = ticks_per_sec();
+       u32 now = currticks();
+       struct io_buffer *iob;
+       int signal;
+       int rc;
+       char ssid[IEEE80211_MAX_SSID_LEN + 1];
+
+       gather_timeout *= ( ctx->essid[0] ? NET80211_PROBE_GATHER :
+                           NET80211_PROBE_GATHER_ALL );
+
+       /* Time out if necessary */
+       if ( now >= ctx->ticks_start + start_timeout )
+               return list_empty ( ctx->beacons ) ? -ETIMEDOUT : +1;
+
+       if ( ctx->ticks_beacon > 0 && now >= ctx->ticks_start + gather_timeout )
+               return +1;
+
+       /* Change channels if necessary */
+       if ( now >= ctx->ticks_channel + ctx->hop_time ) {
+               dev->channel = ( dev->channel + ctx->hop_step )
+                       % dev->nr_channels;
+               dev->op->config ( dev, NET80211_CFG_CHANNEL );
+               udelay ( dev->hw->channel_change_time );
+
+               ctx->ticks_channel = now;
+
+               if ( ctx->probe ) {
+                       struct io_buffer *siob = ctx->probe; /* to send */
+
+                       /* make a copy for future use */
+                       iob = alloc_iob ( siob->tail - siob->head );
+                       iob_reserve ( iob, iob_headroom ( siob ) );
+                       memcpy ( iob_put ( iob, iob_len ( siob ) ),
+                                siob->data, iob_len ( siob ) );
+
+                       ctx->probe = iob;
+                       rc = net80211_tx_mgmt ( dev, IEEE80211_STYPE_PROBE_REQ,
+                                               net80211_ll_broadcast,
+                                               iob_disown ( siob ) );
+                       if ( rc ) {
+                               DBGC ( dev, "802.11 %p send probe failed: "
+                                      "%s\n", dev, strerror ( rc ) );
+                               return rc;
                        }
                }
+       }
 
-               dev->op->poll ( dev );
-               iob = net80211_mgmt_dequeue ( dev, &signal );
-               if ( ! iob ) {
-                       cpu_nap();
-                       continue;
-               }
+       /* Check for new management packets */
+       while ( ( iob = net80211_mgmt_dequeue ( dev, &signal ) ) != NULL ) {
+               struct ieee80211_frame *hdr;
+               struct ieee80211_beacon *beacon;
+               union ieee80211_ie *ie;
+               struct net80211_wlan *wlan;
+               u16 type;
 
                hdr = iob->data;
                type = hdr->fc & IEEE80211_FC_SUBTYPE;
-               beacon = (struct ieee80211_beacon *)hdr->data;
+               beacon = ( struct ieee80211_beacon * ) hdr->data;
 
                if ( type != IEEE80211_STYPE_BEACON &&
                     type != IEEE80211_STYPE_PROBE_RESP ) {
@@ -1157,8 +1286,8 @@ struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
                }
 
                if ( ( void * ) beacon->info_element >= iob->tail ) {
-                       DBGC2 ( dev, "802.11 %p probe: beacon with no IEs\n",
-                               dev );
+                       DBGC ( dev, "802.11 %p probe: beacon with no IEs\n",
+                              dev );
                        goto drop;
                }
 
@@ -1167,71 +1296,137 @@ struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
                        ie = ieee80211_next_ie ( ie, iob->tail );
 
                if ( ! ie ) {
-                       /* didn't find an SSID */
-                       DBGC2 ( dev, "802.11 %p probe: beacon with no SSID\n",
-                               dev );
+                       DBGC ( dev, "802.11 %p probe: beacon with no SSID\n",
+                              dev );
                        goto drop;
                }
-               if ( essid[0] && memcmp ( essid, ie->ssid, ie->len ) != 0 ) {
-                       ie->ssid[ie->len] = 0;
+
+               memcpy ( ssid, ie->ssid, ie->len );
+               ssid[ie->len] = 0;
+
+               if ( ctx->essid[0] && strcmp ( ctx->essid, ssid ) != 0 ) {
                        DBGC2 ( dev, "802.11 %p probe: beacon with wrong SSID "
-                               "(%s)\n", dev, ie->ssid );
+                               "(%s)\n", dev, ssid );
                        goto drop;
                }
 
-               if ( ! wlan ) {
-                       wlan = malloc ( sizeof ( *wlan ) );
-                       if ( ! wlan ) {
-                               DBGC ( dev, "802.11 %p probe: out of memory\n",
-                                      dev );
-                               goto fail;
+               /* See if we've got an entry for this network */
+               list_for_each_entry ( wlan, ctx->beacons, list ) {
+                       if ( strcmp ( wlan->essid, ssid ) != 0 )
+                               continue;
+
+                       if ( signal < wlan->signal ) {
+                               DBGC2 ( dev, "802.11 %p probe: beacon for %s "
+                                       "(%s) with weaker signal %d\n", dev,
+                                       ssid, eth_ntoa ( hdr->addr3 ), signal );
+                               goto drop;
                        }
 
-                       DBGP ( "802.11 %p first good beacon:\n", dev );
-                       DBGP_HD ( iob->data, iob_len ( iob ) );
-               } else if ( signal < wlan->signal ) {
-                       ie->ssid[ie->len] = 0;
-                       DBGC2 ( dev, "802.11 %p probe: beacon for %s (%s) with "
-                               "weaker signal %d\n", dev, ie->ssid,
-                               eth_ntoa ( hdr->addr3 ), signal );
-                       goto drop;
-               } else {
-                       free_iob ( iob_disown ( wlan->beacon ) );
+                       goto fill;
                }
 
-               wlan->essid[ie->len] = 0;
-               memcpy ( wlan->essid, ie->ssid, ie->len );
-               memcpy ( wlan->bssid, hdr->addr3, ETH_ALEN);
+               /* No entry yet - make one */
+               wlan = zalloc ( sizeof ( *wlan ) );
+               strcpy ( wlan->essid, ssid );
+               list_add_tail ( &wlan->list, ctx->beacons );
+
+               /* Whether we're using an old entry or a new one, fill
+                  it with new data. */
+       fill:
+               memcpy ( wlan->bssid, hdr->addr3, ETH_ALEN );
                wlan->signal = signal;
-               wlan->beacon = iob;
-               wlan->security = 0; /* XXX implement */
                wlan->channel = dev->channels[dev->channel].channel_nr;
-               DBGC2 ( dev, "802.11 %p probe: beacon for %s (%s) with "
-                       "new best signal %d\n", dev, wlan->essid,
-                       eth_ntoa ( wlan->bssid ), wlan->signal );
 
-               if ( currticks() > start_ticks + gather_timeout )
-                       break;
+               /* Copy this I/O buffer into a new wlan->beacon; the
+                * iob we've got probably came from the device driver
+                * and may have the full 2.4k allocation, which we
+                * don't want to keep around wasting memory.
+                */
+               free_iob ( wlan->beacon );
+               wlan->beacon = alloc_iob ( iob_len ( iob ) );
+               memcpy ( iob_put ( wlan->beacon, iob_len ( iob ) ),
+                        iob->data, iob_len ( iob ) );
+
+               /* XXX actually check capab and RSN ie to
+                  figure this out */
+               wlan->handshaking = NET80211_SECPROT_NONE;
+               wlan->crypto = NET80211_CRYPT_NONE;
 
-               continue;
+               ctx->ticks_beacon = now;
+
+               DBGC2 ( dev, "802.11 %p probe: good beacon for %s (%s)\n",
+                       dev, wlan->essid, eth_ntoa ( wlan->bssid ) );
 
        drop:
                free_iob ( iob );
        }
 
-       if ( ! wlan )
-               DBGC ( dev, "802.11 %p probe: found no response for '%s'\n",
-                      dev, essid );
+       return 0;
+}
 
-       free ( probe );
-       net80211_keep_mgmt ( dev, old_keep );
-       return wlan;
 
- fail:
-       free ( probe );
-       net80211_free_wlan ( wlan );
-       net80211_keep_mgmt ( dev, old_keep );
-       return NULL;
+/**
+ * Finish probe of 802.11 networks, returning best-signal network found
+ *
+ * @v ctx      Probe context
+ * @ret wlan   Best-signal network found, or @c NULL if none were found
+ *
+ * If net80211_probe_start() was called with a particular SSID
+ * parameter as filter, only a network with that SSID (matching
+ * case-sensitively) can be returned from this function.
+ */
+struct net80211_wlan *
+net80211_probe_finish_best ( struct net80211_probe_ctx *ctx )
+{
+       struct net80211_wlan *best = NULL, *wlan;
+
+       list_for_each_entry ( wlan, ctx->beacons, list ) {
+               if ( ! best || best->signal < wlan->signal )
+                       best = wlan;
+       }
+
+       if ( best )
+               list_del ( &best->list );
+       else
+               DBGC ( ctx->dev, "802.11 %p probe: found nothing for '%s'\n",
+                      ctx->dev, ctx->essid );
+
+       if ( ! list_empty ( ctx->beacons ) )
+               net80211_free_wlanlist ( ctx->beacons );
+
+       net80211_keep_mgmt ( ctx->dev, ctx->old_keep_mgmt );
+
+       if ( ctx->probe )
+               free_iob ( ctx->probe );
+
+       free ( ctx );
+
+       return best;
+}
+
+
+/**
+ * Finish probe of 802.11 networks, returning all networks found
+ *
+ * @v ctx      Probe context
+ * @ret list   List of net80211_wlan detailing networks found
+ *
+ * If net80211_probe_start() was called with a particular SSID
+ * parameter as filter, this will always return either an empty or a
+ * one-element list.
+ */
+struct list_head *net80211_probe_finish_all ( struct net80211_probe_ctx *ctx )
+{
+       struct list_head *beacons = ctx->beacons;
+
+       net80211_keep_mgmt ( ctx->dev, ctx->old_keep_mgmt );
+
+       if ( ctx->probe )
+               free_iob ( ctx->probe );
+
+       free ( ctx );
+
+       return beacons;
 }
 
 
@@ -1248,6 +1443,29 @@ void net80211_free_wlan ( struct net80211_wlan *wlan )
        }
 }
 
+
+/**
+ * Free list of WLAN structures
+ *
+ * @v list     List of WLAN structures to free
+ */
+void net80211_free_wlanlist ( struct list_head *list ) 
+{
+       struct net80211_wlan *wlan, *tmp;
+
+       if ( ! list )
+               return;
+
+       list_for_each_entry_safe ( wlan, tmp, list, list ) {
+               list_del ( &wlan->list );
+               net80211_free_wlan ( wlan );
+       }
+
+       free ( list );
+}
+
+
+
 #define ASSOC_TIMEOUT  TICKS_PER_SEC
 #define ASSOC_RETRIES  2
 
@@ -1267,9 +1485,9 @@ static void net80211_step_associate ( struct process *proc )
                if ( ! dev->associating )
                        return;
 
-               if ( currticks() - dev->associating->last_packet > ASSOC_TIMEOUT ) {
-                       dev->associating->times_tried++;
-                       if ( ++dev->associating->times_tried > ASSOC_RETRIES ) {
+               if ( currticks() - dev->ctx.assoc->last_packet > ASSOC_TIMEOUT ) {
+                       dev->ctx.assoc->times_tried++;
+                       if ( ++dev->ctx.assoc->times_tried > ASSOC_RETRIES ) {
                                rc = -ETIMEDOUT;
                                goto fail;
                        }
@@ -1277,26 +1495,44 @@ static void net80211_step_associate ( struct process *proc )
                        return;
                }
        } else {
-               if ( dev->associating )
-                       dev->associating->times_tried = 0;
+               if ( dev->state & NET80211_PROBED )
+                       dev->ctx.assoc->times_tried = 0;
        }
 
-       if ( ! dev->associating ) {
+       if ( ! ( dev->state & NET80211_PROBED ) ) {
                /* state: scan */
-               int active = fetch_intz_setting ( NULL,
-                                                 &net80211_active_setting );
-               int band = dev->hw->bands;
 
-               if ( active )
-                       band &= ~NET80211_BAND_5GHZ;
+               if ( ! dev->ctx.probe ) {
+                       /* start probe */
+                       int active = fetch_intz_setting ( NULL,
+                                               &net80211_active_setting );
+                       int band = dev->hw->bands;
 
-               rc = net80211_prepare_default ( dev, band, active );
-               if ( rc )
-                       goto fail;
+                       if ( active )
+                               band &= ~NET80211_BAND_5GHZ;
+
+                       rc = net80211_prepare_probe ( dev, band, active );
+                       if ( rc )
+                               goto fail;
 
-               dev->associating = net80211_probe ( dev, dev->essid, active );
+                       dev->ctx.probe = net80211_probe_start ( dev, dev->essid,
+                                                               active );
+                       if ( ! dev->ctx.probe ) {
+                               dev->assoc_rc = -ENOMEM;
+                               goto fail;
+                       }
+               }
+               
+               rc = net80211_probe_step ( dev->ctx.probe );
+               if ( ! rc ) {
+                       return; /* still going */
+               }
+
+               dev->associating = net80211_probe_finish_best ( dev->ctx.probe );
+               dev->ctx.probe = NULL;
                if ( ! dev->associating ) {
-                       rc = -ETIMEDOUT;
+                       if ( rc > 0 ) /* "successful" probe found nothing */
+                               rc = -ETIMEDOUT;
                        goto fail;
                }
 
@@ -1304,39 +1540,47 @@ static void net80211_step_associate ( struct process *proc )
                       dev->associating->essid,
                       eth_ntoa ( dev->associating->bssid ) );
 
-               dev->associating->method = IEEE80211_AUTH_OPEN_SYSTEM;
+               dev->ctx.assoc = zalloc ( sizeof ( *dev->ctx.assoc ) );
+               if ( ! dev->ctx.assoc ) {
+                       rc = -ENOMEM;
+                       goto fail;
+               }
+
+               dev->state |= NET80211_PROBED;
+               dev->ctx.assoc->method = IEEE80211_AUTH_OPEN_SYSTEM;
+
                return;
        }
 
+       dev->ctx.assoc->last_packet = currticks();
+
        if ( ! ( dev->state & NET80211_AUTHENTICATED ) ) {
                /* state: prepare and authenticate */
-               int method = dev->associating->method;
 
                if ( status != IEEE80211_STATUS_SUCCESS ) {
                        /* we tried authenticating already, but failed */
+                       int method = dev->ctx.assoc->method;
 
                        if ( method == IEEE80211_AUTH_OPEN_SYSTEM &&
                             ( status == IEEE80211_STATUS_AUTH_CHALL_INVALID ||
                               status == IEEE80211_STATUS_AUTH_ALGO_UNSUPP ) ) {
                                /* Maybe this network uses Shared Key? */
-                               method = dev->associating->method =
+                               dev->ctx.assoc->method =
                                        IEEE80211_AUTH_SHARED_KEY;
                        } else {
-                               rc = E80211_STATUS ( status );
                                goto fail;
                        }
                }
 
                DBGC ( dev, "802.11 %p authenticating with method %d\n", dev,
-                      method );
+                      dev->ctx.assoc->method );
 
-               dev->associating->last_packet = currticks();
-
-               rc = net80211_prepare ( dev, dev->associating );
+               rc = net80211_prepare_assoc ( dev, dev->associating );
                if ( rc )
                        goto fail;
 
-               rc = net80211_send_auth ( dev, dev->associating, method );
+               rc = net80211_send_auth ( dev, dev->associating,
+                                         dev->ctx.assoc->method );
                if ( rc )
                        goto fail;
 
@@ -1347,12 +1591,8 @@ static void net80211_step_associate ( struct process *proc )
                /* state: associate */
                DBGC ( dev, "802.11 %p associating\n", dev );
 
-               dev->associating->last_packet = currticks();
-
-               if ( status != IEEE80211_STATUS_SUCCESS ) {
-                       rc = E80211_STATUS ( status );
+               if ( status != IEEE80211_STATUS_SUCCESS )
                        goto fail;
-               }
 
                rc = net80211_send_assoc ( dev, dev->associating );
                if ( rc )
@@ -1365,8 +1605,6 @@ static void net80211_step_associate ( struct process *proc )
                /* state: crypto sync */
                DBGC ( dev, "802.11 %p security handshaking\n", dev );
 
-               dev->associating->last_packet = currticks();
-
                dev->state |= NET80211_CRYPTO_SYNCED;
                /* XXX need to actually do something here once we
                   support WPA */
@@ -1374,19 +1612,35 @@ static void net80211_step_associate ( struct process *proc )
        }
 
        /* state: done! */
+       netdev_link_up ( dev->netdev );
+       dev->assoc_rc = 0;
+       dev->state &= ~NET80211_WORKING;
+
+       free ( dev->ctx.assoc );
+       net80211_free_wlan ( dev->associating );
+       dev->associating = NULL;
+
+       process_del ( proc );
+
        DBGC ( dev, "802.11 %p associated with %s (%s)\n", dev,
               dev->essid, eth_ntoa ( dev->bssid ) );
 
-       dev->netdev->state |= NETDEV_LINK_UP;
-       dev->state &= ~NET80211_WORKING;
-       process_del ( proc );
        return;
 
  fail:
        dev->state &= ~( NET80211_WORKING | NET80211_WAITING );
-       DBGC ( dev, "802.11 %p association failed (state=%04x): "
-              "%s\n", dev, dev->state, strerror ( rc ) );
+       if ( rc )
+               dev->assoc_rc = rc;
+
+       netdev_link_err ( dev->netdev, dev->assoc_rc );
+
+       net80211_free_wlan ( dev->associating );
+       dev->associating = NULL;
+
        process_del ( proc );
+
+       DBGC ( dev, "802.11 %p association failed (state=%04x): "
+              "%s\n", dev, dev->state, strerror ( dev->assoc_rc ) );
 }
 
 /**
@@ -1414,7 +1668,7 @@ int net80211_check_ssid_update ( void )
                if ( strcmp ( ssid, dev->essid ) != 0 ) {
                        DBGC ( dev, "802.11 %p updating association: "
                               "%s -> %s\n", dev, dev->essid, ssid );
-                       net80211_set_state ( dev, NET80211_AUTHENTICATED, 0, 0 );
+                       net80211_set_state ( dev, NET80211_PROBED, 0, 0 );
                        net80211_autoassociate ( dev );
                }
        }
@@ -1445,6 +1699,7 @@ void net80211_autoassociate ( struct net80211_device *dev )
                              &net80211_ssid_setting, dev->essid,
                              IEEE80211_MAX_SSID_LEN );
        dev->essid[len] = 0;
+       dev->ctx.probe = NULL;
        dev->associating = NULL;
        net80211_set_state ( dev, NET80211_ASSOCIATED, NET80211_WORKING, 0 );
 }
@@ -1557,8 +1812,8 @@ int net80211_set_channel_nr ( struct net80211_device *dev, int channel )
  * @v active   Whether the scanning will be active
  * @ret rc     Return status code
  */
-int net80211_prepare_default ( struct net80211_device *dev, int band,
-                              int active )
+int net80211_prepare_probe ( struct net80211_device *dev, int band,
+                            int active )
 {
        if ( active && band != NET80211_BAND_2GHZ ) {
                DBGC ( dev, "802.11 %p cannot perform active scanning on "
@@ -1603,8 +1858,8 @@ int net80211_prepare_default ( struct net80211_device *dev, int band,
  * @v wlan     WLAN to prepare for communication with
  * @ret rc     Return status code
  */
-int net80211_prepare ( struct net80211_device *dev,
-                      struct net80211_wlan *wlan )
+int net80211_prepare_assoc ( struct net80211_device *dev,
+                            struct net80211_wlan *wlan )
 {
        struct ieee80211_frame *hdr = wlan->beacon->data;
        struct ieee80211_beacon *beacon =
@@ -1753,7 +2008,7 @@ int net80211_send_assoc ( struct net80211_device *dev,
                assoc->capability |= IEEE80211_CAPAB_SHORT_PMBL;
        if ( ! ( dev->hw->flags & NET80211_HW_NO_SHORT_SLOT ) )
                assoc->capability |= IEEE80211_CAPAB_SHORT_SLOT;
-       if ( wlan->security )
+       if ( wlan->crypto )
                assoc->capability |= IEEE80211_CAPAB_PRIVACY;
 
        assoc->listen_interval = 1;
@@ -1803,6 +2058,32 @@ static void net80211_handle_assoc_reply ( struct net80211_device *dev,
                             IEEE80211_STATUS_SUCCESS );
 }
 
+
+/**
+ * Send 802.11 disassociation frame
+ *
+ * @v dev      802.11 device
+ * @v reason   Reason for disassociation
+ * @ret rc     Return status code
+ */
+static int net80211_send_disassoc ( struct net80211_device *dev, int reason )
+{
+       struct io_buffer *iob = alloc_iob ( 64 );
+       struct ieee80211_disassoc *disassoc;
+
+       if ( ! ( dev->state & NET80211_ASSOCIATED ) )
+               return -EINVAL;
+
+       net80211_set_state ( dev, NET80211_ASSOCIATED, 0, 0 );
+       iob_reserve ( iob, IEEE80211_TYP_FRAME_HEADER_LEN );
+       disassoc = iob_put ( iob, sizeof ( *disassoc ) );
+       disassoc->reason = reason;
+
+       return net80211_tx_mgmt ( dev, IEEE80211_STYPE_DISASSOC, dev->bssid,
+                                 iob );
+}
+
+
 /**
  * Handle receipt of 802.11 management frame
  *
@@ -1817,6 +2098,7 @@ static void net80211_handle_mgmt ( struct net80211_device *dev,
        struct ieee80211_disassoc *disassoc;
        u16 stype = hdr->fc & IEEE80211_FC_SUBTYPE;
        int keep = 0;
+       int is_deauth = ( stype == IEEE80211_STYPE_DEAUTH );
 
        if ( ( hdr->fc & IEEE80211_FC_TYPE ) != IEEE80211_TYPE_MGMT ) {
                free_iob ( iob );
@@ -1827,18 +2109,14 @@ static void net80211_handle_mgmt ( struct net80211_device *dev,
                /* These are usually indicative of a deeper problem,
                   so don't just reassociate right away. */
        case IEEE80211_STYPE_DEAUTH:
-               disassoc = ( struct ieee80211_disassoc * ) hdr->data;
-               net80211_set_state ( dev, NET80211_AUTHENTICATED, 0,
-                                    NET80211_IS_REASON | disassoc->reason );
-               DBGC ( dev, "802.11 %p deauthenticated: reason %d\n",
-                      dev, disassoc->reason );
-               break;
        case IEEE80211_STYPE_DISASSOC:
                disassoc = ( struct ieee80211_disassoc * ) hdr->data;
-               net80211_set_state ( dev, NET80211_ASSOCIATED, 0,
+               net80211_set_state ( dev, is_deauth ? NET80211_ASSOCIATED :
+                                    NET80211_AUTHENTICATED, 0,
                                     NET80211_IS_REASON | disassoc->reason );
-               DBGC ( dev, "802.11 %p disassociated: reason %d\n",
-                     dev, disassoc->reason );
+               DBGC ( dev, "802.11 %p %s: reason %d\n",
+                      dev, is_deauth ? "deauthenticated" : "disassociated",
+                      disassoc->reason );
                break;
 
                /* We handle authentication and association. */
@@ -2117,7 +2395,7 @@ void net80211_rx ( struct net80211_device *dev, struct io_buffer *iob,
                        dev, hdr->seq );
        }
 
-       if ( dev->netdev->state & NETDEV_LINK_UP ) {
+       if ( netdev_link_ok ( dev->netdev ) ) {
                netdev_rx ( dev->netdev, iob );
                return;
        }