[802.11] The 802.11 MAC layer, second big commit
authorJoshua Oreman <oremanj@xenon.get-linux.org>
Sun, 7 Jun 2009 04:47:29 +0000 (21:47 -0700)
committerJoshua Oreman <oremanj@xenon.get-linux.org>
Sun, 7 Jun 2009 04:47:29 +0000 (21:47 -0700)
With this commit the MAC layer is almost "barebones feature-complete";
the only things I still need to implement before I can test is the
network probe functionality. Various changes include:

- net80211_device.channels is now a statically declared array, to
  avoid constantly reallocating it.
- net80211_device.assoc has been replaced by "state", containing a
  set of flags (authenticated, associated, etc) related to the
  association process as well as the most recent IEEE status code from
  said process.
- Signal strength info has been moved from a per-device to a per-frame
  basis, since its only real use (choosing between different APs on
  the same network while scanning) requires that.
    -> The code for this would be made much cleaner if there were
       space for a couple bytes of private data in io_buffer.
- The net80211_wlan structure has been amended to include a copy of
  the beacon from this access point and an authentication-method state
  variable for the association task. Association now can be handled
  just by passing one structure around.
- Added net80211_prepare_default(), to create a default list of
  channels to listen on during network scanning.
- Implemented the association API described on my notes page.
- Added settings for SSID and hidden-network-ness.
- Added a function to send an arbitrary management frame.
- Added processing for received beacons.
- Implemented most of the association process.

src/include/gpxe/ieee80211.h
src/include/gpxe/net80211.h
src/net/net80211.c

index b67c471..0b5092a 100644 (file)
@@ -53,6 +53,7 @@ FILE_LICENCE(GPL2_OR_LATER);
 #define  IEEE80211_STYPE_DISASSOC     0x00A0
 #define  IEEE80211_STYPE_AUTH         0x00B0
 #define  IEEE80211_STYPE_DEAUTH       0x00C0
+#define  IEEE80211_STYPE_ACTION              0x00D0
 /* for control frames: */
 #define  IEEE80211_STYPE_RTS          0x00B0
 #define  IEEE80211_STYPE_CTS          0x00C0
@@ -132,13 +133,13 @@ struct ieee80211_cts
 #define IEEE80211_CAPAB_CFPR          0x0008 /* [0] CF-Poll Request */
 #define IEEE80211_CAPAB_PRIVACY       0x0010 /* [c] Encrypted network */
 #define IEEE80211_CAPAB_SHORT_PMBL    0x0020 /* [h] Short RF preambles supported */
-#define IEEE80211_CAPAB_PBCC          0x0040 /* [h] PBCC modulation supported */
+#define IEEE80211_CAPAB_PBCC          0x0040 /* [0] PBCC modulation supported */
 #define IEEE80211_CAPAB_CHAN_AGILITY  0x0080 /* [0] Channel Agility supported */
 #define IEEE80211_CAPAB_SPECTRUM_MGMT 0x0100 /* [0] Spectrum Management required */
 #define IEEE80211_CAPAB_QOS           0x0200 /* [0] QoS supported */
 #define IEEE80211_CAPAB_SHORT_SLOT    0x0400 /* [h] Short Slot Time option supported */
 #define IEEE80211_CAPAB_APSD          0x0800 /* [0] APSD option supported */
-#define IEEE80211_CAPAB_DSSS_OFDM     0x2000 /* [h] DSSS/OFDM modulation supported
+#define IEEE80211_CAPAB_DSSS_OFDM     0x2000 /* [0] DSSS/OFDM modulation supported
                                                    (802.11g/b interoperability) */
 #define IEEE80211_CAPAB_DELAYED_BACK  0x4000 /* [0] Delayed block ACK supported */
 #define IEEE80211_CAPAB_IMMED_BACK    0x8000 /* [0] Immediate block ACK supported */
@@ -348,6 +349,9 @@ struct ieee80211_probe_req
 struct ieee80211_auth
 {
        u16 algorithm;
+#define IEEE80211_AUTH_OPEN_SYSTEM  0
+#define IEEE80211_AUTH_SHARED_KEY   1
+
        u16 tx_seq;
        u16 status;
        struct ieee80211_ie info_element[0];
index 613df62..c5d5086 100644 (file)
@@ -13,6 +13,7 @@
 /*
  * Major things NOT YET supported:
  * - any type of security (WEP, WPA, WPA2, WPA Enterprise)
+ * - CTS-to-self protection for ERPs
  * - adaptive rate setting
  * - 802.11n
  *
@@ -87,7 +88,8 @@ struct net80211_channel
 #define NET80211_MODE_G  (1 << 2)
 #define NET80211_MODE_N  (1 << 3)
 
-#define NET80211_MAX_RATES 16
+#define NET80211_MAX_RATES     16
+#define NET80211_MAX_CHANNELS  32
 
 /** Information on the capabilities of the hardware supported by a
     driver; must be filled in by said driver. */
@@ -122,7 +124,6 @@ struct net80211_hw_info
                NET80211_SIGNAL_ARBITRARY, /**< arbitrary units */
                NET80211_SIGNAL_DB,        /**< dB relative to arbitrary base */
                NET80211_SIGNAL_DBM,       /**< dB relative to 1mW */
-               NET80211_SIGNAL_NOISE_DBM, /**< both signal and noise in dBm */
        } signal_type;
        
        /** List of transmit supported by the card, in 100kbps
@@ -231,11 +232,14 @@ struct net80211_device
        /** The asynchronous association process. (Successful
            association is represented as "link up".) */
        struct process proc_assoc;
+       /** Data structure for the association process. */
+       struct net80211_wlan *associating;
 
        /** A list of all possible channels, as computed from
            regulatory information in the beacon frame. `channel' is
-           an index into this list. */
-       struct net80211_channel *channels;
+           an index into this list. This should be NULL before
+           prepare() has been called. */
+       struct net80211_channel channels[NET80211_MAX_CHANNELS];
        /** The number of channels in the `channels' list. */
        u8 nr_channels;
        /** The channel currently in use, as an index into the
@@ -255,31 +259,33 @@ struct net80211_device
        /** If basic_rates & (1 << i), then rates[i] is a basic rate. */
        u32 basic_rates;
 
-       /** Are we associated to a network? */
-       short assoc;
+       /** State of our association to the network; bit OR of
+           NET80211_*ED and the most recent status returned by the
+           AP. */
+#define NET80211_STATUS_MASK    0xFF /**< Mask for IEEE status code */
+#define NET80211_AUTHENTICATED  (1 << 8)
+#define NET80211_ASSOCIATED     (1 << 9)
+#define NET80211_CRYPTO_SYNCED  (1 << 10)
+#define NET80211_WORKING        (1 << 11) /**< Association task is running */
+#define NET80211_WAITING        (1 << 12) /**< MAC layer awaiting AP reply */
+       short state;
        /** Encryption method for the network we're associated to.
            NULL for open. */
        struct net80211_crypto *crypto;
        /** Network to which we're associated, if `assoc' is set. */
        u8 bssid[ETH_ALEN];
        /** Name of the SSID we're connected to. */
-       char essid[IEEE80211_MAX_SSID_LEN];
+       char essid[IEEE80211_MAX_SSID_LEN+1];
        /** Association ID given to us by the AP. */
        u16 aid;
        /** ERP-related options, set dynamically based on the assoc
            reply packet. Set even for non-ERP networks based on the
            capabilities field. */
        int erp_flags;
-#define NET80211_ERP_USE_CTS_PROTECTION  (1 << 1)
+#define NET80211_ERP_USE_PROTECTION      (1 << 1)
 #define NET80211_ERP_USE_SHORT_PREAMBLE  (1 << 2)
 #define NET80211_ERP_USE_SHORT_SLOT      (1 << 3)
 
-       /** Signal strength. Driver sets this when it feels like
-           it. (units from hw->signal_type) */
-       int signal;
-       /** Noise level, if the driver can provide it. (dBm) */
-       int noise;
-
        /** Fragment reassembly state. IEEE standard requires >= 3. */
 #define NET80211_NR_CONCURRENT_FRAGS  3
        struct net80211_frag_cache frags[NET80211_NR_CONCURRENT_FRAGS];
@@ -297,18 +303,21 @@ struct net80211_device
            in this queue when keep_mgmt is true. */
        struct list_head mgmt_queue;
 
+       /** RX packet info (signal strength) for packets in the
+           management queue. */
+       struct list_head mgmt_info_queue;
+
        /** Store management packets only when TRUE. */
        int keep_mgmt;
 };
 
 /** Structure returned from the scanning functions and passed to
-    net80211_associate(). At least essid, bssid, channel, and security
-    must be filled in. channel should point to something in the
-    device's channels[] array. */
+    net80211_associate(). At least essid, bssid, channel, and beacon
+    must be filled in. */
 struct net80211_wlan
 {
        /** The human-readable ESSID (network name). */
-       char essid[IEEE80211_MAX_SSID_LEN];
+       char essid[IEEE80211_MAX_SSID_LEN+1];
 
        /** The MAC address of the "best" access point (strongest
            signal) for this ESSID. */
@@ -318,16 +327,24 @@ struct net80211_wlan
            point, measured in whatever units the hardware provides. */
        int signal;
 
-       /** The channel on which that access point communicates. */
+       /** The channel on which that access point communicates;
+           pointer to an appropriate entry in dev->channels. */
        struct net80211_channel *channel;
 
+       /** The complete beacon or probe-response frame from which
+           the network was identified. */
+       struct io_buffer *beacon;
+
        /** One of the NET80211_CRYPT_* constants indicating the
            security mode of the network. */
        int security;
+       
+       /** State variable to be used by association task. */
+       int method;
 
        /** Link to allow chaining multiple structures into a list to
            be returned from net80211_scan(). */
-       struct net80211_wlan *next;
+       struct list_head list;
 };
 
 /* Regulatory hints when we don't get any beacons: */
@@ -340,38 +357,53 @@ struct net80211_wlan
 
 /** Scans the area, returning a list of net80211_wlan structures
     corresponding to each of the reachable wireless networks. */
-struct net80211_wlan *net80211_scan(struct net80211_device *dev);
+struct list_head *net80211_scan(struct net80211_device *dev);
+
+/** Frees the list returned from a scan call. */
+void net80211_free_wlanlist(struct list_head *netlist);
 
 /** Scans for the given ESSID specifically. Can be used to associate
     with "hidden" networks on the 2.4GHz band. */
 struct net80211_wlan *net80211_probe(struct net80211_device *dev,
-                                    const char *essid);
+                                    const char *essid, int active);
+
+/** Frees a single net80211_wlan structure. */
+void net80211_free_wlan(struct net80211_wlan *wlan);
 
 /** Does scan/probe as appropriate given configured settings, and
     associates. Operates as a background process; this function
     returns immediately, before the association has been performed. */
 void net80211_autoassociate(struct net80211_device *dev);
 
-/** Frees the list returned from a scan or probe call. */
-void net80211_free_netlist(struct net80211_wlan *nets);
-
 /* -- general-use net80211 API: -- */
 
 /** If the passed net_device wraps a net80211_device, returns the
     net80211_device. If not, returns NULL. */
 struct net80211_device *net80211_get(struct net_device *netdev);
 
+/* - association steps: -   [normally performed by autoassociate] */
+
+/** Sets up the device's regulatory parameters to a sane default for
+    passive scanning or active 2.4GHz scanning. */
+int net80211_prepare_default(struct net80211_device *dev, int band,
+                            int active);
+
 /** Sets up the device's regulatory parameters as the provided beacon
     or probe response frame tells us to. Must be called before
     associating. */
-int net80211_prepare(struct net80211_device *netdev,
-                    struct io_buffer *iobuf);
+int net80211_prepare(struct net80211_device *dev, struct net80211_wlan *wlan);
+
+/** Performs an authentication sequence using the specified
+    method. Returns immediately, sets state variables. */
+int net80211_send_auth(struct net80211_device *dev, struct net80211_wlan *wlan,
+                      int method);
 
-/** Requests that we associate with the network identified by
-    `wlan'. If authentication credentials are required, they must
-    have been set up using a previous call. This function blocks until
-    the association either completes or fails. */
-int net80211_associate(struct net80211_device *dev, struct net80211_wlan *wlan);
+/** Performs the actual association with the network described by
+    `wlan'. Requires authentication to have previously succeeded. */
+int net80211_send_assoc(struct net80211_device *dev,
+                       struct net80211_wlan *wlan);
+
+/* - management frame handling: - */
 
 /** Sets the state of the device keeping management packets. Only call
     this with enable == 1 if you're planning to actually look at all
@@ -384,11 +416,23 @@ int net80211_keep_mgmt(struct net80211_device *dev, int enable);
 /** Returns the next packet from the device's received management
     packet queue, or NULL if the queue is empty. You must have called
     net80211_set_management(dev, 1) previously. Ownership of the
-    returned io_buffer passes to the calling function. */
-struct io_buffer *net80211_mgmt_dequeue(struct net80211_device *dev);
-
+    returned io_buffer passes to the calling function. If `signal' is
+    non-NULL it is filled with the signal strength of the received
+    frame in device units. */
+struct io_buffer *net80211_mgmt_dequeue(struct net80211_device *dev,
+                                       int *signal);
+
+/** Sends an 802.11 management packet. The passed io_buffer must have
+    at least IEEE80211_TYP_FRAME_HEADER_LEN = 24 bytes of headroom for
+    the 802.11 frame header to be prepended. The management frame is
+    sent to the AP of the network identified by `bssid'; it is not
+    possible to use this function to send management frames that are
+    not directed at APs. The frame is encrypted if fc & PROTECTED. */
+int net80211_tx_mgmt(struct net80211_device *dev, u16 fc, u8 bssid[6],
+                    struct io_buffer *iob);
 
 /* -- Driver API: -- */
+
 /* The 802.11 layer handles the things drivers usually do to the
    wrapping net_device. Drivers should use the below functions and
    *not* their net_device analogues on dev->netdev. */
@@ -408,8 +452,10 @@ int net80211_register(struct net80211_device *dev,
                      struct net80211_hw_info *hw);
 
 /** Indicate receipt of a raw packet from the network interface. Takes
-    ownership of the iob. */
-void net80211_rx(struct net80211_device *dev, struct io_buffer *iob);
+    ownership of the iob. `signal' is signal strength in device
+    units. */
+void net80211_rx(struct net80211_device *dev, struct io_buffer *iob,
+                int signal);
 
 /** Indicate an error in receiving a packet. Pass iob if you want it
     freed. */
index 4db5a91..af3b160 100644 (file)
 
 FILE_LICENCE ( GPL2_OR_LATER );
 
+#include <stdio.h>
 #include <string.h>
 #include <byteswap.h>
 #include <stdlib.h>
+#include <gpxe/settings.h>
 #include <gpxe/if_arp.h>
 #include <gpxe/ethernet.h>
 #include <gpxe/ieee80211.h>
@@ -34,21 +36,42 @@ FILE_LICENCE ( GPL2_OR_LATER );
 /** List of 802.11 devices */
 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
+ * greatest signal strength.
+ */
+struct setting net80211_ssid_setting __setting = {
+       .name = "ssid",
+       .description = "802.11 SSID (network name)",
+       .type = &setting_type_string,
+};
+
+/** Whether to use active scanning
+ *
+ * In order to associate with a hidden SSID, it's necessary to use an
+ * active scan (send probe packets). If this setting is nonzero, an
+ * active scan on the 2.4GHz band will be used to associate.
+ */
+struct setting net80211_hidden_setting __setting = {
+       .name = "hidden",
+       .description = "Associate with 802.11 hidden network",
+       .type = &setting_type_int8,
+};
+
 /** Set of device operations that does nothing */
 static struct net80211_device_operations net80211_null_ops;
 
-/* ---------- still needs implementing... ---------- */
-void net80211_handle_mgmt(struct net80211_device *dev, struct io_buffer *iob)
-{
-       (void)dev; (void)iob;
-       /* ... */
-}
+/** Single-step the association process. */
+void net80211_step_associate ( struct process *proc );
 
-void net80211_autoassociate(struct net80211_device *dev) 
+/** Structure for keeping a queue of saved management packet signal
+    strengths in parallel to the saved iobs. */
+struct net80211_rx_info
 {
-       (void)dev;
-       /* ... */
-}
+       int signal;
+       struct list_head list;
+};
 
 /* ---------- net_device wrapper ---------- */
 
@@ -247,7 +270,7 @@ static struct ll_protocol net80211_ll_protocol __ll_protocol = {
        .ll_broadcast   = net80211_ll_broadcast,
 };
 
-/* ---------- rest-of-gPXE 802.11 API ---------- */
+/* ---------- 802.11 network management API ---------- */
 
 struct net80211_device *net80211_get(struct net_device *netdev) 
 {
@@ -268,10 +291,18 @@ int net80211_keep_mgmt(struct net80211_device *dev, int enable)
        return oldenab;
 }
 
-struct io_buffer *net80211_mgmt_dequeue(struct net80211_device *dev) 
+struct io_buffer *net80211_mgmt_dequeue(struct net80211_device *dev,
+                                       int *signal)
 {
        struct io_buffer *iobuf;
+       struct net80211_rx_info *rxi;
 
+       list_for_each_entry(rxi, &dev->mgmt_info_queue, list) {
+               list_del(&rxi->list);
+               if (signal)
+                       *signal = rxi->signal;
+               break;
+       }
        list_for_each_entry(iobuf, &dev->mgmt_queue, list) {
                list_del(&iobuf->list);
                return iobuf;
@@ -279,6 +310,34 @@ struct io_buffer *net80211_mgmt_dequeue(struct net80211_device *dev)
        return NULL;
 }
 
+int net80211_tx_mgmt(struct net80211_device *dev, u16 fc, u8 dest[6],
+                    struct io_buffer *iob) 
+{
+       struct ieee80211_frame *hdr = iob_push(iob,
+                                              IEEE80211_TYP_FRAME_HEADER_LEN);
+       
+       hdr->fc = IEEE80211_THIS_VERSION | IEEE80211_TYPE_MGMT |
+                 (fc & ~IEEE80211_FC_PROTECTED);
+       hdr->duration = net80211_duration(dev, 10);
+       hdr->seq = ++dev->last_tx_seqnr & 0x0FFF;
+
+       memcpy(hdr->addr1, dest, ETH_ALEN); /* DA = RA */
+       memcpy(hdr->addr2, dev->netdev->ll_addr, ETH_ALEN); /* SA = TA */
+       memcpy(hdr->addr3, dest, ETH_ALEN); /* BSSID */
+
+       if (fc & IEEE80211_FC_PROTECTED) {
+               if (!dev->crypto)
+                       return -EINVAL;
+               
+               struct io_buffer *eiob = dev->crypto->encrypt(dev, iob);
+               free_iob(iob);
+               iob = eiob;
+       }
+
+       return netdev_tx(dev->netdev, iob);
+}
+
+
 /* ---------- driver API ---------- */
 
 struct net80211_device *net80211_alloc(size_t priv_size) 
@@ -299,6 +358,7 @@ struct net80211_device *net80211_alloc(size_t priv_size)
        dev->op = &net80211_null_ops;
        dev->last_rx_seq = -1;
 
+       dev->proc_assoc.step = net80211_step_associate;
        INIT_LIST_HEAD(&dev->mgmt_queue);
 
        return dev;
@@ -334,7 +394,626 @@ void net80211_free(struct net80211_device *dev)
        netdev_put(dev->netdev);
 }
 
-/* ---------- packet handling ---------- */
+/* ---------- 802.11 network management workhorse code ---------- */
+
+/* Clear the flags in `clear', set those in `set', and set status to
+   `status'. Clears are done before sets. The LINK_UP bit is cleared
+   when association is cleared, but not set when association is set -
+   that's left to the association task to do after it checks
+   everything's OK. */
+static inline void net80211_set_state(struct net80211_device *dev,
+                                     short clear, short set, u16 status) 
+{
+       /* The conditions in this function are deliberately formulated
+          to be decidable at compile-time. */
+
+       if (clear & NET80211_AUTHENTICATED)
+               clear |= NET80211_ASSOCIATED;
+       if (clear & NET80211_ASSOCIATED)
+               clear |= NET80211_CRYPTO_SYNCED;
+
+       dev->state = (dev->state & ~clear) | set;
+       dev->state = (dev->state & ~NET80211_STATUS_MASK) |
+               (status & NET80211_STATUS_MASK);
+
+       if (clear & NET80211_ASSOCIATED)
+               dev->netdev->state &= ~NETDEV_LINK_UP;
+
+       if ((clear | set) & NET80211_ASSOCIATED)
+               dev->op->config(dev, NET80211_CFG_ASSOC);
+}
+
+static void net80211_add_channels(struct net80211_device *dev, int start,
+                                 int len, int txpower) 
+{
+       int i, chan = start;
+
+       for (i = dev->nr_channels; len-- && i < NET80211_MAX_CHANNELS; i++) {
+               dev->channels[i].channel_nr = chan;
+               dev->channels[i].maxpower = txpower;
+
+               if (chan >= 1 && chan <= 14) {
+                       dev->channels[i].band = NET80211_BAND_2GHZ;
+                       if (chan == 14)
+                               dev->channels[i].center_freq = 2484;
+                       else
+                               dev->channels[i].center_freq = 2407 + 5*chan;
+                       chan++;
+               } else {
+                       dev->channels[i].band = NET80211_BAND_5GHZ;
+                       dev->channels[i].center_freq = 5000 + 5*chan;
+                       chan += 4;
+               }
+       }
+
+       dev->nr_channels = i;
+}
+
+static int net80211_process_capab(struct net80211_device *dev, u16 capab)
+{
+       u16 old_erp = dev->erp_flags;
+
+       if ((capab & (IEEE80211_CAPAB_MANAGED | IEEE80211_CAPAB_ADHOC)) !=
+           IEEE80211_CAPAB_MANAGED) {
+               DBG("802.11 cannot handle IBSS network\n");
+               return -ENOSYS;
+       }
+
+       if (capab & IEEE80211_CAPAB_SPECTRUM_MGMT) {
+               DBG("802.11 cannot handle spectrum managed network\n");
+               return -ENOSYS;
+       }
+
+       dev->erp_flags &= ~(NET80211_ERP_USE_SHORT_PREAMBLE |
+                           NET80211_ERP_USE_SHORT_SLOT);
+
+       if (capab & IEEE80211_CAPAB_SHORT_PMBL)
+               dev->erp_flags |= NET80211_ERP_USE_SHORT_PREAMBLE;
+
+       if (capab & IEEE80211_CAPAB_SHORT_SLOT)
+               dev->erp_flags |= NET80211_ERP_USE_SHORT_SLOT;
+
+       if (old_erp != dev->erp_flags)
+               dev->op->config(dev, NET80211_CFG_ERP_PARAMS);
+
+       return 0;
+}
+
+static int net80211_process_ie(struct net80211_device *dev,
+                              struct ieee80211_ie *ie, int len)
+{
+       void *ie_byte = (u8 *)ie;
+       void *ie_byte_end = ie_byte + len;
+       u16 old_rate = NET80211_RATE_VALUE(dev->rates[dev->rate]);
+       u16 old_erp = dev->erp_flags;
+       int have_rates = 0, i;
+       int ds_channel = 0;
+       int changed = 0;
+
+       for (ie = ie_byte; ie_byte < ie_byte_end;
+            ie_byte += ie->len + 2, ie = ie_byte) {
+               switch (ie->id) {
+               case IEEE80211_IE_SSID:
+                       if (ie->len <= 32) {
+                               memcpy(dev->essid, ie->ssid, ie->len);
+                               dev->essid[ie->len] = 0;
+                       }
+                       break;
+
+               case IEEE80211_IE_RATES:
+               case IEEE80211_IE_EXT_RATES:
+                       if (!have_rates) {
+                               dev->nr_rates = 0;
+                               dev->basic_rates = 0;
+                               have_rates = 1;
+                       }
+                       for (i = 0; i < ie->len &&
+                                    dev->nr_rates < NET80211_MAX_RATES; i++) {
+                               u8 rid = ie->rates[i];
+                               u16 rate = (rid & 0x7f) * 5;
+                               if (rid & 0x80)
+                                       dev->basic_rates |= (1 << dev->nr_rates);
+                               if (rate != 10 && rate != 20 && rate != 55 &&
+                                   rate != 110) /* 802.11b rates */
+                                       rate |= NET80211_RATE_ERP;
+                               dev->rates[dev->nr_rates++] = rate;
+                       }
+                       
+                       break;
+
+               case IEEE80211_IE_DS_PARAM:
+                       if (dev->channel < dev->nr_channels &&
+                           dev->channels[dev->channel].channel_nr == ds_channel)
+                               break;
+                       ds_channel = ie->ds_param.current_channel;
+                       changed |= NET80211_CFG_CHANNEL;
+                       break;
+
+               case IEEE80211_IE_COUNTRY:
+                       dev->nr_channels = 0;
+
+                       DBG("802.11 setting country regulations for %c%c\n",
+                           ie->country.name[0], ie->country.name[1]);
+                       for (i = 0; i < (ie->len - 3) / 3; i++) {
+                               if (ie->country.triplet[i].ext.reg_ext_id > 200) {
+                                       DBG("802.11 don't know how to parse "
+                                           "regulatory extension information\n");
+                               } else {
+                                       net80211_add_channels(dev,
+                                         ie->country.triplet[i].band.first_channel,
+                                         ie->country.triplet[i].band.nr_channels,
+                                         ie->country.triplet[i].band.max_txpower);
+                               }
+                       }
+                       break;
+                       
+               case IEEE80211_IE_ERP_INFO:
+                       dev->erp_flags &= ~(NET80211_ERP_USE_PROTECTION |
+                                           NET80211_ERP_USE_SHORT_PREAMBLE);
+                       if (ie->erp_info & IEEE80211_ERP_USE_PROTECTION)
+                               dev->erp_flags |= NET80211_ERP_USE_PROTECTION;
+                       if (!(ie->erp_info & IEEE80211_ERP_BARKER_LONG))
+                               dev->erp_flags |= NET80211_ERP_USE_SHORT_PREAMBLE;
+                       break;
+                       
+               case IEEE80211_IE_RSN:
+                       /* XXX need to implement WPA stuff */
+                       break;
+               }
+       }
+
+       if (have_rates) {
+               /* Allow only those rates that are also supported by
+                  the hardware. */
+               int delta = 0, j;
+
+               dev->rate = 0;
+               for (i = 0; i < dev->nr_rates; i++) {
+                       int ok = 0;
+                       for (j = 0; j < dev->hw->nr_supported_rates; j++) {
+                               if (dev->hw->supported_rates[i] ==
+                                   dev->rates[i]) {                                    
+                                       ok = 1;
+                                       break;
+                               }
+                       }
+
+                       if (!ok)
+                               delta++;
+                       else {
+                               dev->rates[i - delta] = dev->rates[i];
+                               if (old_rate == dev->rates[i])
+                                       dev->rate = i - delta;
+                       }
+               }
+
+               dev->nr_rates -= delta;
+
+               if (dev->rates[dev->rate] != old_rate)
+                       changed |= NET80211_CFG_RATE;
+       }
+
+       if (ds_channel) {
+               for (i = 0; i < dev->nr_channels; i++) {
+                       if (dev->channels[i].channel_nr == ds_channel) {
+                               dev->channel = i;
+                               break;
+                       }
+               }
+       }
+
+       if (dev->hw->flags & NET80211_HW_NO_SHORT_PREAMBLE)
+               dev->erp_flags &= ~NET80211_ERP_USE_SHORT_PREAMBLE;
+       if (dev->hw->flags & NET80211_HW_NO_SHORT_SLOT)
+               dev->erp_flags &= ~NET80211_ERP_USE_SHORT_SLOT;
+
+       if (old_erp != dev->erp_flags)
+               changed |= NET80211_CFG_ERP_PARAMS;
+
+       if (changed)
+               dev->op->config(dev, changed);
+
+       return 0;
+}
+
+struct net80211_wlan *net80211_probe(struct net80211_device *dev,
+                                    const char *essid, int active) 
+{
+       (void)dev; (void)essid; (void)active;
+       return NULL;
+}
+
+void net80211_free_wlan ( struct net80211_wlan *wlan )
+{
+       free_iob ( wlan->beacon );
+       free ( wlan );
+}
+
+void net80211_step_associate ( struct process *proc )
+{
+       struct net80211_device *dev =
+               container_of ( proc, struct net80211_device, proc_assoc );
+       int rc = 0;
+       int status = dev->state & NET80211_STATUS_MASK;
+
+       if ( dev->state & NET80211_WAITING )
+               return;
+
+       if ( ! dev->associating ) {
+               /* state: scan */
+               int hidden = fetch_intz_setting ( NULL,
+                                                 &net80211_hidden_setting );
+               int band = dev->hw->bands;
+
+               if ( hidden )
+                       band &= ~NET80211_BAND_5GHZ;
+
+               rc = net80211_prepare_default ( dev, band, hidden );
+               if ( rc )
+                       goto fail;
+
+               dev->associating = net80211_probe ( dev, dev->essid, hidden );
+               if ( ! dev->associating )
+                       goto fail;
+
+               DBG ( "802.11 found network %s (%s)\n", dev->associating->essid,
+                     eth_ntoa ( dev->associating->bssid ) );
+
+               dev->associating->method = IEEE80211_AUTH_OPEN_SYSTEM;
+               return;
+       }
+
+       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 */
+
+                       if ( method == IEEE80211_AUTH_OPEN_SYSTEM &&
+                            ( status == IEEE80211_STATUS_AUTH_ALGO_UNSUPP ||
+                              status == IEEE80211_STATUS_AUTH_CHALL_INVALID ) ) {
+                               /* Maybe this network uses Shared Key? */
+                               method = dev->associating->method =
+                                       IEEE80211_AUTH_SHARED_KEY;
+                       } else {
+                               goto fail;
+                       }
+               }
+
+               DBG ( "802.11 authenticating with method %d\n", method );
+
+               rc = net80211_prepare ( dev, dev->associating );
+               if ( rc )
+                       goto fail;
+
+               rc = net80211_send_auth ( dev, dev->associating, method );
+               if ( rc )
+                       goto fail;
+
+               return;
+       }
+
+       if ( ! ( dev->state & NET80211_ASSOCIATED ) ) {
+               /* state: associate */
+               DBG ( "802.11 associating\n");
+
+               if ( status != IEEE80211_STATUS_SUCCESS )
+                       goto fail;
+
+               rc = net80211_send_assoc ( dev, dev->associating );
+               if ( rc )
+                       goto fail;
+
+               return;
+       }
+
+       if ( ! ( dev->state & NET80211_CRYPTO_SYNCED ) ) {
+               /* state: crypto sync */
+               dev->state |= NET80211_CRYPTO_SYNCED;
+               /* XXX need to actually do something here once we
+                  support WPA */
+               return;
+       }
+
+       /* state: done! */
+       dev->netdev->state |= NETDEV_LINK_UP;
+       dev->state &= ~NET80211_WORKING;
+       printf ( "[associated to %s] ", dev->essid );
+       process_del ( proc );
+
+ fail:
+       DBG ( "802.11 association process failed with state=%04x "
+             "rc=%08x\n", dev->state, rc );
+       printf ( "association error: %s\n", strerror ( rc ) );
+       dev->state &= ~NET80211_WORKING;
+       process_del ( proc );
+}
+
+void net80211_autoassociate ( struct net80211_device *dev )
+{
+       int len;
+
+       if ( ! ( dev->state & NET80211_WORKING ) )
+               process_add ( &dev->proc_assoc );
+       if ( dev->associating )
+               net80211_free_wlan ( dev->associating );
+
+       len = fetch_setting ( netdev_settings ( dev->netdev ),
+                             &net80211_ssid_setting, dev->essid,
+                             IEEE80211_MAX_SSID_LEN );
+       dev->essid[len] = 0;
+       dev->state |= NET80211_WORKING;
+       dev->associating = NULL;
+}
+
+int net80211_prepare_default(struct net80211_device *dev, int band, int active)
+{
+       if (active && band != NET80211_BAND_2GHZ) {
+               DBG("802.11 cannot perform active scanning on 5GHz band\n");
+               return -EINVAL;
+       }
+
+       dev->nr_channels = 0;
+
+       if (active)
+               net80211_add_channels(dev, 1, 11, NET80211_REG_TXPOWER);
+       else {
+               if (band & NET80211_BAND_2GHZ)
+                       net80211_add_channels(dev, 1, 14, NET80211_REG_TXPOWER);
+               if (band & NET80211_BAND_5GHZ)
+                       net80211_add_channels(dev, 36, 8, NET80211_REG_TXPOWER);
+       }
+
+       dev->channel = 0;
+       dev->op->config(dev, NET80211_CFG_CHANNEL);
+       return 0;
+}
+
+int net80211_prepare(struct net80211_device *dev, struct net80211_wlan *wlan)
+{
+       struct ieee80211_frame *hdr = wlan->beacon->data;
+       struct ieee80211_beacon *beacon = (struct ieee80211_beacon *)hdr->data;
+       int rc;
+
+       net80211_set_state(dev, NET80211_ASSOCIATED, 0, 0);
+       memcpy(dev->bssid, wlan->bssid, ETH_ALEN);
+       strcpy(dev->essid, wlan->essid);
+
+       /* do crypto setup here */
+       
+       rc = net80211_process_capab(dev, beacon->capability);
+       if (rc)
+               return rc;
+
+       rc = net80211_process_ie(dev, beacon->info_element,
+                                iob_len(wlan->beacon) - sizeof(*hdr));
+       if (rc)
+               return rc;
+
+       return 0;
+}
+
+int net80211_send_auth(struct net80211_device *dev, struct net80211_wlan *wlan,
+                      int method) 
+{
+       struct io_buffer *iob = alloc_iob(64);
+       struct ieee80211_auth *auth;
+
+       iob_reserve(iob, IEEE80211_TYP_FRAME_HEADER_LEN);
+       auth = iob->data;
+       auth->algorithm = method;
+       auth->tx_seq = 1;
+       auth->status = 0;
+
+       return net80211_tx_mgmt(dev, IEEE80211_STYPE_AUTH, wlan->bssid, iob);
+}
+
+static void net80211_handle_auth(struct net80211_device *dev,
+                                struct io_buffer *iob) 
+{
+       struct ieee80211_frame *hdr = iob->data;
+       struct ieee80211_auth *auth = (struct ieee80211_auth *)hdr->data;
+
+       if (auth->tx_seq & 1) {
+               DBG("%s: 802.11 authentication received improperly directed "
+                   "frame (sequence %d)\n", dev->netdev->name, auth->tx_seq);
+               net80211_set_state(dev, NET80211_WAITING, 0,
+                                  IEEE80211_STATUS_FAILURE);
+               return;
+       }
+
+       if (auth->status != IEEE80211_STATUS_SUCCESS) {
+               DBG("%s: 802.11 authentication failed: status %d\n",
+                   dev->netdev->name, auth->status);
+               net80211_set_state(dev, NET80211_WAITING, 0, auth->status);
+               return;
+       }
+
+       if (auth->algorithm == IEEE80211_AUTH_SHARED_KEY && !dev->crypto) {
+               DBG("%s: 802.11 can't perform shared-key authentication without "
+                   "a cryptosystem\n", dev->netdev->name);
+               net80211_set_state(dev, NET80211_WAITING, 0,
+                                  IEEE80211_STATUS_FAILURE);
+               return;
+       }
+
+       if (auth->algorithm == IEEE80211_AUTH_SHARED_KEY && auth->tx_seq == 2) {
+               /* Since the iob we got is going to be freed as soon
+                  as we return, we can do some in-place
+                  modification. */
+               auth->tx_seq = 3;
+               auth->status = 0;
+
+               memcpy(hdr->addr2, hdr->addr1, ETH_ALEN);
+               memcpy(hdr->addr1, hdr->addr3, ETH_ALEN);
+
+               netdev_tx(dev->netdev, dev->crypto->encrypt(dev, iob));
+               return;
+       }
+
+       net80211_set_state(dev, NET80211_WAITING, NET80211_AUTHENTICATED,
+                          IEEE80211_STATUS_SUCCESS);
+
+       return;
+}
+
+int net80211_send_assoc(struct net80211_device *dev, struct net80211_wlan *wlan)
+{
+       struct io_buffer *iob = alloc_iob(128);
+       struct ieee80211_assoc_req *assoc;
+       struct ieee80211_ie *ie;
+       void *ie_byte;
+       int i;
+
+       iob_reserve(iob, IEEE80211_TYP_FRAME_HEADER_LEN);
+       assoc = iob->data;
+
+       assoc->capability = IEEE80211_CAPAB_MANAGED;
+       if (!(dev->hw->flags & NET80211_HW_NO_SHORT_PREAMBLE))
+               assoc->capability |= IEEE80211_CAPAB_SHORT_PMBL;
+       if (!(dev->hw->flags & NET80211_HW_NO_SHORT_SLOT))
+               assoc->capability |= IEEE80211_CAPAB_SHORT_SLOT;
+       if (wlan->security)
+               assoc->capability |= IEEE80211_CAPAB_PRIVACY;
+
+       assoc->listen_interval = 1;
+
+       ie = assoc->info_element;
+       ie_byte = ie;
+
+       ie->id = IEEE80211_IE_SSID;
+       ie->len = strlen(wlan->essid);
+       strcpy(ie->ssid, wlan->essid);
+
+       ie_byte += ie->len;
+       ie = ie_byte;
+
+       ie->id = IEEE80211_IE_RATES;
+       ie->len = dev->hw->nr_supported_rates;
+       if (ie->len > 8)
+               ie->len = 8;
+       for (i = 0; i < ie->len; i++) {
+               ie->rates[i] = dev->hw->supported_rates[i] / 5;
+       }
+
+       ie_byte += ie->len;
+       ie = ie_byte;
+
+       if (i < dev->hw->nr_supported_rates) {
+               ie->id = IEEE80211_IE_EXT_RATES;
+               ie->len = dev->hw->nr_supported_rates - i;
+               for (; i < ie->len; i++) {
+                       ie->rates[i - 8] = dev->hw->supported_rates[i] / 5;
+               }
+
+               ie_byte += ie->len;
+               ie = ie_byte;
+       }
+
+       /* XXX add RSN ie for WPA support */
+
+       return net80211_tx_mgmt(dev, IEEE80211_STYPE_ASSOC_REQ, wlan->bssid,
+                               iob);
+}
+
+static void net80211_handle_assoc_reply(struct net80211_device *dev,
+                                       struct io_buffer *iob) 
+{
+       struct ieee80211_frame *hdr = iob->data;
+       struct ieee80211_assoc_resp *assoc =
+               (struct ieee80211_assoc_resp *)hdr->data;
+
+       net80211_process_capab(dev, assoc->capability);
+       net80211_process_ie(dev, assoc->info_element,
+                           iob_len(iob) - sizeof(*hdr));
+
+       if (assoc->status != IEEE80211_STATUS_SUCCESS) {
+               DBG("%s: 802.11 association failed: status %d\n",
+                   dev->netdev->name, assoc->status);
+               net80211_set_state(dev, NET80211_WAITING, 0, assoc->status);
+               return;
+       }
+
+       /* ESSID was filled before the association request was sent */
+       memcpy(dev->bssid, hdr->addr3, ETH_ALEN);
+       dev->aid = assoc->aid;
+
+       net80211_set_state(dev, NET80211_WAITING, NET80211_ASSOCIATED,
+                          IEEE80211_STATUS_SUCCESS);
+}
+
+static void net80211_handle_mgmt(struct net80211_device *dev,
+                                struct io_buffer *iob, int signal) 
+{
+       struct ieee80211_frame *hdr = iob->data;
+       u16 stype = hdr->fc & IEEE80211_FC_SUBTYPE;
+       int keep = 0;
+
+       if ((hdr->fc & IEEE80211_FC_TYPE) != IEEE80211_TYPE_MGMT) {
+               free_iob(iob);
+               return;         /* only handle management frames */
+       }
+
+       switch (stype) {
+       case IEEE80211_STYPE_DEAUTH:
+               net80211_set_state(dev, NET80211_AUTHENTICATED, 0, 0);
+               DBG("%s: 802.11 deauthenticated\n", dev->netdev->name);
+               net80211_autoassociate(dev);
+               break;
+       case IEEE80211_STYPE_DISASSOC:
+               net80211_set_state(dev, NET80211_ASSOCIATED, 0, 0);
+               DBG("%s: 802.11 disassociated\n", dev->netdev->name);
+               net80211_autoassociate(dev);
+               break;
+
+               /* We handle authentication and association. */
+       case IEEE80211_STYPE_AUTH:
+               if (!(dev->state & NET80211_AUTHENTICATED))
+                       net80211_handle_auth(dev, iob);
+               break;
+
+       case IEEE80211_STYPE_ASSOC_RESP:
+       case IEEE80211_STYPE_REASSOC_RESP:
+               if (!(dev->state & NET80211_ASSOCIATED))
+                       net80211_handle_assoc_reply(dev, iob);
+               break;
+
+               /* We pass probes and beacons onto network scanning
+                  code. Pass actions for future extensibility. */
+       case IEEE80211_STYPE_PROBE_RESP:
+       case IEEE80211_STYPE_BEACON:
+       case IEEE80211_STYPE_ACTION:
+               if (dev->keep_mgmt) {
+                       struct net80211_rx_info *rxinf;
+                       rxinf = zalloc(sizeof(*rxinf));
+                       if (!rxinf) {
+                               DBG("No memory for rxinf structure\n");
+                               break;
+                       }
+                       rxinf->signal = signal;
+                       list_add_tail(&iob->list, &dev->mgmt_queue);
+                       list_add_tail(&rxinf->list, &dev->mgmt_info_queue);
+                       keep = 1;
+               }
+               break;
+
+       case IEEE80211_STYPE_ASSOC_REQ:
+       case IEEE80211_STYPE_REASSOC_REQ:
+       case IEEE80211_STYPE_PROBE_REQ:
+               /* We should never receive these, only send them. */
+               DBG("Received strange management request (%04x)\n", stype);
+               break;
+       default:
+               DBG("Received unimplemented management packet (%04x)\n", stype);
+               break;
+       }
+
+       if (!keep)
+               free_iob(iob);
+}
+
+/* ---------- Packet handling functions ---------- */
 
 /* Free the iob's for frag cache entry fcid and mark it unused. */
 static void net80211_free_frags(struct net80211_device *dev, int fcid) 
@@ -385,7 +1064,8 @@ static struct io_buffer *net80211_accum_frags(struct net80211_device *dev,
        return niob;
 }
 
-static void net80211_rx_frag(struct net80211_device *dev, struct io_buffer *iob)
+static void net80211_rx_frag(struct net80211_device *dev, struct io_buffer *iob,
+                            int signal)
 {
        struct ieee80211_frame *hdr = iob->data;
 
@@ -430,8 +1110,13 @@ static void net80211_rx_frag(struct net80211_device *dev, struct io_buffer *iob)
                            dev->frags[i].seqnr == IEEE80211_SEQNR(hdr->seq))
                                break;
                }
-               if (i == NET80211_NR_CONCURRENT_FRAGS)
-                       return; /* drop non-first not-in-cache fragments */
+               if (i == NET80211_NR_CONCURRENT_FRAGS) {
+                       /* drop non-first not-in-cache fragments */
+                       DBG2("802.11 dropped fragment fc=%04x seq=%04x\n",
+                            hdr->fc, hdr->seq);
+                       free_iob(iob);
+                       return;
+               }
 
                dev->frags[i].iob[IEEE80211_FRAG(hdr->seq)] = iob;
 
@@ -453,25 +1138,25 @@ static void net80211_rx_frag(struct net80211_device *dev, struct io_buffer *iob)
                                struct io_buffer *niob =
                                        net80211_accum_frags(dev, i, size);
                                net80211_free_frags(dev, i);
-                               net80211_rx(dev, niob);
+                               net80211_rx(dev, niob, signal);
                        }
                }
        }
 }
 
-void net80211_rx(struct net80211_device *dev, struct io_buffer *iob
+void net80211_rx(struct net80211_device *dev, struct io_buffer *iob, int signal)
 {
        struct ieee80211_frame *hdr = iob->data;
        u16 type = hdr->fc & IEEE80211_FC_TYPE;
        if ((hdr->fc & IEEE80211_FC_VERSION) != IEEE80211_THIS_VERSION)
-               return;         /* drop invalid-version packets */
+               goto drop;      /* drop invalid-version packets */
 
        if (type == IEEE80211_TYPE_CTRL)
-               return;         /* we don't handle control packets,
+               goto drop;      /* we don't handle control packets,
                                   the hardware does */
 
        if (dev->last_rx_seq == hdr->seq)
-               return;         /* avoid duplicate packet */
+               goto drop;      /* avoid duplicate packet */
        dev->last_rx_seq = hdr->seq;
 
        if (dev->hw->flags & NET80211_HW_RX_HAS_FCS) {
@@ -482,29 +1167,38 @@ void net80211_rx(struct net80211_device *dev, struct io_buffer *iob)
        if (hdr->fc & IEEE80211_FC_PROTECTED) {
                struct io_buffer *niob;
                if (!dev->crypto)
-                       return; /* can't decrypt packets on an open network */
+                       goto drop;      /* can't decrypt packets on an open network */
 
                niob = dev->crypto->decrypt(dev, iob);
                if (!niob)
-                       return; /* drop failed decryption */
+                       goto drop;      /* drop failed decryption */
                free_iob(iob);
                iob = niob;
        }
 
+       /* Fragments go into the frag cache or get dropped. */
        if (IEEE80211_FRAG(hdr->seq) != 0 || (hdr->fc & IEEE80211_FC_MORE_FRAG)) {
-               net80211_rx_frag(dev, iob);
+               net80211_rx_frag(dev, iob, signal);
                return;
        }
 
+       /* Management frames get handled, enqueued, or dropped. */
        if (type == IEEE80211_TYPE_MGMT) {
-               net80211_handle_mgmt(dev, iob);
+               net80211_handle_mgmt(dev, iob, signal);
                return;
-       } else {
-               if ((hdr->fc & IEEE80211_FC_SUBTYPE) != IEEE80211_STYPE_DATA)
-                       return; /* drop QoS, CFP, or null data packets */
+       }
+
+       /* Data frames get dropped or sent to the net_device. */
+       if ((hdr->fc & IEEE80211_FC_SUBTYPE) != IEEE80211_STYPE_DATA)
+               goto drop;      /* drop QoS, CFP, or null data packets */
 
-               if (dev->netdev->state & NETDEV_LINK_UP)
-                       netdev_rx(dev->netdev, iob);
+       if (dev->netdev->state & NETDEV_LINK_UP) {
+               netdev_rx(dev->netdev, iob);
                return;
        }
+
+ drop:
+       DBG2("802.11 dropped packet fc=%04x seq=%04x\n", hdr->fc, hdr->seq);
+       free_iob(iob);
+       return;
 }