[802.11] MAC layer: Add probe functionality, other small improvements
authorJoshua Oreman <oremanj@xenon.get-linux.org>
Mon, 8 Jun 2009 06:37:33 +0000 (23:37 -0700)
committerJoshua Oreman <oremanj@xenon.get-linux.org>
Mon, 8 Jun 2009 06:37:33 +0000 (23:37 -0700)
The MAC layer is now theoretically complete enough to use for booting.
We'll see if it works.

src/net/net80211.c

index 21fb1f6..fe8c72d 100644 (file)
@@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <gpxe/netdevice.h>
 #include <gpxe/net80211.h>
 #include <gpxe/timer.h>
+#include <gpxe/nap.h>
 #include <errno.h>
 
 /** List of 802.11 devices */
@@ -189,7 +190,7 @@ static int net80211_ll_push ( struct net_device *netdev,
        memcpy ( hdr->addr2, ll_source, ETH_ALEN );
        memcpy ( hdr->addr3, ll_dest, ETH_ALEN );
 
-       hdr->seq = ++dev->last_tx_seqnr & 0x0FFF;       /* fragment 0 */
+       hdr->seq = IEEE80211_MAKESEQ ( ++dev->last_tx_seqnr, 0 );
 
        lhdr->dsap = IEEE80211_LLC_DSAP;
        lhdr->ssap = IEEE80211_LLC_SSAP;
@@ -324,7 +325,7 @@ int net80211_tx_mgmt ( struct net80211_device *dev, u16 fc, u8 dest[6],
        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;
+       hdr->seq = IEEE80211_MAKESEQ ( ++dev->last_tx_seqnr, 0 );
 
        memcpy ( hdr->addr1, dest, ETH_ALEN );  /* DA = RA */
        memcpy ( hdr->addr2, dev->netdev->ll_addr, ETH_ALEN );  /* SA = TA */
@@ -630,19 +631,216 @@ static int net80211_process_ie ( struct net80211_device *dev,
        return 0;
 }
 
-struct net80211_wlan *net80211_probe ( struct net80211_device *dev,
-                                      const char *essid, int active )
+struct ieee80211_ie *
+net80211_marshal_request_info ( struct net80211_device *dev,
+                               struct ieee80211_ie *ie ) 
 {
-       ( void ) dev;
-       ( void ) essid;
-       ( void ) active;
+       void *ie_byte = ie;
+       int i;
+
+       ie->id = IEEE80211_IE_SSID;
+       ie->len = strlen ( dev->essid );
+       memcpy ( ie->ssid, dev->essid, ie->len );
+
+       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;
+       }
+
+       return ie;
+}
+
+#define NET80211_PROBE_GATHER    2 /* seconds to always take */
+#define NET80211_PROBE_TIMEOUT   6 /* seconds to take at maximum */
+
+struct net80211_wlan * net80211_probe ( 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;
+       struct ieee80211_ie *ie;
+       void *ie_byte;
+       int rc;
+
+       /* Channels on the 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--;  
+
+       dev->channel = 0;
+
+       /* If we're scanning actively, make a probe packet. */
+       if ( active ) {
+               struct ieee80211_probe_req *probe_req;
+
+               probe = malloc ( 128 );
+               probe_req = probe;
+
+               /* 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;
+               ie->len = 3;
+               ie->request[0] = IEEE80211_IE_COUNTRY;
+               ie->request[1] = IEEE80211_IE_ERP_INFO;
+               ie->request[2] = IEEE80211_IE_RSN;
+
+               probe_len = ( void * ) ie + ie->len + 2 - probe;
+       }
+
+       while ( currticks() < start_ticks + start_timeout ) {
+               struct ieee80211_frame *hdr;
+               struct ieee80211_beacon *beacon; /* == iee80211_probe_resp */
+               struct ieee80211_ie *ie;
+               int signal;
+               u16 type;
+
+               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 ) {
+                                       DBG ( "802.11 send probe failed: %s\n",
+                                             strerror ( rc ) );
+                               }
+                       }
+               }
+
+               dev->op->poll ( dev );
+               iob = net80211_mgmt_dequeue ( dev, &signal );
+               if ( ! iob ) {
+                       cpu_nap();
+                       continue;
+               }
+
+               hdr = iob->data;
+               type = hdr->fc & IEEE80211_FC_SUBTYPE;
+               beacon = (struct ieee80211_beacon *)hdr->data;
+
+               if ( type != IEEE80211_STYPE_BEACON &&
+                    type != IEEE80211_STYPE_PROBE_RESP ) {
+                       DBG2 ( "802.11 probe: non-beacon\n" );
+                       goto drop;
+               }
+
+               ie = beacon->info_element;
+               ie_byte = ie;
+               while ( ie_byte < iob->tail && ie->id != IEEE80211_IE_SSID ) {
+                       ie_byte += ie->len;
+                       ie = ie_byte;
+               }
+               if ( ie_byte >= iob->tail ) {
+                       /* didn't find an SSID */
+                       DBG2 ( "802.11 probe: beacon with no SSID\n" );
+                       goto drop;
+               }
+               if ( memcmp ( essid, ie->ssid, ie->len ) != 0 ) {
+                       ie->ssid[ie->len] = 0;
+                       DBG2 ( "802.11 probe: beacon with wrong SSID (%s)\n",
+                              ie->ssid );
+                       goto drop;
+               }
+
+               if ( ! wlan ) {
+                       wlan = malloc ( sizeof ( *wlan ) );
+                       if ( ! wlan ) {
+                               DBG ( "802.11 probe: out of memory\n" );
+                               goto fail;
+                       }
+               } else if ( signal < wlan->signal ) {
+                       DBG ( "802.11 probe: discarding signal %d < %d\n",
+                             signal, wlan->signal );
+                       goto drop;
+               } else {
+                       free_iob ( iob_disown ( wlan->beacon ) );
+               }
+
+               wlan->essid[ie->len] = 0;
+               memcpy ( wlan->essid, ie->ssid, ie->len );
+               memcpy ( wlan->bssid, hdr->addr3, ETH_ALEN);
+               wlan->signal = signal;
+               wlan->beacon = iob;
+               wlan->security = 0; /* XXX implement */
+               DBG ( "802.11 probe: new best signal %d for AP %s on '%s'\n",
+                     wlan->signal, eth_ntoa ( wlan->bssid ), wlan->essid );
+
+               if ( currticks() > start_ticks + gather_timeout )
+                       break;
+
+               continue;
+
+       drop:
+               free_iob ( iob );
+       }
+
+       if ( ! wlan )
+               DBG ( "802.11 probe: found no response for '%s'\n", essid );
+
+       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;
 }
 
 void net80211_free_wlan ( struct net80211_wlan *wlan )
 {
-       free_iob ( wlan->beacon );
-       free ( wlan );
+       if ( wlan ) {
+               free_iob ( wlan->beacon );
+               free ( wlan );
+       }
 }
 
 void net80211_step_associate ( struct process *proc )
@@ -764,6 +962,35 @@ void net80211_autoassociate ( struct net80211_device *dev )
        dev->associating = NULL;
 }
 
+void net80211_set_rate_intelligently ( struct net80211_device *dev ) 
+{
+       if ( dev->nr_rates == 0 ) {
+               int i;
+
+               for ( i = 0; i < dev->hw->nr_supported_rates; i++ ) {
+                       u16 rate = dev->hw->supported_rates[i];
+                       if ( rate != 10 && rate != 20 && rate != 55 &&
+                            rate != 110 )
+                               rate |= NET80211_RATE_ERP;
+                       dev->rates[dev->nr_rates++] = rate;
+               }
+
+               /* For initial probe, where we know nothing of the
+                  type of network we're on, use a safe default. */
+
+               dev->rate = dev->nr_rates;
+               for ( i = 0; i < dev->nr_rates; i++ ) {
+                       if ( dev->rates[i] & NET80211_RATE_ERP )
+                               continue;
+                       dev->rate = i;
+                       break;
+               }
+
+               if ( dev->rate == dev->nr_rates ) /* no non-ERP rates */
+                       dev->rate = 0;
+       }
+}
+
 int net80211_prepare_default ( struct net80211_device *dev, int band,
                               int active )
 {
@@ -772,6 +999,11 @@ int net80211_prepare_default ( struct net80211_device *dev, int band,
                return -EINVAL;
        }
 
+       if ( band == 0 ) {
+               DBG ( "802.11 asked to prepare for scanning nothing\n" );
+               return -EINVAL;
+       }
+
        dev->nr_channels = 0;
 
        if ( active )
@@ -786,7 +1018,11 @@ int net80211_prepare_default ( struct net80211_device *dev, int band,
        }
 
        dev->channel = 0;
-       dev->op->config ( dev, NET80211_CFG_CHANNEL );
+       dev->nr_rates = 0;
+
+       net80211_set_rate_intelligently ( dev );
+
+       dev->op->config ( dev, NET80211_CFG_CHANNEL | NET80211_CFG_RATE );
        return 0;
 }
 
@@ -814,6 +1050,8 @@ int net80211_prepare ( struct net80211_device *dev,
        if ( rc )
                return rc;
 
+       net80211_set_rate_intelligently ( dev );
+
        return 0;
 }
 
@@ -824,7 +1062,7 @@ int net80211_send_auth ( struct net80211_device *dev,
        struct ieee80211_auth *auth;
 
        iob_reserve ( iob, IEEE80211_TYP_FRAME_HEADER_LEN );
-       auth = iob->data;
+       auth = iob_put ( iob, sizeof ( *auth ) );
        auth->algorithm = method;
        auth->tx_seq = 1;
        auth->status = 0;
@@ -892,7 +1130,6 @@ int net80211_send_assoc ( struct net80211_device *dev,
        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;
@@ -907,40 +1144,13 @@ int net80211_send_assoc ( struct net80211_device *dev,
 
        assoc->listen_interval = 1;
 
-       ie = assoc->info_element;
+       ie = net80211_marshal_request_info ( dev, 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 */
 
+       iob_put ( iob, ie_byte - iob->data );
+
        return net80211_tx_mgmt ( dev, IEEE80211_STYPE_ASSOC_REQ,
                                  wlan->bssid, iob );
 }
@@ -1028,12 +1238,17 @@ static void net80211_handle_mgmt ( struct net80211_device *dev,
                }
                break;
 
+       case IEEE80211_STYPE_PROBE_REQ:
+               /* Some nodes send these broadcast. */
+               DBG2 ( "Received probe for %s\n", eth_ntoa ( hdr->addr1 ) );
+               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 );