[802.11] Add settings applicator and error-propagating code
authorJoshua Oreman <oremanj@xenon.get-linux.org>
Sat, 13 Jun 2009 00:18:45 +0000 (17:18 -0700)
committerJoshua Oreman <oremanj@xenon.get-linux.org>
Sat, 13 Jun 2009 00:18:45 +0000 (17:18 -0700)
802.11-specific error codes are encoded using the 32 possible per-file
errors and four POSIX error codes: ECONNREFUSED and EHOSTUNREACH for
rejected associations explained by a status code, and ECONNRESET and
ENETRESET for later disassociations explained by a reason code. In
each case the second POSIX error is used if the 802.11 code is greater
than 32, since each type of error has currently defined values up to
45 or 50. In this way, we keep all error information necessary without
going outside the established gPXE error-reporting mechanism.

Also added a settings applicator; changing net0/ssid while an 802.11
device is open should cause it to associate with the new network.

src/net/net80211.c

index 3f7bb6e..6f4d60e 100644 (file)
@@ -35,6 +35,41 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <unistd.h>
 #include <errno.h>
 
+/* Disambiguate the EINVAL's a bit */
+#define EINVAL_PKT_TOO_SHORT   ( EINVAL | EUNIQ_01 )
+#define EINVAL_PKT_VERSION     ( EINVAL | EUNIQ_02 )
+#define EINVAL_PKT_NOT_DATA    ( EINVAL | EUNIQ_03 )
+#define EINVAL_PKT_NOT_FROMDS  ( EINVAL | EUNIQ_04 )
+#define EINVAL_PKT_LLC_HEADER  ( EINVAL | EUNIQ_05 )
+#define EINVAL_CRYPTO_REQUEST  ( EINVAL | EUNIQ_06 )
+#define EINVAL_ACTIVE_SCAN     ( EINVAL | EUNIQ_07 )
+
+/*
+ * 802.11 error codes: The AP can give us a status code explaining why
+ * authentication failed, or a reason code explaining why we were
+ * deauthenticated/disassociated. These codes range from 0-63 (the
+ * field is 16 bits wide, but only up to 45 or so are defined yet; we
+ * allow up to 63 for extensibility). This is encoded into an error
+ * code as such:
+ *
+ *                                      status & 0x1f goes here --vv--
+ *   Status code 0-31:  ECONNREFUSED | EUNIQ_(status & 0x1f) (0e1a6038)
+ *   Status code 32-63: EHOSTUNREACH | EUNIQ_(status & 0x1f) (171a6011)
+ *   Reason code 0-31:  ECONNRESET | EUNIQ_(reason & 0x1f)   (0f1a6039)
+ *   Reason code 32-63: ENETRESET | EUNIQ_(reason & 0x1f)    (271a6001)
+ *
+ * The POSIX error codes more or less convey the appropriate message
+ * (status codes occur when we can't associate at all, reason codes
+ * when we lose association unexpectedly) and let us extract the
+ * complete 802.11 error code from the rc value.
+ */
+
+#define E80211_STATUS( stat )  ( ((stat & 0x20)? EHOSTUNREACH : ECONNREFUSED) \
+                                       | ((stat & 0x1f) << 8) )
+#define E80211_REASON( reas )  ( ((reas & 0x20)? ENETRESET : ECONNRESET) \
+                                       | ((reas & 0x1f) << 8) )
+
+
 /** List of 802.11 devices */
 static struct list_head net80211_devices = LIST_HEAD_INIT ( net80211_devices );
 
@@ -109,6 +144,17 @@ static struct io_buffer *net80211_accum_frags ( struct net80211_device *dev,
 static void net80211_rx_frag ( struct net80211_device *dev,
                               struct io_buffer *iob, int signal );
 
+static int net80211_check_ssid_update ( void );
+
+/** 802.11 settings applicator
+ *
+ * When the SSID is changed, this will cause any open devices to
+ * re-associate.
+ */
+struct settings_applicator net80211_ssid_applicator __settings_applicator = {
+       .apply = net80211_check_ssid_update,
+};
+
 /* ---------- net_device wrapper ---------- */
 
 /**
@@ -317,6 +363,10 @@ static u16 net80211_duration ( struct net80211_device *dev, int bytes )
  *
  * This adds both the 802.11 frame header and the 802.3 LLC/SNAP
  * header used on data packets.
+ *
+ * We also check here for state of the link that would make it invalid
+ * to send a data packet; every data packet must pass through here,
+ * and no non-data packet (e.g. management frame) should.
  */
 static int net80211_ll_push ( struct net_device *netdev,
                              struct io_buffer *iobuf, const void *ll_dest,
@@ -329,6 +379,16 @@ static int net80211_ll_push ( struct net_device *netdev,
        struct ieee80211_llc_snap_header *lhdr =
                ( 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 );
+               }
+               return -ENETUNREACH;
+       }
+
        hdr->fc = IEEE80211_THIS_VERSION | IEEE80211_TYPE_DATA |
            IEEE80211_STYPE_DATA | IEEE80211_FC_TODS;
 
@@ -378,25 +438,25 @@ static int net80211_ll_pull ( struct net_device *netdev __unused,
             IEEE80211_LLC_HEADER_LEN ) {
                DBG ( "802.11 packet too short (%zd bytes)\n",
                      iob_len ( iobuf ) );
-               return -EINVAL;
+               return -EINVAL_PKT_TOO_SHORT;
        }
 
        if ( ( hdr->fc & IEEE80211_FC_VERSION ) != IEEE80211_THIS_VERSION ) {
                DBG ( "802.11 packet invalid version %04x\n",
                      hdr->fc & IEEE80211_FC_VERSION );
-               return -EINVAL;
+               return -EINVAL_PKT_VERSION;
        }
 
        if ( ( hdr->fc & IEEE80211_FC_TYPE ) != IEEE80211_TYPE_DATA ||
             ( hdr->fc & IEEE80211_FC_SUBTYPE ) != IEEE80211_STYPE_DATA ) {
                DBG ( "802.11 packet not data/data (fc=%04x)\n", hdr->fc );
-               return -EINVAL;
+               return -EINVAL_PKT_NOT_DATA;
        }
 
        if ( ( hdr->fc & ( IEEE80211_FC_TODS | IEEE80211_FC_FROMDS ) ) !=
             IEEE80211_FC_FROMDS ) {
                DBG ( "802.11 packet not from DS (fc=%04x)\n", hdr->fc );
-               return -EINVAL;
+               return -EINVAL_PKT_NOT_FROMDS;
        }
 
        if ( lhdr->dsap != IEEE80211_LLC_DSAP || lhdr->ssap != IEEE80211_LLC_SSAP ||
@@ -406,7 +466,7 @@ static int net80211_ll_pull ( struct net_device *netdev __unused,
                      "%02x->%02x [%02x] %02x:%02x:%02x %04x\n",
                      lhdr->dsap, lhdr->ssap, lhdr->ctrl, lhdr->oui[0],
                      lhdr->oui[1], lhdr->oui[2], lhdr->ethertype );
-               return -EINVAL;
+               return -EINVAL_PKT_LLC_HEADER;
        }
 
        iob_pull ( iobuf, sizeof ( *hdr ) + sizeof ( *lhdr ) );
@@ -555,7 +615,7 @@ int net80211_tx_mgmt ( struct net80211_device *dev, u16 fc, u8 dest[6],
 
        if ( fc & IEEE80211_FC_PROTECTED ) {
                if ( ! dev->crypto )
-                       return -EINVAL;
+                       return -EINVAL_CRYPTO_REQUEST;
 
                struct io_buffer *eiob = dev->crypto->encrypt ( dev->crypto,
                                                                iob );
@@ -1226,6 +1286,7 @@ static void net80211_step_associate ( struct process *proc )
                                method = dev->associating->method =
                                        IEEE80211_AUTH_SHARED_KEY;
                        } else {
+                               rc = E80211_STATUS ( status );
                                goto fail;
                        }
                }
@@ -1247,8 +1308,10 @@ static void net80211_step_associate ( struct process *proc )
                /* state: associate */
                DBG ( "802.11 associating\n" );
 
-               if ( status != IEEE80211_STATUS_SUCCESS )
+               if ( status != IEEE80211_STATUS_SUCCESS ) {
+                       rc = E80211_STATUS ( status );
                        goto fail;
+               }
 
                rc = net80211_send_assoc ( dev, dev->associating );
                if ( rc )
@@ -1280,6 +1343,38 @@ static void net80211_step_associate ( struct process *proc )
        process_del ( proc );
 }
 
+/**
+ * Check for 802.11 SSID updates
+ *
+ * This acts as a settings applicator; if the user changes netX/ssid,
+ * and netX is currently open, the association task will be invoked
+ * again.
+ */
+int net80211_check_ssid_update ( void )
+{
+       struct net80211_device *dev;
+       char ssid[IEEE80211_MAX_SSID_LEN + 1];
+       int len;
+
+       list_for_each_entry ( dev, &net80211_devices, list ) {
+               if ( ! ( dev->netdev->state & NETDEV_OPEN ) )
+                       continue;
+
+               len = fetch_setting ( netdev_settings ( dev->netdev ),
+                                     &net80211_ssid_setting, ssid,
+                                     IEEE80211_MAX_SSID_LEN );
+               ssid[len] = 0;
+
+               if ( strcmp ( ssid, dev->essid ) != 0 ) {
+                       DBG ( "802.11 updating association of %s: %s -> %s\n",
+                             dev->netdev->name, dev->essid, ssid );
+                       net80211_autoassociate ( dev );
+               }
+       }
+
+       return 0;
+}
+
 /**
  * Start 802.11 association process
  *
@@ -1357,12 +1452,14 @@ int net80211_prepare_default ( struct net80211_device *dev, int band,
 {
        if ( active && band != NET80211_BAND_2GHZ ) {
                DBG ( "802.11 cannot perform active scanning on 5GHz band\n" );
-               return -EINVAL;
+               return -EINVAL_ACTIVE_SCAN;
        }
 
        if ( band == 0 ) {
+               /* This can happen for a 5GHz-only card with 5GHz
+                  scanning masked out by an active request. */
                DBG ( "802.11 asked to prepare for scanning nothing\n" );
-               return -EINVAL;
+               return -EINVAL_ACTIVE_SCAN;
        }
 
        dev->nr_channels = 0;
@@ -1619,14 +1716,16 @@ 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:
-               net80211_set_state ( dev, NET80211_AUTHENTICATED, 0, 0 );
                disassoc = ( struct ieee80211_disassoc * ) hdr->data;
+               net80211_set_state ( dev, NET80211_AUTHENTICATED, 0,
+                                    NET80211_IS_REASON | disassoc->reason );
                DBG ( "%s: 802.11 deauthenticated: reason %d\n",
                      dev->netdev->name, disassoc->reason );
                break;
        case IEEE80211_STYPE_DISASSOC:
-               net80211_set_state ( dev, NET80211_ASSOCIATED, 0, 0 );
                disassoc = ( struct ieee80211_disassoc * ) hdr->data;
+               net80211_set_state ( dev, NET80211_ASSOCIATED, 0,
+                                    NET80211_IS_REASON | disassoc->reason );
                DBG ( "%s: 802.11 disassociated: reason %d\n",
                      dev->netdev->name, disassoc->reason );
                break;