diff --git a/src/Makefile b/src/Makefile index b646d3bc..55d99ccf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ SRCDIRS := SRCDIRS += libgcc SRCDIRS += core SRCDIRS += proto -SRCDIRS += net net/tcp net/udp net/infiniband +SRCDIRS += net net/tcp net/udp net/infiniband net/80211 SRCDIRS += image SRCDIRS += drivers/bus SRCDIRS += drivers/net diff --git a/src/include/gpxe/errfile.h b/src/include/gpxe/errfile.h index d3155324..e777ecb1 100644 --- a/src/include/gpxe/errfile.h +++ b/src/include/gpxe/errfile.h @@ -148,6 +148,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_ib_pathrec ( ERRFILE_NET | 0x001c0000 ) #define ERRFILE_ib_mcast ( ERRFILE_NET | 0x001d0000 ) #define ERRFILE_ib_cm ( ERRFILE_NET | 0x001e0000 ) +#define ERRFILE_net80211 ( ERRFILE_NET | 0x001f0000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/gpxe/ieee80211.h b/src/include/gpxe/ieee80211.h new file mode 100644 index 00000000..e2d25191 --- /dev/null +++ b/src/include/gpxe/ieee80211.h @@ -0,0 +1,1174 @@ +#ifndef _GPXE_IEEE80211_H +#define _GPXE_IEEE80211_H + +#include /* for ETH_ALEN */ + +/** @file + * Constants and data structures defined in IEEE 802.11, subsetted + * according to what gPXE knows how to use. + */ + +FILE_LICENCE(GPL2_OR_LATER); + +/* ---------- Maximum lengths of things ---------- */ + +/** + * @defgroup ieee80211_maxlen Maximum lengths in the 802.11 protocol + * @{ + */ + +/** Maximum length of frame payload + * + * This does not include cryptographic overhead, which can be up to 20 + * bytes, but it DOES include the 802.2 LLC/SNAP headers that are used + * on data frames (but not management frames). + */ +#define IEEE80211_MAX_DATA_LEN 2304 + +/** Length of LLC/SNAP headers on data frames */ +#define IEEE80211_LLC_HEADER_LEN 8 + +/** Maximum cryptographic overhead before encrypted data */ +#define IEEE80211_MAX_CRYPTO_HEADER 8 + +/** Maximum cryptographic overhead after encrypted data */ +#define IEEE80211_MAX_CRYPTO_TRAILER 12 + +/** Total maximum cryptographic overhead */ +#define IEEE80211_MAX_CRYPTO_OVERHEAD 20 + +/** Bytes of network-layer data that can go into a regular data frame */ +#define IEEE80211_MAX_FRAME_DATA 2296 + +/** Frame header length for frames we might work with + * + * QoS adds a two-byte field on top of this, and APs communicating + * with each other in Wireless Distribution System (WDS) mode add an + * extra 6-byte MAC address field, but we do not work with such + * frames. + */ +#define IEEE80211_TYP_FRAME_HEADER_LEN 24 + +/** Theoretical maximum frame header length + * + * This includes the QoS and WDS Addr4 fields that we should never + * see. + */ +#define IEEE80211_MAX_FRAME_HEADER_LEN 32 + +/** Maximum combined frame length + * + * The biggest frame will include 32 frame header bytes, 20 bytes of + * crypto overhead, and 2304 data bytes. + */ +#define IEEE80211_MAX_FRAME_LEN 2356 + +/** Maximum length of an ESSID */ +#define IEEE80211_MAX_SSID_LEN 32 + +/** @} */ + + +/* ---------- Frame Control defines ---------- */ + +/** + * @defgroup ieee80211_fc 802.11 Frame Control field bits + * @{ + */ + +/** 802.11 Frame Control field, Version bitmask */ +#define IEEE80211_FC_VERSION 0x0003 + +/** Expected value of Version bits in Frame Control */ +#define IEEE80211_THIS_VERSION 0x0000 + + +/** 802.11 Frame Control field, Frame Type bitmask */ +#define IEEE80211_FC_TYPE 0x000C + +/** Type value for management (layer-2) frames */ +#define IEEE80211_TYPE_MGMT 0x0000 + +/** Type value for control (layer-1, hardware-managed) frames */ +#define IEEE80211_TYPE_CTRL 0x0004 + +/** Type value for data frames */ +#define IEEE80211_TYPE_DATA 0x0008 + + +/** 802.11 Frame Control field, Frame Subtype bitmask */ +#define IEEE80211_FC_SUBTYPE 0x00F0 + +/** Subtype value for association-request management frames + * + * Association request frames are sent after authentication from the + * client to the Access Point to establish the client as part of the + * Access Point's network. + */ +#define IEEE80211_STYPE_ASSOC_REQ 0x0000 + +/** Subtype value for association-response management frames + * + * Association response frames are sent by the Access Point to confirm + * or deny the association requested in an association request frame. + */ +#define IEEE80211_STYPE_ASSOC_RESP 0x0010 + +/** Subtype value for reassociation-request management frames + * + * Reassociation request frames are sent by clients wishing to change + * from one Access Point to another while roaming within the same + * extended network (same ESSID). + */ +#define IEEE80211_STYPE_REASSOC_REQ 0x0020 + +/** Subtype value for reassociation-response management frames + * + * Reassociation response frames are sent by the Access Point to + * confirm or deny the swap requested in a reassociation request + * frame. + */ +#define IEEE80211_STYPE_REASSOC_RESP 0x0030 + +/** Subtype value for probe-request management frames + * + * Probe request frames are sent by clients to request that all Access + * Points on the sending channel, or all belonging to a particular + * ESSID, identify themselves by BSSID, supported transfer rates, RF + * configuration, and other capabilities. + */ +#define IEEE80211_STYPE_PROBE_REQ 0x0040 + +/** Subtype value for probe-response management frames + * + * Probe response frames are sent by Access Points in response to + * probe request frames, providing the requested information. + */ +#define IEEE80211_STYPE_PROBE_RESP 0x0050 + +/** Subtype value for beacon management frames + * + * Beacon frames are sent by Access Points at regular intervals, + * usually ten per second, on the channel on which they communicate. + * They can be used to probe passively for access points on a channel + * where local regulatory restrictions prohibit active scanning, or + * due to their regularity as a mechanism to determine the fraction of + * packets that are being dropped. + */ +#define IEEE80211_STYPE_BEACON 0x0080 + +/** Subtype value for disassociation management frames + * + * Disassociation frames are sent by either a client or an Access + * Point to unequivocally terminate the association between the two. + * They may be sent by clients upon leaving the network, or by an + * Access Point upon reconfiguration, among other reasons; they are + * usually more "polite" than deauthentication frames. + */ +#define IEEE80211_STYPE_DISASSOC 0x00A0 + +/** Subtype value for authentication management frames + * + * Authentication frames are exchanged between a client and an Access + * Point before association may be performed. Confusingly, in the most + * common authentication method (Open System) no security tokens are + * exchanged at all. Modern 802.11 security handshaking takes place + * after association. + */ +#define IEEE80211_STYPE_AUTH 0x00B0 + +/** Subtype value for deauthentication management frames + * + * Deauthentication frames are sent by either a client or an Access + * Point to terminate the authentication (and therefore also the + * association) between the two. They are generally more forceful than + * disassociation frames, sent for such reasons as a failure to + * set up security properly after associating. + */ +#define IEEE80211_STYPE_DEAUTH 0x00C0 + +/** Subtype value for action management frames + * + * Action frames are used to implement spectrum management and QoS + * features that gPXE currently does not support. + */ +#define IEEE80211_STYPE_ACTION 0x00D0 + + +/** Subtype value for RTS (request to send) control frames */ +#define IEEE80211_STYPE_RTS 0x00B0 + +/** Subtype value for CTS (clear to send) control frames */ +#define IEEE80211_STYPE_CTS 0x00C0 + +/** Subtype value for ACK (acknowledgement) control frames */ +#define IEEE80211_STYPE_ACK 0x00D0 + + +/** Subtype value for ordinary data frames, with no QoS or CF add-ons */ +#define IEEE80211_STYPE_DATA 0x0000 + +/** Subtype value for data frames containing no data */ +#define IEEE80211_STYPE_NODATA 0x0040 + + +/** 802.11 Frame Control field: To Data System flag + * + * This is set on data frames sent to an Access Point. + */ +#define IEEE80211_FC_TODS 0x0100 + +/** 802.11 Frame Control field: From Data System flag + * + * This is set on data frames sent from an Access Point. If both TODS + * and FROMDS are set, the frame header is a 4-address format used for + * inter-Access Point communication. + */ +#define IEEE80211_FC_FROMDS 0x0200 + +/** 802.11 Frame Control field: More Fragments flag */ +#define IEEE80211_FC_MORE_FRAG 0x0400 + +/** 802.11 Frame Control field: Retransmission flag */ +#define IEEE80211_FC_RETRY 0x0800 + +/** 802.11 Frame Control field: Power Managed flag + * + * This is set on any frame sent by a low-power station that will go + * into a power-saving mode immediately after this frame. Access + * Points are not allowed to act as low-power stations. + */ +#define IEEE80211_FC_PWR_MGMT 0x1000 + +/** 802.11 Frame Control field: More Data flag + * + * This is set on any frame sent by a station that has more data + * queued to be sent than is in the frame. + */ +#define IEEE80211_FC_MORE_DATA 0x2000 + +/** 802.11 Frame Control field: Protected flag + * + * This is set on frames in which data is encrypted (by any method). + */ +#define IEEE80211_FC_PROTECTED 0x4000 + +/** 802.11 Frame Control field: Ordered flag [?] */ +#define IEEE80211_FC_ORDER 0x8000 + +/** @} */ + + +/* ---------- Sequence Control defines ---------- */ + +/** + * @defgroup ieee80211_seq 802.11 Sequence Control field handling + * @{ + */ + +/** Extract sequence number from 802.11 Sequence Control field */ +#define IEEE80211_SEQNR( seq ) ( ( seq ) >> 4 ) + +/** Extract fragment number from 802.11 Sequence Control field */ +#define IEEE80211_FRAG( seq ) ( ( seq ) & 0x000F ) + +/** Make 802.11 Sequence Control field from sequence and fragment numbers */ +#define IEEE80211_MAKESEQ( seqnr, frag ) \ + ( ( ( ( seqnr ) & 0xFFF ) << 4 ) | ( ( frag ) & 0xF ) ) + +/** @} */ + + +/* ---------- Frame header formats ---------- */ + +/** + * @defgroup ieee80211_hdr 802.11 frame header formats + * @{ + */ + +/** An 802.11 data or management frame without QoS or WDS header fields */ +struct ieee80211_frame +{ + u16 fc; /**< 802.11 Frame Control field */ + u16 duration; /**< Microseconds to reserve link */ + u8 addr1[ETH_ALEN]; /**< Address 1 (immediate receiver) */ + u8 addr2[ETH_ALEN]; /**< Address 2 (immediate sender) */ + u8 addr3[ETH_ALEN]; /**< Address 3 (often "forward to") */ + u16 seq; /**< 802.11 Sequence Control field */ + u8 data[0]; /**< Beginning of frame data */ +} __attribute__((packed)); + +/** The 802.2 LLC/SNAP header sent before actual data in a data frame + * + * This header is not acknowledged in the 802.11 standard at all; it + * is treated just like data for MAC-layer purposes, including + * fragmentation and encryption. It is actually two headers + * concatenated: a three-byte 802.2 LLC header indicating Subnetwork + * Accesss Protocol (SNAP) in both source and destination Service + * Access Point (SAP) fields, and a five-byte SNAP header indicating a + * zero OUI and two-byte Ethernet protocol type field. + * + * Thus, an eight-byte header in which six of the bytes are redundant. + * Lovely, isn't it? + */ +struct ieee80211_llc_snap_header +{ + /* LLC part: */ + u8 dsap; /**< Destination SAP ID */ + u8 ssap; /**< Source SAP ID */ + u8 ctrl; /**< Control information */ + + /* SNAP part: */ + u8 oui[3]; /**< Organization code, usually 0 */ + u16 ethertype; /**< Ethernet Type field */ +} __attribute__((packed)); + +/** Value for DSAP field in 802.2 LLC header for 802.11 frames: SNAP */ +#define IEEE80211_LLC_DSAP 0xAA + +/** Value for SSAP field in 802.2 LLC header for 802.11 frames: SNAP */ +#define IEEE80211_LLC_SSAP 0xAA + +/** Value for control field in 802.2 LLC header for 802.11 frames + * + * "Unnumbered Information". + */ +#define IEEE80211_LLC_CTRL 0x03 + + +/** 16-byte RTS frame format, with abbreviated header */ +struct ieee80211_rts +{ + u16 fc; /**< 802.11 Frame Control field */ + u16 duration; /**< Microseconds to reserve link */ + u8 addr1[ETH_ALEN]; /**< Address 1 (immediate receiver) */ + u8 addr2[ETH_ALEN]; /**< Address 2 (immediate sender) */ +} __attribute__((packed)); + +/** Length of 802.11 RTS control frame */ +#define IEEE80211_RTS_LEN 16 + +/** 10-byte CTS or ACK frame format, with abbreviated header */ +struct ieee80211_cts_or_ack +{ + u16 fc; /**< 802.11 Frame Control field */ + u16 duration; /**< Microseconds to reserve link */ + u8 addr1[ETH_ALEN]; /**< Address 1 (immediate receiver) */ +} __attribute__((packed)); + +#define ieee80211_cts ieee80211_cts_or_ack +#define ieee80211_ack ieee80211_cts_or_ack + +/** Length of 802.11 CTS control frame */ +#define IEEE80211_CTS_LEN 10 + +/** Length of 802.11 ACK control frame */ +#define IEEE80211_ACK_LEN 10 + +/** @} */ + + +/* ---------- Capability bits, status and reason codes ---------- */ + +/** + * @defgroup ieee80211_capab 802.11 management frame capability field bits + * @{ + */ + +/** Set if using an Access Point (managed mode) */ +#define IEEE80211_CAPAB_MANAGED 0x0001 + +/** Set if operating in IBSS (no-AP, "Ad-Hoc") mode */ +#define IEEE80211_CAPAB_ADHOC 0x0002 + +/** Set if we support Contention-Free Period operation */ +#define IEEE80211_CAPAB_CFPOLL 0x0004 + +/** Set if we wish to be polled for Contention-Free operation */ +#define IEEE80211_CAPAB_CFPR 0x0008 + +/** Set if the network is encrypted (by any method) */ +#define IEEE80211_CAPAB_PRIVACY 0x0010 + +/** Set if PHY supports short preambles on 802.11b */ +#define IEEE80211_CAPAB_SHORT_PMBL 0x0020 + +/** Set if PHY supports PBCC modulation */ +#define IEEE80211_CAPAB_PBCC 0x0040 + +/** Set if we support Channel Agility */ +#define IEEE80211_CAPAB_CHAN_AGILITY 0x0080 + +/** Set if we support spectrum management (DFS and TPC) on the 5GHz band */ +#define IEEE80211_CAPAB_SPECTRUM_MGMT 0x0100 + +/** Set if we support Quality of Service enhancements */ +#define IEEE80211_CAPAB_QOS 0x0200 + +/** Set if PHY supports short slot time on 802.11g */ +#define IEEE80211_CAPAB_SHORT_SLOT 0x0400 + +/** Set if PHY supports APSD option */ +#define IEEE80211_CAPAB_APSD 0x0800 + +/** Set if PHY supports DSSS/OFDM modulation (one way of 802.11 b/g mixing) */ +#define IEEE80211_CAPAB_DSSS_OFDM 0x2000 + +/** Set if we support delayed block ACK */ +#define IEEE80211_CAPAB_DELAYED_BACK 0x4000 + +/** Set if we support immediate block ACK */ +#define IEEE80211_CAPAB_IMMED_BACK 0x8000 + +/** @} */ + + +/** + * @defgroup ieee80211_status 802.11 status codes + * + * These are returned to indicate an immediate denial of + * authentication or association. In gPXE, the lower 5 bits of the + * status code are encoded into the file-unique portion of an error + * code, the ERRFILE portion is always @c ERRFILE_net80211, and the + * POSIX error code is @c ECONNREFUSED for status 0-31 or @c + * EHOSTUNREACH for status 32-63. + * + * For a complete table with non-abbreviated error messages, see IEEE + * Std 802.11-2007, Table 7-23, p.94. + * + * @{ + */ + +#define IEEE80211_STATUS_SUCCESS 0 +#define IEEE80211_STATUS_FAILURE 1 +#define IEEE80211_STATUS_CAPAB_UNSUPP 10 +#define IEEE80211_STATUS_REASSOC_INVALID 11 +#define IEEE80211_STATUS_ASSOC_DENIED 12 +#define IEEE80211_STATUS_AUTH_ALGO_UNSUPP 13 +#define IEEE80211_STATUS_AUTH_SEQ_INVALID 14 +#define IEEE80211_STATUS_AUTH_CHALL_INVALID 15 +#define IEEE80211_STATUS_AUTH_TIMEOUT 16 +#define IEEE80211_STATUS_ASSOC_NO_ROOM 17 +#define IEEE80211_STATUS_ASSOC_NEED_RATE 18 +#define IEEE80211_STATUS_ASSOC_NEED_SHORT_PMBL 19 +#define IEEE80211_STATUS_ASSOC_NEED_PBCC 20 +#define IEEE80211_STATUS_ASSOC_NEED_CHAN_AGILITY 21 +#define IEEE80211_STATUS_ASSOC_NEED_SPECTRUM_MGMT 22 +#define IEEE80211_STATUS_ASSOC_BAD_POWER 23 +#define IEEE80211_STATUS_ASSOC_BAD_CHANNELS 24 +#define IEEE80211_STATUS_ASSOC_NEED_SHORT_SLOT 25 +#define IEEE80211_STATUS_ASSOC_NEED_DSSS_OFDM 26 +#define IEEE80211_STATUS_QOS_FAILURE 32 +#define IEEE80211_STATUS_QOS_NO_ROOM 33 +#define IEEE80211_STATUS_LINK_IS_HORRIBLE 34 +#define IEEE80211_STATUS_ASSOC_NEED_QOS 35 +#define IEEE80211_STATUS_REQUEST_DECLINED 37 +#define IEEE80211_STATUS_REQUEST_INVALID 38 +#define IEEE80211_STATUS_TS_NOT_CREATED_AGAIN 39 +#define IEEE80211_STATUS_INVALID_IE 40 +#define IEEE80211_STATUS_GROUP_CIPHER_INVALID 41 +#define IEEE80211_STATUS_PAIR_CIPHER_INVALID 42 +#define IEEE80211_STATUS_AKMP_INVALID 43 +#define IEEE80211_STATUS_RSN_VERSION_UNSUPP 44 +#define IEEE80211_STATUS_RSN_CAPAB_INVALID 45 +#define IEEE80211_STATUS_CIPHER_REJECTED 46 +#define IEEE80211_STATUS_TS_NOT_CREATED_WAIT 47 +#define IEEE80211_STATUS_DIRECT_LINK_FORBIDDEN 48 +#define IEEE80211_STATUS_DEST_NOT_PRESENT 49 +#define IEEE80211_STATUS_DEST_NOT_QOS 50 +#define IEEE80211_STATUS_ASSOC_LISTEN_TOO_HIGH 51 + +/** @} */ + + + +/** + * @defgroup ieee80211_reason 802.11 reason codes + * + * These are returned to indicate the reason for a deauthentication or + * disassociation sent (usually) after authentication or association + * had succeeded. In gPXE, the lower 5 bits of the reason code are + * encoded into the file-unique portion of an error code, the ERRFILE + * portion is always @c ERRFILE_net80211, and the POSIX error code is + * @c ECONNRESET for reason 0-31 or @c ENETRESET for reason 32-63. + * + * For a complete table with non-abbreviated error messages, see IEEE + * Std 802.11-2007, Table 7-22, p.92. + * + * @{ + */ + +#define IEEE80211_REASON_NONE 0 +#define IEEE80211_REASON_UNSPECIFIED 1 +#define IEEE80211_REASON_AUTH_NO_LONGER_VALID 2 +#define IEEE80211_REASON_LEAVING 3 +#define IEEE80211_REASON_INACTIVITY 4 +#define IEEE80211_REASON_OUT_OF_RESOURCES 5 +#define IEEE80211_REASON_NEED_AUTH 6 +#define IEEE80211_REASON_NEED_ASSOC 7 +#define IEEE80211_REASON_LEAVING_TO_ROAM 8 +#define IEEE80211_REASON_REASSOC_INVALID 9 +#define IEEE80211_REASON_BAD_POWER 10 +#define IEEE80211_REASON_BAD_CHANNELS 11 +#define IEEE80211_REASON_INVALID_IE 13 +#define IEEE80211_REASON_MIC_FAILURE 14 +#define IEEE80211_REASON_4WAY_TIMEOUT 15 +#define IEEE80211_REASON_GROUPKEY_TIMEOUT 16 +#define IEEE80211_REASON_4WAY_INVALID 17 +#define IEEE80211_REASON_GROUP_CIPHER_INVALID 18 +#define IEEE80211_REASON_PAIR_CIPHER_INVALID 19 +#define IEEE80211_REASON_AKMP_INVALID 20 +#define IEEE80211_REASON_RSN_VERSION_INVALID 21 +#define IEEE80211_REASON_RSN_CAPAB_INVALID 22 +#define IEEE80211_REASON_8021X_FAILURE 23 +#define IEEE80211_REASON_CIPHER_REJECTED 24 +#define IEEE80211_REASON_QOS_UNSPECIFIED 32 +#define IEEE80211_REASON_QOS_OUT_OF_RESOURCES 33 +#define IEEE80211_REASON_LINK_IS_HORRIBLE 34 +#define IEEE80211_REASON_INVALID_TXOP 35 +#define IEEE80211_REASON_REQUESTED_LEAVING 36 +#define IEEE80211_REASON_REQUESTED_NO_USE 37 +#define IEEE80211_REASON_REQUESTED_NEED_SETUP 38 +#define IEEE80211_REASON_REQUESTED_TIMEOUT 39 +#define IEEE80211_REASON_CIPHER_UNSUPPORTED 45 + +/** @} */ + +/* ---------- Information element declarations ---------- */ + +/** + * @defgroup ieee80211_ie 802.11 information elements + * + * Many management frames include a section that amounts to a + * concatenation of these information elements, so that the sender can + * choose which information to send and the receiver can ignore the + * parts it doesn't understand. Each IE contains a two-byte header, + * one byte ID and one byte length, followed by IE-specific data. The + * length does not include the two-byte header. Information elements + * are required to be sorted by ID, but gPXE does not require that in + * those it receives. + * + * This group also includes a few inline functions to simplify common + * tasks in IE processing. + * + * @{ + */ + +/** Generic 802.11 information element header */ +struct ieee80211_ie_header { + u8 id; /**< Information element ID */ + u8 len; /**< Information element length */ +} __attribute__ ((packed)); + + +/** 802.11 SSID information element */ +struct ieee80211_ie_ssid { + u8 id; /**< SSID ID: 0 */ + u8 len; /**< SSID length */ + char ssid[0]; /**< SSID data, not NUL-terminated */ +} __attribute__ ((packed)); + +/** Information element ID for SSID information element */ +#define IEEE80211_IE_SSID 0 + + +/** 802.11 rates information element + * + * The first 8 rates go in an IE of type RATES (1), and any more rates + * go in one of type EXT_RATES (50). Each rate is a byte with the low + * 7 bits equal to the rate in units of 500 kbps, and the high bit set + * if and only if the rate is "basic" (must be supported by all + * connected stations). + */ +struct ieee80211_ie_rates { + u8 id; /**< Rates ID: 1 or 50 */ + u8 len; /**< Number of rates */ + u8 rates[0]; /**< Rates data, one rate per byte */ +} __attribute__ ((packed)); + +/** Information element ID for rates information element */ +#define IEEE80211_IE_RATES 1 + +/** Information element ID for extended rates information element */ +#define IEEE80211_IE_EXT_RATES 50 + + +/** 802.11 Direct Spectrum parameter information element + * + * This just contains the channel number. It has the fancy name + * because IEEE 802.11 also defines a frequency-hopping PHY that + * changes channels at regular intervals following a predetermined + * pattern; in practice nobody uses the FH PHY. + */ +struct ieee80211_ie_ds_param { + u8 id; /**< DS parameter ID: 3 */ + u8 len; /**< DS parameter length: 1 */ + u8 current_channel; /**< Current channel number, 1-14 */ +} __attribute__ ((packed)); + +/** Information element ID for Direct Spectrum parameter information element */ +#define IEEE80211_IE_DS_PARAM 3 + + +/** 802.11 Country information element regulatory extension triplet */ +struct ieee80211_ie_country_ext_triplet { + u8 reg_ext_id; /**< Regulatory extension ID */ + u8 reg_class_id; /**< Regulatory class ID */ + u8 coverage_class; /**< Coverage class */ +} __attribute__ ((packed)); + +/** 802.11 Country information element regulatory band triplet */ +struct ieee80211_ie_country_band_triplet { + u8 first_channel; /**< Channel number for first channel in band */ + u8 nr_channels; /**< Number of contiguous channels in band */ + u8 max_txpower; /**< Maximum TX power in dBm */ +} __attribute__ ((packed)); + +/** 802.11 Country information element regulatory triplet + * + * It is a band triplet if the first byte is 200 or less, and a + * regulatory extension triplet otherwise. + */ +union ieee80211_ie_country_triplet { + /** Differentiator between band and ext triplets */ + u8 first; + + /** Information about a band of channels */ + struct ieee80211_ie_country_band_triplet band; + + /** Regulatory extension information */ + struct ieee80211_ie_country_ext_triplet ext; +}; + +/** 802.11 Country information element + * + * This contains some data about RF regulations. + */ +struct ieee80211_ie_country { + u8 id; /**< Country information ID: 7 */ + u8 len; /**< Country information length: varies */ + char name[2]; /**< ISO Alpha2 country code */ + char in_out; /**< 'I' for indoor, 'O' for outdoor */ + + /** List of regulatory triplets */ + union ieee80211_ie_country_triplet triplet[0]; +} __attribute__ ((packed)); + +/** Information element ID for Country information element */ +#define IEEE80211_IE_COUNTRY 7 + + +/** 802.11 Request information element + * + * This contains a list of information element types we would like to + * be included in probe response frames. + */ +struct ieee80211_ie_request { + u8 id; /**< Request ID: 10 */ + u8 len; /**< Number of IEs requested */ + u8 request[0]; /**< List of IEs requested */ +} __attribute__ ((packed)); + +/** Information element ID for Request information element */ +#define IEEE80211_IE_REQUEST 10 + + +/** 802.11 Challenge Text information element + * + * This is used in authentication frames under Shared Key + * authentication. + */ +struct ieee80211_ie_challenge_text { + u8 id; /**< Challenge Text ID: 16 */ + u8 len; /**< Challenge Text length: usually 128 */ + u8 challenge_text[0]; /**< Challenge Text data */ +} __attribute__ ((packed)); + +/** Information element ID for Challenge Text information element */ +#define IEEE80211_IE_CHALLENGE_TEXT 16 + + +/** 802.11 Power Constraint information element + * + * This is used to specify an additional power limitation on top of + * the Country requirements. + */ +struct ieee80211_ie_power_constraint { + u8 id; /**< Power Constraint ID: 52 */ + u8 len; /**< Power Constraint length: 1 */ + u8 power_constraint; /**< Decrease in allowed TX power, dBm */ +} __attribute__ ((packed)); + +/** Information element ID for Power Constraint information element */ +#define IEEE80211_IE_POWER_CONSTRAINT 52 + + +/** 802.11 Power Capability information element + * + * This is used in association request frames to indicate the extremes + * of our TX power abilities. It is required only if we indicate + * support for spectrum management. + */ +struct ieee80211_ie_power_capab { + u8 id; /**< Power Capability ID: 33 */ + u8 len; /**< Power Capability length: 2 */ + u8 min_txpower; /**< Minimum possible TX power, dBm */ + u8 max_txpower; /**< Maximum possible TX power, dBm */ +} __attribute__ ((packed)); + +/** Information element ID for Power Capability information element */ +#define IEEE80211_IE_POWER_CAPAB 33 + + +/** 802.11 Channels information element channel band tuple */ +struct ieee80211_ie_channels_channel_band { + u8 first_channel; /**< Channel number of first channel in band */ + u8 nr_channels; /**< Number of channels in band */ +} __attribute__ ((packed)); + +/** 802.11 Channels information element + * + * This is used in association frames to indicate the channels we can + * use. It is required only if we indicate support for spectrum + * management. + */ +struct ieee80211_ie_channels { + u8 id; /**< Channels ID: 36 */ + u8 len; /**< Channels length: 2 */ + + /** List of (start, length) channel bands we can use */ + struct ieee80211_ie_channels_channel_band channels[0]; +} __attribute__ ((packed)); + +/** Information element ID for Channels information element */ +#define IEEE80211_IE_CHANNELS 36 + + +/** 802.11 ERP Information information element + * + * This is used to communicate some PHY-level flags. + */ +struct ieee80211_ie_erp_info { + u8 id; /**< ERP Information ID: 42 */ + u8 len; /**< ERP Information length: 1 */ + u8 erp_info; /**< ERP flags */ +} __attribute__ ((packed)); + +/** Information element ID for ERP Information information element */ +#define IEEE80211_IE_ERP_INFO 42 + +/** ERP information element: Flag set if 802.11b stations are present */ +#define IEEE80211_ERP_NONERP_PRESENT 0x01 + +/** ERP information element: Flag set if CTS protection must be used */ +#define IEEE80211_ERP_USE_PROTECTION 0x02 + +/** ERP information element: Flag set if long preambles must be used */ +#define IEEE80211_ERP_BARKER_LONG 0x04 + + +/** 802.11 Robust Security Network ("WPA") information element + * + * Showing once again a striking clarity of design, the IEEE folks put + * dynamically-sized data in the middle of this structure. As such, + * the below structure definition is only a guideline; the + * @c IEEE80211_RSN_FIELD, @c IEEE80211_RSN_CIPHER, and + * @c IEEE80211_RSN_AUTHTYPE macros should be used to access any + * data. + * + * Also inspired was IEEE's choice of 16-bit fields to count the + * number of 4-byte elements in a structure with a maximum length of + * 255 bytes. + * + * Many fields reference a cipher or authentication-type ID; this is a + * three-byte OUI followed by one byte identifying the cipher with + * respect to that OUI. For all standard ciphers the OUI is 00:0F:AC. + * + * The authentication types referenced in this structure have nothing + * to do with 802.11 authentication frames or the @c algorithm field + * within them. + */ +struct ieee80211_ie_rsn { + /** Information element ID */ + u8 id; + + /** Information element length */ + u8 len; + + /** RSN information element version */ + u16 version; + + /** Cipher ID for the cipher used in multicast/broadcast frames */ + u8 group_cipher[4]; + + /** Number of unicast ciphers supported */ + u16 pairwise_count; + + /** List of cipher IDs for supported unicast frame ciphers */ + u8 pairwise_cipher[4]; + + /** Number of authentication types supported */ + u16 akm_count; + + /** List of authentication type IDs for supported types */ + u8 akm_list[4]; + + /** Security capabilities field. */ + u16 rsn_capab; + + /** Number of PMKIDs included (present only in association frames) */ + u16 pmkid_count; + + /** List of PMKIDs included, each a 16-byte SHA1 hash */ + u8 pmkid_list[0]; +} __attribute__((packed)); + +/** Information element ID for Robust Security Network information element */ +#define IEEE80211_IE_RSN 48 + +/** OUI for standard ciphers in RSN information element */ +#define IEEE80211_RSN_OUI "\x00\x0F\xAC" + +/** Extract RSN IE version field */ +#define IEEE80211_RSN_FIELD_version( rsnp ) ( (rsnp)->version ) + +/** Extract RSN IE group_cipher field */ +#define IEEE80211_RSN_FIELD_group_cipher( rsnp ) ( (rsnp)->group_cipher ) + +/** Extract RSN IE pairwise_count field */ +#define IEEE80211_RSN_FIELD_pairwise_count( rsnp ) ( (rsnp)->pairwise_count ) + +/** Extract RSN IE akm_count field */ +#define IEEE80211_RSN_FIELD_akm_count( rsnp ) \ + ( ( ( struct ieee80211_ie_rsn * ) ( ( void * ) ( rsnp ) + \ + 4*( ( rsnp )->pairwise_count - 1 ) ) )->akm_count ) + +/** Extract RSN IE rsn_capab field */ +#define IEEE80211_RSN_FIELD_rsn_capab( rsnp ) \ + ( ( ( struct ieee80211_ie_rsn * ) ( ( void * ) ( rsnp ) + \ + 4*( ( rsnp )->pairwise_count - 1 ) + \ + 4*( ( rsnp )->akm_count - 1 ) ) )->rsn_capab ) + +/** Extract RSN IE pmkid_count field */ +#define IEEE80211_RSN_FIELD_pmkid_count( rsnp ) \ + ( ( ( struct ieee80211_ie_rsn * ) ( ( void * ) ( rsnp ) + \ + 4*( ( rsnp )->pairwise_count - 1 ) + \ + 4*( ( rsnp )->akm_count - 1 ) ) )->pmkid_count ) + +/** Extract field from RSN information element + * + * @v rsnp Pointer to RSN information element + * @v field Name of field to extract + * @ret val Lvalue of the requested field + * + * You must fill the fields of the structure in order for this to work + * properly. + */ +#define IEEE80211_RSN_FIELD( rsnp, field ) \ + IEEE80211_RSN_FIELD_ ## field ( rsnp ) + +/** Get pointer to pairwise cipher from RSN information element + * + * @v rsnp Pointer to RSN information element + * @v cipher Index of pairwise cipher to extract + * @ret ptr Pointer to requested cipher + */ +#define IEEE80211_RSN_CIPHER( rsnp, cipher ) \ + ( ( rsnp )->pairwise_cipher + 4 * ( cipher ) ) + +/** Get pointer to authentication type from RSN information element + * + * @v rsnp Pointer to RSN information element + * @v akm Index of authentication type to extract + * @ret ptr Pointer to requested authentication type + * + * The @c pairwise_count field must be correct. + */ +#define IEEE80211_RSN_AUTHTYPE( rsnp, akm ) \ + ( ( rsnp )->akm_list + 4 * ( ( rsnp )->pairwise_count - 1 ) + 4 * ( akm ) ) + +/** Get pointer to PMKID from RSN information element + * + * @v rsnp Pointer to RSN information element + * @v idx Index of PMKID to extract + * @ret ptr Pointer to requested PMKID + * + * The @c pairwise_count and @c akm_count fields must be correct. + */ +#define IEEE80211_RSN_PMKID( rsnp, idx ) \ + ( ( rsnp )->pmkid_list + 4 * ( ( rsnp )->pairwise_count - 1 ) + \ + 4 * ( ( rsnp )->akm_count - 1 ) + 16 * ( idx ) ) + +/** Verify size of RSN information element + * + * @v rsnp Pointer to RSN information element + * @ret ok TRUE if count fields are consistent with length field + * + * It is important to drop any RSN IE that does not pass this function + * before using the @c IEEE80211_RSN_FIELD, @c IEEE80211_RSN_CIPHER, + * and @c IEEE80211_RSN_AUTHTYPE macros, to avoid potential security + * compromise due to a malformed RSN IE. + * + * This function does not consider the possibility of some PMKIDs + * included in the RSN IE, because PMKIDs are only included in RSN IEs + * sent in association request frames, and we should never receive an + * association request frame. An RSN IE that includes PMKIDs will + * always fail this check. + */ +static inline int ieee80211_rsn_check ( struct ieee80211_ie_rsn *rsnp ) { + if ( rsnp->len < 12 + 4 * rsnp->pairwise_count ) + return 0; + return ( rsnp->len == 12 + 4 * ( rsnp->pairwise_count + + IEEE80211_RSN_FIELD ( rsnp, akm_count ) ) ); +} + +/** Calculate necessary size of RSN information element + * + * @v npair Number of pairwise ciphers supported + * @v nauth Number of authentication types supported + * @v npmkid Number of PMKIDs to include + * @ret size Necessary size of RSN IE, including header bytes + */ +static inline size_t ieee80211_rsn_size ( int npair, int nauth, int npmkid ) { + return 16 + 4 * ( npair + nauth ) + 16 * npmkid; +} + +/** 802.11 RSN IE: expected version number */ +#define IEEE80211_RSN_VERSION 1 + +/** 802.11 RSN IE: fourth byte of cipher type for 40-bit WEP */ +#define IEEE80211_RSN_CTYPE_WEP40 1 + +/** 802.11 RSN IE: fourth byte of cipher type for 104-bit WEP */ +#define IEEE80211_RSN_CTYPE_WEP104 5 + +/** 802.11 RSN IE: fourth byte of cipher type for TKIP ("WPA") */ +#define IEEE80211_RSN_CTYPE_TKIP 2 + +/** 802.11 RSN IE: fourth byte of cipher type for CCMP ("WPA2") */ +#define IEEE80211_RSN_CTYPE_CCMP 4 + +/** 802.11 RSN IE: fourth byte of cipher type for "use group" + * + * This can only appear as a pairwise cipher, and means unicast frames + * should be encrypted in the same way as broadcast/multicast frames. + */ +#define IEEE80211_RSN_CTYPE_USEGROUP 0 + +/** 802.11 RSN IE: fourth byte of auth method type for using an 802.1X server */ +#define IEEE80211_RSN_ATYPE_8021X 1 + +/** 802.11 RSN IE: fourth byte of auth method type for using a pre-shared key */ +#define IEEE80211_RSN_ATYPE_PSK 2 + +/** 802.11 RSN IE capabilities: AP supports pre-authentication */ +#define IEEE80211_RSN_CAPAB_PREAUTH 0x001 + +/** 802.11 RSN IE capabilities: Node has conflict between TKIP and WEP + * + * This is a legacy issue; APs always set it to 0, and gPXE sets it to + * 0. + */ +#define IEEE80211_RSN_CAPAB_NO_PAIRWISE 0x002 + +/** 802.11 RSN IE capabilities: Number of PTKSA replay counters + * + * A value of 0 means one replay counter, 1 means two, 2 means four, + * and 3 means sixteen. + */ +#define IEEE80211_RSN_CAPAB_PTKSA_REPLAY 0x00C + +/** 802.11 RSN IE capabilities: Number of GTKSA replay counters + * + * A value of 0 means one replay counter, 1 means two, 2 means four, + * and 3 means sixteen. + */ +#define IEEE80211_RSN_CAPAB_GTKSA_REPLAY 0x030 + +/** 802.11 RSN IE capabilities: PeerKey Handshaking is suported */ +#define IEEE80211_RSN_CAPAB_PEERKEY 0x200 + + + +/** Any 802.11 information element + * + * This is formatted for ease of use, so IEs with complex structures + * get referenced in full, while those with only one byte of data or a + * simple array are pulled in to avoid a layer of indirection like + * ie->channels.channels[0]. + */ +union ieee80211_ie +{ + /** Generic and simple information element info */ + struct { + u8 id; /**< Information element ID */ + u8 len; /**< Information element data length */ + union { + char ssid[0]; /**< SSID text */ + u8 rates[0]; /**< Rates data */ + u8 request[0]; /**< Request list */ + u8 challenge_text[0]; /**< Challenge text data */ + u8 power_constraint; /**< Power constraint, dBm */ + u8 erp_info; /**< ERP information flags */ + /** List of channels */ + struct ieee80211_ie_channels_channel_band channels[0]; + }; + }; + + /** DS parameter set */ + struct ieee80211_ie_ds_param ds_param; + + /** Country information */ + struct ieee80211_ie_country country; + + /** Power capability */ + struct ieee80211_ie_power_capab power_capab; + + /** Security information */ + struct ieee80211_ie_rsn rsn; +}; + +/** Advance to next 802.11 information element + * + * @v ie Current information element pointer + * @v end Pointer to first byte not in information element space + * @ret next Pointer to next information element, or NULL if no more + * + * When processing received IEs, @a end should be set to the I/O + * buffer tail pointer; when marshalling IEs for sending, @a end + * should be NULL. + */ +static inline union ieee80211_ie * ieee80211_next_ie ( union ieee80211_ie *ie, + void *end ) +{ + void *next_ie_byte = ( void * ) ie + ie->len + 2; + union ieee80211_ie *next_ie = next_ie_byte; + + if ( ! end ) + return next_ie; + + if ( next_ie_byte < end && next_ie_byte + next_ie->len <= end ) + return next_ie; + + return NULL; +} + +/** @} */ + + +/* ---------- Management frame data formats ---------- */ + +/** + * @defgroup ieee80211_mgmt_data Management frame data payloads + * @{ + */ + +/** Beacon or probe response frame data */ +struct ieee80211_beacon_or_probe_resp +{ + /** 802.11 TSFT value at frame send */ + u64 timestamp; + + /** Interval at which beacons are sent, in units of 1024 us */ + u16 beacon_interval; + + /** Capability flags */ + u16 capability; + + /** List of information elements */ + union ieee80211_ie info_element[0]; +} __attribute__((packed)); + +#define ieee80211_beacon ieee80211_beacon_or_probe_resp +#define ieee80211_probe_resp ieee80211_beacon_or_probe_resp + +/** Disassociation or deauthentication frame data */ +struct ieee80211_disassoc_or_deauth +{ + /** Reason code */ + u16 reason; +} __attribute__((packed)); + +#define ieee80211_disassoc ieee80211_disassoc_or_deauth +#define ieee80211_deauth ieee80211_disassoc_or_deauth + +/** Association request frame data */ +struct ieee80211_assoc_req +{ + /** Capability flags */ + u16 capability; + + /** Interval at which we wake up, in units of the beacon interval */ + u16 listen_interval; + + /** List of information elements */ + union ieee80211_ie info_element[0]; +} __attribute__((packed)); + +/** Association or reassociation response frame data */ +struct ieee80211_assoc_or_reassoc_resp +{ + /** Capability flags */ + u16 capability; + + /** Status code */ + u16 status; + + /** Association ID */ + u16 aid; + + /** List of information elements */ + union ieee80211_ie info_element[0]; +} __attribute__((packed)); + +#define ieee80211_assoc_resp ieee80211_assoc_or_reassoc_resp +#define ieee80211_reassoc_resp ieee80211_assoc_or_reassoc_resp + +/** Reassociation request frame data */ +struct ieee80211_reassoc_req +{ + /** Capability flags */ + u16 capability; + + /** Interval at which we wake up, in units of the beacon interval */ + u16 listen_interval; + + /** MAC address of current Access Point */ + u8 current_addr[ETH_ALEN]; + + /** List of information elements */ + union ieee80211_ie info_element[0]; +} __attribute__((packed)); + +/** Probe request frame data */ +struct ieee80211_probe_req +{ + /** List of information elements */ + union ieee80211_ie info_element[0]; +} __attribute__((packed)); + +/** Authentication frame data */ +struct ieee80211_auth +{ + /** Authentication algorithm (Open System or Shared Key) */ + u16 algorithm; + + /** Sequence number of this frame; first from client to AP is 1 */ + u16 tx_seq; + + /** Status code */ + u16 status; + + /** List of information elements */ + union ieee80211_ie info_element[0]; +} __attribute__((packed)); + +/** Open System authentication algorithm */ +#define IEEE80211_AUTH_OPEN_SYSTEM 0 + +/** Shared Key authentication algorithm */ +#define IEEE80211_AUTH_SHARED_KEY 1 + +/** @} */ + +#endif diff --git a/src/include/gpxe/net80211.h b/src/include/gpxe/net80211.h new file mode 100644 index 00000000..a1bddd5d --- /dev/null +++ b/src/include/gpxe/net80211.h @@ -0,0 +1,983 @@ +#ifndef _GPXE_NET80211_H +#define _GPXE_NET80211_H + +#include +#include +#include +#include +#include + +/** @file + * + * The gPXE 802.11 MAC layer. + */ + +/* + * Major things NOT YET supported: + * - any type of security + * - 802.11n + * + * Major things that probably will NEVER be supported, barring a + * compelling use case and/or corporate sponsorship: + * - QoS + * - 802.1X authentication ("WPA Enterprise") + * - Contention-free periods + * - "ad-hoc" networks (IBSS), monitor mode, host AP mode + * - hidden networks on the 5GHz band due to regulatory issues + * - spectrum management on the 5GHz band (TPC and DFS), as required + * in some non-US regulatory domains + * - Clause 14 PHYs (Frequency-Hopping Spread Spectrum on 2.4GHz) + * and Clause 16 PHYs (infrared) - I'm not aware of any real-world + * use of these. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/* All 802.11 devices are handled using a generic "802.11 device" + net_device, with a link in its `priv' field to a net80211_device + which we use to handle 802.11-specific details. */ + + +/** @defgroup net80211_band RF bands on which an 802.11 device can transmit */ +/** @{ */ + +/** The 2.4 GHz ISM band, unlicensed in most countries */ +#define NET80211_BAND_2GHZ (1 << 0) +/** The band from 4.9 GHz to 5.7 GHz, which tends to be more restricted */ +#define NET80211_BAND_5GHZ (1 << 1) + +/** @} */ + + +/** @defgroup net80211_mode 802.11 operation modes supported by hardware */ +/** @{ */ + +/** 802.11a: 54 Mbps operation using OFDM signaling on the 5GHz band */ +#define NET80211_MODE_A (1 << 0) + +/** 802.11b: 1-11 Mbps operation using DSSS/CCK signaling on the 2.4GHz band */ +#define NET80211_MODE_B (1 << 1) + +/** 802.11g: 54 Mbps operation using ERP/OFDM signaling on the 2.4GHz band */ +#define NET80211_MODE_G (1 << 2) + +/** 802.11n: High-rate operation using MIMO technology on 2.4GHz or 5GHz */ +#define NET80211_MODE_N (1 << 3) + +/** @} */ + + +/** @defgroup net80211_cfg Constants for the net80211 config callback */ +/** @{ */ + +/** Channel choice (@c dev->channel) or regulatory parameters have changed */ +#define NET80211_CFG_CHANNEL (1 << 0) + +/** Requested transmission rate (@c dev->rate) has changed */ +#define NET80211_CFG_RATE (1 << 1) + +/** Association has been established with a new BSS (@c dev->bssid) */ +#define NET80211_CFG_ASSOC (1 << 2) + +/** Low-level link parameters (short preamble, protection, etc) have changed */ +#define NET80211_CFG_PHY_PARAMS (1 << 3) + +/** @} */ + + +/** An 802.11 security handshaking protocol */ +enum net80211_security_proto { + /** No security handshaking + * + * This might be used with an open network or with WEP, as + * WEP does not have a cryptographic handshaking phase. + */ + NET80211_SECPROT_NONE = 0, + + /** Pre-shared key handshaking + * + * This implements the "WPA Personal" handshake. 802.1X + * authentication is not performed -- the user supplies a + * pre-shared key directly -- but there is a 4-way handshake + * between client and AP to verify that both have the same key + * without revealing the contents of that key. + */ + NET80211_SECPROT_PSK = 1, + + /** Full EAP 802.1X handshaking + * + * This implements the "WPA Enterprise" handshake, connecting + * to an 802.1X authentication server to provide credentials + * and receive a pairwise master key (PMK), which is then used + * in the same 4-way handshake as the PSK method. + */ + NET80211_SECPROT_EAP = 2, +}; + + +/** An 802.11 data encryption algorithm */ +enum net80211_crypto_alg { + /** No security, an "Open" network */ + NET80211_CRYPT_NONE = 0, + + /** Network protected with WEP (awful RC4-based system) + * + * WEP uses a naive application of RC4, with a monotonically + * increasing initialization vector that is prepended to the + * key to initialize the RC4 keystream. It is highly insecure + * and can be completely cracked or subverted using automated, + * robust, freely available tools (aircrack-ng) in minutes. + * + * 40-bit and 104-bit WEP are differentiated only by the size + * of the key. They may be advertised as 64-bit and 128-bit, + * counting the non-random IV as part of the key bits. + */ + NET80211_CRYPT_WEP = 1, + + /** Network protected with TKIP (better RC4-based system) + * + * Usually known by its trade name of WPA (Wi-Fi Protected + * Access), TKIP implements a message integrity code (MIC) + * called Michael, a timestamp counter for replay prevention, + * and a key mixing function that together remove almost all + * the security problems with WEP. Countermeasures are + * implemented to prevent high data-rate attacks. + * + * There exists one known attack on TKIP, that allows one to + * send between 7 and 15 arbitrary short data packets on a + * QoS-enabled network given about an hour of data + * gathering. Since gPXE does not support QoS for 802.11 + * networks, this is not a threat to us. The only other method + * is a brute-force passphrase attack. + */ + NET80211_CRYPT_TKIP = 2, + + /** Network protected with CCMP (AES-based system) + * + * Often called WPA2 in commerce, or RSNA (Robust Security + * Network Architecture) in the 802.11 standard, CCMP is + * highly secure and does not have any known attack vectors. + * Since it is based on a block cipher, the statistical + * correlation and "chopchop" attacks used with great success + * against WEP and minor success against TKIP fail. + */ + NET80211_CRYPT_CCMP = 3, +}; + + +/** @defgroup net80211_state Bits for the 802.11 association state field */ +/** @{ */ + +/** An error code indicating the failure mode, or 0 if successful */ +#define NET80211_STATUS_MASK 0x7F + +/** Whether the error code provided is a "reason" code, not a "status" code */ +#define NET80211_IS_REASON 0x80 + +/** Whether we have found the network we will be associating with */ +#define NET80211_PROBED (1 << 8) + +/** Whether we have successfully authenticated with the network + * + * This usually has nothing to do with actual security; it is a + * holdover from older 802.11 implementation ideas. + */ +#define NET80211_AUTHENTICATED (1 << 9) + +/** Whether we have successfully associated with the network */ +#define NET80211_ASSOCIATED (1 << 10) + +/** Whether we have completed security handshaking with the network + * + * Once this is set, we can send data packets. For that reason this + * bit is set even in cases where no security handshaking is + * required. + */ +#define NET80211_CRYPTO_SYNCED (1 << 11) + +/** Whether the auto-association task is running */ +#define NET80211_WORKING (1 << 12) + +/** Whether the auto-association task is waiting for a reply from the AP */ +#define NET80211_WAITING (1 << 13) + +/** Whether the auto-association task should be suppressed + * + * This is set by the `iwlist' command so that it can open the device + * without starting another probe process that will interfere with its + * own. + */ +#define NET80211_NO_ASSOC (1 << 14) + +/** Whether this association was performed using a broadcast SSID + * + * If the user opened this device without netX/ssid set, the device's + * SSID will be set to that of the network it chooses to associate + * with, but the netX/ssid setting will remain blank. If we don't + * remember that we started from no specified SSID, it will appear + * every time settings are updated (e.g. after DHCP) that we need to + * reassociate due to the difference between the set SSID and our own. + */ +#define NET80211_AUTO_SSID (1 << 15) + + +/** @} */ + + +/** @defgroup net80211_phy 802.11 physical layer flags */ +/** @{ */ + +/** Whether to use RTS/CTS or CTS-to-self protection for transmissions + * + * Since the RTS or CTS is transmitted using 802.11b signaling, and + * includes a field indicating the amount of time that will be used by + * transmission of the following packet, this serves as an effective + * protection mechanism to avoid 802.11b clients interfering with + * 802.11g clients on mixed networks. + */ +#define NET80211_PHY_USE_PROTECTION (1 << 1) + +/** Whether to use 802.11b short preamble operation + * + * Short-preamble operation can moderately increase throughput on + * 802.11b networks operating between 2Mbps and 11Mbps. It is + * irrelevant for 802.11g data rates, since they use a different + * modulation scheme. + */ +#define NET80211_PHY_USE_SHORT_PREAMBLE (1 << 2) + +/** Whether to use 802.11g short slot operation + * + * This affects a low-level timing parameter of 802.11g transmissions. + */ +#define NET80211_PHY_USE_SHORT_SLOT (1 << 3) + +/** @} */ + + +/** The maximum number of TX rates we allow to be configured simultaneously */ +#define NET80211_MAX_RATES 16 + +/** The maximum number of channels we allow to be configured simultaneously */ +#define NET80211_MAX_CHANNELS 32 + +/** Seconds we'll wait to get all fragments of a packet */ +#define NET80211_FRAG_TIMEOUT 2 + +/** The number of fragments we can receive at once + * + * The 802.11 standard requires that this be at least 3. + */ +#define NET80211_NR_CONCURRENT_FRAGS 3 + +/** Maximum TX power to allow (dBm), if we don't get a regulatory hint */ +#define NET80211_REG_TXPOWER 20 + + +struct net80211_device; + +/** Operations that must be implemented by an 802.11 driver */ +struct net80211_device_operations { + /** Open 802.11 device + * + * @v dev 802.11 device + * @ret rc Return status code + * + * This method should allocate RX I/O buffers and enable the + * hardware to start transmitting and receiving packets on the + * channels its net80211_register() call indicated it could + * handle. It does not need to tune the antenna to receive + * packets on any particular channel. + */ + int ( * open ) ( struct net80211_device *dev ); + + /** Close 802.11 network device + * + * @v dev 802.11 device + * + * This method should stop the flow of packets, and call + * net80211_tx_complete() for any packets remaining in the + * device's TX queue. + */ + void ( * close ) ( struct net80211_device *dev ); + + /** Transmit packet on 802.11 network device + * + * @v dev 802.11 device + * @v iobuf I/O buffer + * @ret rc Return status code + * + * This method should cause the hardware to initiate + * transmission of the I/O buffer, using the channel and rate + * most recently indicated by an appropriate call to the + * @c config callback. The 802.11 layer guarantees that said + * channel and rate will be the same as those currently + * reflected in the fields of @a dev. + * + * If this method returns success, the I/O buffer remains + * owned by the network layer's TX queue, and the driver must + * eventually call net80211_tx_complete() to free the buffer + * whether transmission succeeded or not. If this method + * returns failure, it will be interpreted as "failure to + * enqueue buffer" and the I/O buffer will be immediately + * released. + * + * This method is guaranteed to be called only when the device + * is open. + */ + int ( * transmit ) ( struct net80211_device *dev, + struct io_buffer *iobuf ); + + /** Poll for completed and received packets + * + * @v dev 802.11 device + * + * This method should cause the hardware to check for + * completed transmissions and received packets. Any received + * packets should be delivered via net80211_rx(), and + * completed transmissions should be indicated using + * net80211_tx_complete(). + * + * This method is guaranteed to be called only when the device + * is open. + */ + void ( * poll ) ( struct net80211_device *dev ); + + /** Enable or disable interrupts + * + * @v dev 802.11 device + * @v enable If TRUE, interrupts should be enabled + */ + void ( * irq ) ( struct net80211_device *dev, int enable ); + + /** Update hardware state to match 802.11 layer state + * + * @v dev 802.11 device + * @v changed Set of flags indicating what may have changed + * @ret rc Return status code + * + * This method should cause the hardware state to be + * reinitialized from the state indicated in fields of + * net80211_device, in the areas indicated by bits set in + * @a changed. If the hardware is unable to do so, this method + * may return an appropriate error indication. + * + * This method is guaranteed to be called only when the device + * is open. + */ + int ( * config ) ( struct net80211_device *dev, int changed ); +}; + +/** An 802.11 RF channel. */ +struct net80211_channel +{ + /** The band with which this channel is associated */ + u8 band; + + /** A channel number interpreted according to the band + * + * The 2.4GHz band uses channel numbers from 1-13 at 5MHz + * intervals such that channel 1 is 2407 MHz; channel 14, + * legal for use only in Japan, is defined separately as 2484 + * MHz. Adjacent channels will overlap, since 802.11 + * transmissions use a 20 MHz (4-channel) bandwidth. Most + * commonly, channels 1, 6, and 11 are used. + * + * The 5GHz band uses channel numbers derived directly from + * the frequency; channel 0 is 5000 MHz, and channels are + * always spaced 5 MHz apart. Channel numbers over 180 are + * relative to 4GHz instead of 5GHz, but these are rarely + * seen. Most channels are not legal for use. + */ + u8 channel_nr; + + /** The center frequency for this channel + * + * Currently a bandwidth of 20 MHz is assumed. + */ + u16 center_freq; + + /** Maximum allowable transmit power, in dBm + * + * This should be interpreted as EIRP, the power supplied to + * an ideal isotropic antenna in order to achieve the same + * average signal intensity as the real hardware at a + * particular distance. + * + * Currently no provision is made for directional antennas. + */ + u8 maxpower; +}; + +/** Information on the capabilities of an 802.11 hardware device + * + * In its probe callback, an 802.11 driver must read hardware + * registers to determine the appropriate contents of this structure, + * fill it, and pass it to net80211_register() so that the 802.11 + * layer knows how to treat the hardware and what to advertise as + * supported to access points. + */ +struct net80211_hw_info +{ + /** Default hardware MAC address. + * + * The user may change this by setting the @c netX/mac setting + * before the driver's open function is called; in that case + * the driver must set the hardware MAC address to the address + * contained in the wrapping net_device's ll_addr field, or if + * that is impossible, set that ll_addr field back to the + * unchangeable hardware MAC address. + */ + u8 hwaddr[ETH_ALEN]; + + /** A bitwise OR of the 802.11x modes supported by this device */ + int modes; + + /** A bitwise OR of the bands on which this device can communicate */ + int bands; + + /** A set of flags indicating peculiarities of this device. */ + enum { + /** Received frames include a frame check sequence. */ + NET80211_HW_RX_HAS_FCS = (1 << 1), + + /** Hardware doesn't support 2.4GHz short preambles + * + * This is only relevant for 802.11b operation above + * 2Mbps. All 802.11g devices support short preambles. + */ + NET80211_HW_NO_SHORT_PREAMBLE = (1 << 2), + + /** Hardware doesn't support 802.11g short slot operation */ + NET80211_HW_NO_SHORT_SLOT = (1 << 3), + } flags; + + /** Signal strength information that can be provided by the device + * + * Signal strength is passed to net80211_rx(), primarily to + * allow determination of the closest access point for a + * multi-AP network. The units are provided for completeness + * of status displays. + */ + enum { + /** No signal strength information supported */ + NET80211_SIGNAL_NONE = 0, + /** Signal strength in arbitrary units */ + NET80211_SIGNAL_ARBITRARY, + /** Signal strength in decibels relative to arbitrary base */ + NET80211_SIGNAL_DB, + /** Signal strength in decibels relative to 1mW */ + NET80211_SIGNAL_DBM, + } signal_type; + + /** Maximum signal in arbitrary cases + * + * If signal_type is NET80211_SIGNAL_ARBITRARY or + * NET80211_SIGNAL_DB, the driver should report it on a scale + * from 0 to signal_max. + */ + unsigned signal_max; + + /** List of transmission rates supported by the card + * + * Rates should be in 100kbps increments (e.g. 11 Mbps would + * be represented as the number 110). + */ + u16 supported_rates[NET80211_MAX_RATES]; + + /** Number of supported rates */ + int nr_supported_rates; + + /** Estimate of the time required to change channels, in microseconds + * + * If this is not known, a guess on the order of a few + * milliseconds (value of 1000-5000) is reasonable. + */ + unsigned channel_change_time; +}; + +/** Structure tracking received fragments for a packet + * + * We set up a fragment cache entry when we receive a packet marked as + * fragment 0 with the "more fragments" bit set in its frame control + * header. We are required by the 802.11 standard to track 3 + * fragmented packets arriving simultaneously; if we receive more we + * may drop some. Upon receipt of a new fragment-0 packet, if no entry + * is available or expired, we take over the most @e recent entry for + * the new packet, since we don't want to starve old entries from ever + * finishing at all. If we get a fragment after the zeroth with no + * cache entry for its packet, we drop it. + */ +struct net80211_frag_cache +{ + /** Whether this cache entry is in use */ + u8 in_use; + + /** Sequence number of this MSDU (packet) */ + u16 seqnr; + + /** Timestamp from point at which first fragment was collected */ + u32 start_ticks; + + /** Buffers for each fragment */ + struct io_buffer *iob[16]; +}; + +/** Interface to an 802.11 cryptographic algorithm + * + * Cryptographic algorithms define a net80211_crypto structure + * statically, using a gPXE linker table to make it available to the + * 802.11 layer. When the algorithm needs to be used, the 802.11 code + * will allocate a copy of the static definition plus whatever space + * the algorithm has requested for private state, and point + * net80211_device::crypto at it. + */ +struct net80211_crypto +{ + /** The cryptographic algorithm implemented */ + enum net80211_crypto_alg algorithm; + + /** Initialize cryptographic algorithm using a given key + * + * @v crypto 802.11 cryptographic algorithm + * @v key Pointer to key bytes + * @v keylen Number of key bytes + * @ret rc Return status code + * + * This method is passed the communication key provided by the + * security handshake handler, which will already be in the + * low-level form required. + */ + int ( * initialize ) ( struct net80211_crypto *crypto, u8 *key, + int keylen ); + + /** Encrypt a frame using the cryptographic algorithm + * + * @v crypto 802.11 cryptographic algorithm + * @v iob I/O buffer + * @ret eiob Newly allocated I/O buffer with encrypted packet + * + * This method is called to encrypt a single frame. It is + * guaranteed that initialize() will have completed + * successfully before this method is called. + * + * The frame passed already has an 802.11 header prepended, + * but the PROTECTED bit in the frame control field will not + * be set; this method is responsible for setting it. The + * returned I/O buffer should contain a complete copy of @a + * iob, including the 802.11 header, but with the PROTECTED + * bit set, the data encrypted, and whatever encryption + * headers/trailers are necessary added. + * + * This method should never free the passed I/O buffer. + * + * Return NULL if the packet could not be encrypted, due to + * memory limitations or otherwise. + */ + struct io_buffer * ( * encrypt ) ( struct net80211_crypto *crypto, + struct io_buffer *iob ); + + /** Decrypt a frame using the cryptographic algorithm + * + * @v crypto 802.11 cryptographic algorithm + * @v eiob Encrypted I/O buffer + * @ret iob Newly allocated I/O buffer with decrypted packet + * + * This method is called to decrypt a single frame. It is + * guaranteed that initialize() will have completed + * successfully before this method is called. + * + * Decryption follows the reverse of the pattern used for + * encryption: this method must copy the 802.11 header into + * the returned packet, decrypt the data stream, remove any + * encryption header or trailer, and clear the PROTECTED bit + * in the frame control header. + * + * This method should never free the passed I/O buffer. + * + * Return NULL if memory was not available for decryption, if + * a consistency or integrity check on the decrypted frame + * failed, or if the decrypted frame should not be processed + * by the network stack for any other reason. + */ + struct io_buffer * ( * decrypt ) ( struct net80211_crypto *crypto, + struct io_buffer *iob ); + + /** Length of private data requested to be allocated */ + int priv_len; + + /** Private data for the algorithm to store key and state info */ + void *priv; +}; + + +struct net80211_probe_ctx; +struct net80211_assoc_ctx; + + +/** Structure encapsulating the complete state of an 802.11 device + * + * An 802.11 device is always wrapped by a network device, and this + * network device is always pointed to by the @a netdev field. In + * general, operations should never be performed by 802.11 code using + * netdev functions directly. It is usually the case that the 802.11 + * layer might need to do some processing or bookkeeping on top of + * what the netdevice code will do. + */ +struct net80211_device +{ + /** The net_device that wraps us. */ + struct net_device *netdev; + + /** List of 802.11 devices. */ + struct list_head list; + + /** 802.11 device operations */ + struct net80211_device_operations *op; + + /** Driver private data */ + void *priv; + + /** Information about the hardware, provided to net80211_register() */ + struct net80211_hw_info *hw; + + /* ---------- Channel and rate fields ---------- */ + + /** A list of all possible channels we might use */ + struct net80211_channel channels[NET80211_MAX_CHANNELS]; + + /** The number of channels in the channels array */ + u8 nr_channels; + + /** The channel currently in use, as an index into the channels array */ + u8 channel; + + /** A list of all possible TX rates we might use + * + * Rates are in units of 100 kbps. + */ + u16 rates[NET80211_MAX_RATES]; + + /** The number of transmission rates in the rates array */ + u8 nr_rates; + + /** The rate currently in use, as an index into the rates array */ + u8 rate; + + /** The rate to use for RTS/CTS transmissions + * + * This is always the fastest basic rate that is not faster + * than the data rate in use. Also an index into the rates array. + */ + u8 rtscts_rate; + + /** Bitmask of basic rates + * + * If bit N is set in this value, with the LSB considered to + * be bit 0, then rate N in the rates array is a "basic" rate. + * + * We don't decide which rates are "basic"; our AP does, and + * we respect its wishes. We need to be able to identify basic + * rates in order to calculate the duration of a CTS packet + * used for 802.11 g/b interoperability. + */ + u32 basic_rates; + + /* ---------- Association fields ---------- */ + + /** The asynchronous association process. + * + * When an 802.11 netdev is opened, or when the user changes + * the SSID setting on an open 802.11 device, an + * autoassociation task is started by net80211_autoassocate() + * to associate with the new best network. The association is + * asynchronous, but no packets can be transmitted until it is + * complete. If it is successful, the wrapping net_device is + * set as "link up". If it fails, @c assoc_rc will be set with + * an error indication. + */ + struct process proc_assoc; + + /** Network with which we are associating + * + * This will be NULL when we are not actively in the process + * of associating with a network we have already successfully + * probed for. + */ + struct net80211_wlan *associating; + + /** Context for the association process + * + * This is a probe_ctx if the @c PROBED flag is not set in @c + * state, and an assoc_ctx otherwise. + */ + union { + struct net80211_probe_ctx *probe; + struct net80211_assoc_ctx *assoc; + } ctx; + + /** State of our association to the network + * + * Since the association process happens asynchronously, it's + * necessary to have some channel of communication so the + * driver can say "I got an association reply and we're OK" or + * similar. This variable provides that link. It is a bitmask + * of any of NET80211_PROBED, NET80211_AUTHENTICATED, + * NET80211_ASSOCIATED, NET80211_CRYPTO_SYNCED to indicate how + * far along in associating we are; NET80211_WORKING if the + * association task is running; and NET80211_WAITING if a + * packet has been sent that we're waiting for a reply to. We + * can only be crypto-synced if we're associated, we can + * only be associated if we're authenticated, we can only be + * authenticated if we've probed. + * + * If an association process fails (that is, we receive a + * packet with an error indication), the error code is copied + * into bits 6-0 of this variable and bit 7 is set to specify + * what type of error code it is. An AP can provide either a + * "status code" (0-51 are defined) explaining why it refused + * an association immediately, or a "reason code" (0-45 are + * defined) explaining why it canceled an association after it + * had originally OK'ed it. Status and reason codes serve + * similar functions, but they use separate error message + * tables. A gPXE-formatted return status code (negative) is + * placed in @c assoc_rc. + * + * If the failure to associate is indicated by a status code, + * the NET80211_IS_REASON bit will be clear; if it is + * indicated by a reason code, the bit will be set. If we were + * successful, both zero status and zero reason mean success, + * so there is no ambiguity. + * + * To prevent association when opening the device, user code + * can set the NET80211_NO_ASSOC bit. The final bit in this + * variable, NET80211_AUTO_SSID, is used to remember whether + * we picked our SSID through automated probing as opposed to + * user specification; the distinction becomes relevant in the + * settings applicator. + */ + u16 state; + + /** Return status code associated with @c state */ + int assoc_rc; + + /* ---------- Parameters of currently associated network ---------- */ + + /** 802.11 cryptographic algorithm for our current network + * + * For an open network, this will be set to NULL. + */ + struct net80211_crypto *crypto; + + /** MAC address of the access point most recently associated */ + u8 bssid[ETH_ALEN]; + + /** SSID of the access point we are or will be associated with + * + * Although the SSID field in 802.11 packets is generally not + * NUL-terminated, here and in net80211_wlan we add a NUL for + * convenience. + */ + char essid[IEEE80211_MAX_SSID_LEN+1]; + + /** Association ID given to us by the AP */ + u16 aid; + + /** TSFT value for last beacon received, microseconds */ + u64 last_beacon_timestamp; + + /** Time between AP sending beacons, microseconds */ + u32 tx_beacon_interval; + + /** Smoothed average time between beacons, microseconds */ + u32 rx_beacon_interval; + + /* ---------- Physical layer information ---------- */ + + /** Physical layer options + * + * These control the use of CTS protection, short preambles, + * and short-slot operation. + */ + int phy_flags; + + /** Signal strength of last received packet */ + int last_signal; + + /** Rate control state */ + struct rc80211_ctx *rctl; + + /* ---------- Packet handling state ---------- */ + + /** Fragment reassembly state */ + struct net80211_frag_cache frags[NET80211_NR_CONCURRENT_FRAGS]; + + /** The sequence number of the last packet we sent */ + u16 last_tx_seqnr; + + /** Packet duplication elimination state + * + * We are only required to handle immediate duplicates for + * each direct sender, and since we can only have one direct + * sender (the AP), we need only keep the sequence control + * field from the most recent packet we've received. Thus, + * this field stores the last sequence control field we've + * received for a packet from the AP. + */ + u16 last_rx_seq; + + /** RX management packet queue + * + * Sometimes we want to keep probe, beacon, and action packets + * that we receive, such as when we're scanning for networks. + * Ordinarily we drop them because they are sent at a large + * volume (ten beacons per second per AP, broadcast) and we + * have no need of them except when we're scanning. + * + * When keep_mgmt is TRUE, received probe, beacon, and action + * management packets will be stored in this queue. + */ + struct list_head mgmt_queue; + + /** RX management packet info queue + * + * We need to keep track of the signal strength for management + * packets we're keeping, because that provides the only way + * to distinguish between multiple APs for the same network. + * Since we can't extend io_buffer to store signal, this field + * heads a linked list of "RX packet info" structures that + * contain that signal strength field. Its entries always + * parallel the entries in mgmt_queue, because the two queues + * are always added to or removed from in parallel. + */ + struct list_head mgmt_info_queue; + + /** Whether to store management packets + * + * Received beacon, probe, and action packets will be added to + * mgmt_queue (and their signal strengths added to + * mgmt_info_queue) only when this variable is TRUE. It should + * be set by net80211_keep_mgmt() (which returns the old + * value) only when calling code is prepared to poll the + * management queue frequently, because packets will otherwise + * pile up and exhaust memory. + */ + int keep_mgmt; +}; + +/** Structure representing a probed network. + * + * This is returned from the net80211_probe_finish functions and + * passed to the low-level association functions. At least essid, + * bssid, channel, beacon, and security must be filled in if you want + * to build this structure manually. + */ +struct net80211_wlan +{ + /** The human-readable ESSID (network name) + * + * Although the 802.11 SSID field is generally not + * NUL-terminated, the gPXE code adds an extra NUL (and + * expects one in this structure) for convenience. + */ + char essid[IEEE80211_MAX_SSID_LEN+1]; + + /** MAC address of the strongest-signal access point for this ESSID */ + u8 bssid[ETH_ALEN]; + + /** Signal strength of beacon frame from that access point */ + int signal; + + /** The channel on which that access point communicates + * + * This is a raw channel number (net80211_channel::channel_nr), + * so that it will not be affected by reconfiguration of the + * device channels array. + */ + int channel; + + /** The complete beacon or probe-response frame received */ + struct io_buffer *beacon; + + /** Security handshaking method used on the network */ + enum net80211_security_proto handshaking; + + /** Cryptographic algorithm used on the network */ + enum net80211_crypto_alg crypto; + + /** Link to allow chaining multiple structures into a list to + be returned from net80211_probe_finish_all(). */ + struct list_head list; +}; + + +/** + * @defgroup net80211_probe 802.11 network location API + * @{ + */ +int net80211_prepare_probe ( struct net80211_device *dev, int band, + int active ); +struct net80211_probe_ctx * net80211_probe_start ( struct net80211_device *dev, + const char *essid, + int active ); +int net80211_probe_step ( struct net80211_probe_ctx *ctx ); +struct net80211_wlan * +net80211_probe_finish_best ( struct net80211_probe_ctx *ctx ); +struct list_head *net80211_probe_finish_all ( struct net80211_probe_ctx *ctx ); + +void net80211_free_wlan ( struct net80211_wlan *wlan ); +void net80211_free_wlanlist ( struct list_head *list ); +/** @} */ + + +/** + * @defgroup net80211_mgmt 802.11 network management API + * @{ + */ +struct net80211_device * net80211_get ( struct net_device *netdev ); +void net80211_autoassociate ( struct net80211_device *dev ); + +int net80211_change_channel ( struct net80211_device *dev, int channel ); +void net80211_set_rate_idx ( struct net80211_device *dev, int rate ); + +int net80211_keep_mgmt ( struct net80211_device *dev, int enable ); +struct io_buffer * net80211_mgmt_dequeue ( struct net80211_device *dev, + int *signal ); +int net80211_tx_mgmt ( struct net80211_device *dev, u16 fc, + u8 bssid[ETH_ALEN], struct io_buffer *iob ); +/** @} */ + + +/** + * @defgroup net80211_assoc 802.11 network association API + * @{ + */ +int net80211_prepare_assoc ( struct net80211_device *dev, + struct net80211_wlan *wlan ); +int net80211_send_auth ( struct net80211_device *dev, + struct net80211_wlan *wlan, int method ); +int net80211_send_assoc ( struct net80211_device *dev, + struct net80211_wlan *wlan ); +/** @} */ + + +/** + * @defgroup net80211_driver 802.11 driver interface API + * @{ + */ +struct net80211_device *net80211_alloc ( size_t priv_size ); +int net80211_register ( struct net80211_device *dev, + struct net80211_device_operations *ops, + struct net80211_hw_info *hw ); +void net80211_rx ( struct net80211_device *dev, struct io_buffer *iob, + int signal, u16 rate ); +void net80211_rx_err ( struct net80211_device *dev, + struct io_buffer *iob, int rc ); +void net80211_tx_complete ( struct net80211_device *dev, + struct io_buffer *iob, int retries, int rc ); +void net80211_unregister ( struct net80211_device *dev ); +void net80211_free ( struct net80211_device *dev ); +/** @} */ + + +#endif diff --git a/src/include/gpxe/rc80211.h b/src/include/gpxe/rc80211.h new file mode 100644 index 00000000..0856896c --- /dev/null +++ b/src/include/gpxe/rc80211.h @@ -0,0 +1,19 @@ +#ifndef _GPXE_RC80211_H +#define _GPXE_RC80211_H + +/** @file + * + * Rate-control algorithm prototype for 802.11. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +struct net80211_device; +struct rc80211_ctx; + +struct rc80211_ctx * rc80211_init ( struct net80211_device *dev ); +void rc80211_update_tx ( struct net80211_device *dev, int retries, int rc ); +void rc80211_update_rx ( struct net80211_device *dev, int retry, u16 rate ); +void rc80211_free ( struct rc80211_ctx *ctx ); + +#endif /* _GPXE_RC80211_H */ diff --git a/src/net/80211/net80211.c b/src/net/80211/net80211.c new file mode 100644 index 00000000..7d10aaa2 --- /dev/null +++ b/src/net/80211/net80211.c @@ -0,0 +1,2595 @@ +/* + * The gPXE 802.11 MAC layer. + * + * Copyright (c) 2009 Joshua Oreman . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * 802.11 device management + */ + +/* 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. + */ + +/** Make return status code from 802.11 status code */ +#define E80211_STATUS( stat ) ( ((stat & 0x20)? EHOSTUNREACH : ECONNREFUSED) \ + | ((stat & 0x1f) << 8) ) + +/** Make return status code from 802.11 reason code */ +#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 ); + +/** Set of device operations that does nothing */ +static struct net80211_device_operations net80211_null_ops; + +/** Information associated with a received management packet + * + * This is used to keep beacon signal strengths in a parallel queue to + * the beacons themselves. + */ +struct net80211_rx_info { + int signal; + 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; +}; + +/** + * @defgroup net80211_netdev Network device interface functions + * @{ + */ +static int net80211_netdev_open ( struct net_device *netdev ); +static void net80211_netdev_close ( struct net_device *netdev ); +static int net80211_netdev_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ); +static void net80211_netdev_poll ( struct net_device *netdev ); +static void net80211_netdev_irq ( struct net_device *netdev, int enable ); +/** @} */ + +/** + * @defgroup net80211_linklayer 802.11 link-layer protocol functions + * @{ + */ +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, + const void *ll_source, uint16_t net_proto ); +static int net80211_ll_pull ( struct net_device *netdev, + struct io_buffer *iobuf, const void **ll_dest, + const void **ll_source, uint16_t * net_proto ); +static int net80211_ll_mc_hash ( unsigned int af, const void *net_addr, + void *ll_addr ); +/** @} */ + +/** + * @defgroup net80211_help 802.11 helper functions + * @{ + */ +static void net80211_add_channels ( struct net80211_device *dev, int start, + int len, int txpower ); +static void net80211_set_rtscts_rate ( struct net80211_device *dev ); +static int net80211_process_capab ( struct net80211_device *dev, + u16 capab ); +static int net80211_process_ie ( struct net80211_device *dev, + union ieee80211_ie *ie, void *ie_end ); +static union ieee80211_ie * +net80211_marshal_request_info ( struct net80211_device *dev, + union ieee80211_ie *ie ); +/** @} */ + +/** + * @defgroup net80211_assoc_ll 802.11 association handling functions + * @{ + */ +static void net80211_step_associate ( struct process *proc ); +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 ); +/** @} */ + +/** + * @defgroup net80211_frag 802.11 fragment handling functions + * @{ + */ +static void net80211_free_frags ( struct net80211_device *dev, int fcid ); +static struct io_buffer *net80211_accum_frags ( struct net80211_device *dev, + int fcid, int nfrags, int size ); +static void net80211_rx_frag ( struct net80211_device *dev, + struct io_buffer *iob, int signal ); +/** @} */ + +/** + * @defgroup net80211_settings 802.11 settings handlers + * @{ + */ +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, +}; + +/** The network name to associate with + * + * If this is blank, we scan for all networks 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_active_setting __setting = { + .name = "active-scan", + .description = "Use an active scan during 802.11 association", + .type = &setting_type_int8, +}; + +/** @} */ + + +/* ---------- net_device wrapper ---------- */ + +/** + * Open 802.11 device and start association + * + * @v netdev Wrapping network device + * @ret rc Return status code + * + * This sets up a default conservative set of channels for probing, + * 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. + */ +static int net80211_netdev_open ( struct net_device *netdev ) +{ + struct net80211_device *dev = netdev->priv; + int rc = 0; + + if ( dev->op == &net80211_null_ops ) + return -EFAULT; + + if ( dev->op->open ) + rc = dev->op->open ( dev ); + + if ( rc < 0 ) + return rc; + + if ( ! ( dev->state & NET80211_NO_ASSOC ) ) + net80211_autoassociate ( dev ); + + return 0; +} + +/** + * Close 802.11 device + * + * @v netdev Wrapping network device. + * + * If the association task is running, this will stop it. + */ +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; + + if ( dev->op->close ) + dev->op->close ( dev ); +} + +/** + * Transmit packet on 802.11 device + * + * @v netdev Wrapping network device + * @v iobuf I/O buffer + * @ret rc Return status code + * + * If encryption is enabled for the currently associated network, the + * packet will be encrypted prior to transmission. + */ +static int net80211_netdev_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) +{ + struct net80211_device *dev = netdev->priv; + int rc = -ENOSYS; + + if ( dev->crypto ) { + struct io_buffer *niob = dev->crypto->encrypt ( dev->crypto, + iobuf ); + if ( ! niob ) + return -ENOMEM; /* only reason encryption could fail */ + + free_iob ( iobuf ); + iobuf = niob; + } + + if ( dev->op->transmit ) + rc = dev->op->transmit ( dev, iobuf ); + + return rc; +} + +/** + * Poll 802.11 device for received packets and completed transmissions + * + * @v netdev Wrapping network device + */ +static void net80211_netdev_poll ( struct net_device *netdev ) +{ + struct net80211_device *dev = netdev->priv; + + if ( dev->op->poll ) + dev->op->poll ( dev ); +} + +/** + * Enable or disable interrupts for 802.11 device + * + * @v netdev Wrapping network device + * @v enable Whether to enable interrupts + */ +static void net80211_netdev_irq ( struct net_device *netdev, int enable ) +{ + struct net80211_device *dev = netdev->priv; + + if ( dev->op->irq ) + dev->op->irq ( dev, enable ); +} + +/** Network device operations for a wrapped 802.11 device */ +static struct net_device_operations net80211_netdev_ops = { + .open = net80211_netdev_open, + .close = net80211_netdev_close, + .transmit = net80211_netdev_transmit, + .poll = net80211_netdev_poll, + .irq = net80211_netdev_irq, +}; + + +/* ---------- 802.11 link-layer protocol ---------- */ + +/** 802.11 broadcast MAC address */ +static u8 net80211_ll_broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +/** + * Determine whether a transmission rate uses ERP/OFDM + * + * @v rate Rate in 100 kbps units + * @ret is_erp TRUE if the rate is an ERP/OFDM rate + * + * 802.11b supports rates of 1.0, 2.0, 5.5, and 11.0 Mbps; any other + * rate than these on the 2.4GHz spectrum is an ERP (802.11g) rate. + */ +static inline int net80211_rate_is_erp ( u16 rate ) +{ + if ( rate == 10 || rate == 20 || rate == 55 || rate == 110 ) + return 0; + return 1; +} + + +/** + * Calculate one frame's contribution to 802.11 duration field + * + * @v dev 802.11 device + * @v bytes Amount of data to calculate duration for + * @ret dur Duration field in microseconds + * + * To avoid multiple stations attempting to transmit at once, 802.11 + * provides that every packet shall include a duration field + * specifying a length of time for which the wireless medium will be + * reserved after it is transmitted. The duration is measured in + * microseconds and is calculated with respect to the current + * physical-layer parameters of the 802.11 device. + * + * For an unfragmented data or management frame, or the last fragment + * of a fragmented frame, the duration captures only the 10 data bytes + * of one ACK; call once with bytes = 10. + * + * For a fragment of a data or management rame that will be followed + * by more fragments, the duration captures an ACK, the following + * fragment, and its ACK; add the results of three calls, two with + * bytes = 10 and one with bytes set to the next fragment's size. + * + * For an RTS control frame, the duration captures the responding CTS, + * the frame being sent, and its ACK; add the results of three calls, + * two with bytes = 10 and one with bytes set to the next frame's size + * (assuming unfragmented). + * + * For a CTS-to-self control frame, the duration captures the frame + * being protected and its ACK; add the results of two calls, one with + * bytes = 10 and one with bytes set to the next frame's size. + * + * No other frame types are currently supported by gPXE. + */ +static u16 net80211_duration ( struct net80211_device *dev, int bytes ) +{ + struct net80211_channel *chan = &dev->channels[dev->channel]; + u16 rate = dev->rates[dev->rate]; + u32 kbps = rate * 100; + + if ( chan->band == NET80211_BAND_5GHZ || net80211_rate_is_erp ( rate ) ) { + /* OFDM encoding (802.11a/g) */ + int bits_per_symbol = ( kbps * 4 ) / 1000; /* 4us/symbol */ + int bits = 22 + ( bytes << 3 ); /* 22-bit PLCP */ + int symbols = ( bits + bits_per_symbol - 1 ) / bits_per_symbol; + + return 16 + 20 + ( symbols * 4 ); /* 16us SIFS, 20us preamble */ + } else { + /* CCK encoding (802.11b) */ + int phy_time = 144 + 48; /* preamble + PLCP */ + int bits = bytes << 3; + int data_time = ( bits * 1000 + kbps - 1 ) / kbps; + + if ( dev->phy_flags & NET80211_PHY_USE_SHORT_PREAMBLE ) + phy_time >>= 1; + + return 10 + phy_time + data_time; /* 10us SIFS */ + } +} + +/** + * Add 802.11 link-layer header + * + * @v netdev Wrapping network device + * @v iobuf I/O buffer + * @v ll_dest Link-layer destination address + * @v ll_source Link-layer source address + * @v net_proto Network-layer protocol, in network byte order + * @ret rc Return status code + * + * This adds both the 802.11 frame header and the 802.2 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, + const void *ll_source, uint16_t net_proto ) +{ + struct net80211_device *dev = netdev->priv; + struct ieee80211_frame *hdr = iob_push ( iobuf, + IEEE80211_LLC_HEADER_LEN + + IEEE80211_TYP_FRAME_HEADER_LEN ); + 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 ( ! netdev_link_ok ( netdev ) ) { + if ( dev->assoc_rc ) + return dev->assoc_rc; + return -ENETUNREACH; + } + + hdr->fc = IEEE80211_THIS_VERSION | IEEE80211_TYPE_DATA | + IEEE80211_STYPE_DATA | IEEE80211_FC_TODS; + + /* We don't send fragmented frames, so duration is the time + for an SIFS + 10-byte ACK. */ + hdr->duration = net80211_duration ( dev, 10 ); + + memcpy ( hdr->addr1, dev->bssid, ETH_ALEN ); + memcpy ( hdr->addr2, ll_source, ETH_ALEN ); + memcpy ( hdr->addr3, ll_dest, ETH_ALEN ); + + hdr->seq = IEEE80211_MAKESEQ ( ++dev->last_tx_seqnr, 0 ); + + lhdr->dsap = IEEE80211_LLC_DSAP; + lhdr->ssap = IEEE80211_LLC_SSAP; + lhdr->ctrl = IEEE80211_LLC_CTRL; + memset ( lhdr->oui, 0x00, 3 ); + lhdr->ethertype = net_proto; + + return 0; +} + +/** + * Remove 802.11 link-layer header + * + * @v netdev Wrapping network device + * @v iobuf I/O buffer + * @ret ll_dest Link-layer destination address + * @ret ll_source Link-layer source + * @ret net_proto Network-layer protocol, in network byte order + * @ret rc Return status code + * + * This expects and removes both the 802.11 frame header and the 802.2 + * LLC/SNAP header that are used on data packets. + */ +static int net80211_ll_pull ( struct net_device *netdev __unused, + struct io_buffer *iobuf, + const void **ll_dest, const void **ll_source, + uint16_t * net_proto ) +{ + struct ieee80211_frame *hdr = iobuf->data; + struct ieee80211_llc_snap_header *lhdr = + ( void * ) hdr + IEEE80211_TYP_FRAME_HEADER_LEN; + + /* Bunch of sanity checks */ + if ( iob_len ( iobuf ) < IEEE80211_TYP_FRAME_HEADER_LEN + + IEEE80211_LLC_HEADER_LEN ) { + DBGC ( netdev->priv, "802.11 %p packet too short (%zd bytes)\n", + netdev->priv, iob_len ( iobuf ) ); + return -EINVAL_PKT_TOO_SHORT; + } + + if ( ( hdr->fc & IEEE80211_FC_VERSION ) != IEEE80211_THIS_VERSION ) { + DBGC ( netdev->priv, "802.11 %p packet invalid version %04x\n", + netdev->priv, hdr->fc & IEEE80211_FC_VERSION ); + return -EINVAL_PKT_VERSION; + } + + if ( ( hdr->fc & IEEE80211_FC_TYPE ) != IEEE80211_TYPE_DATA || + ( hdr->fc & IEEE80211_FC_SUBTYPE ) != IEEE80211_STYPE_DATA ) { + DBGC ( netdev->priv, "802.11 %p packet not data/data (fc=%04x)\n", + netdev->priv, hdr->fc ); + return -EINVAL_PKT_NOT_DATA; + } + + if ( ( hdr->fc & ( IEEE80211_FC_TODS | IEEE80211_FC_FROMDS ) ) != + IEEE80211_FC_FROMDS ) { + DBGC ( netdev->priv, "802.11 %p packet not from DS (fc=%04x)\n", + netdev->priv, hdr->fc ); + return -EINVAL_PKT_NOT_FROMDS; + } + + if ( lhdr->dsap != IEEE80211_LLC_DSAP || lhdr->ssap != IEEE80211_LLC_SSAP || + lhdr->ctrl != IEEE80211_LLC_CTRL || lhdr->oui[0] || lhdr->oui[1] || + lhdr->oui[2] ) { + DBGC ( netdev->priv, "802.11 %p LLC header is not plain EtherType " + "encapsulator: %02x->%02x [%02x] %02x:%02x:%02x %04x\n", + netdev->priv, lhdr->dsap, lhdr->ssap, lhdr->ctrl, + lhdr->oui[0], lhdr->oui[1], lhdr->oui[2], lhdr->ethertype ); + return -EINVAL_PKT_LLC_HEADER; + } + + iob_pull ( iobuf, sizeof ( *hdr ) + sizeof ( *lhdr ) ); + + *ll_dest = hdr->addr1; + *ll_source = hdr->addr3; + *net_proto = lhdr->ethertype; + return 0; +} + +/** + * Hash 802.11 multicast address + * + * @v af Address family + * @v net_addr Network-layer address + * @ret ll_addr Filled link-layer address + * @ret rc Return status code + * + * Currently unimplemented. + */ +static int net80211_ll_mc_hash ( unsigned int af __unused, + const void *net_addr __unused, + void *ll_addr __unused ) +{ + return -ENOTSUP; +} + +/** 802.11 link-layer protocol */ +static struct ll_protocol net80211_ll_protocol __ll_protocol = { + .name = "802.11", + .push = net80211_ll_push, + .pull = net80211_ll_pull, + .ntoa = eth_ntoa, + .mc_hash = net80211_ll_mc_hash, + .ll_proto = htons ( ARPHRD_ETHER ), /* "encapsulated Ethernet" */ + .ll_addr_len = ETH_ALEN, + .ll_header_len = IEEE80211_TYP_FRAME_HEADER_LEN + + IEEE80211_LLC_HEADER_LEN, +}; + + +/* ---------- 802.11 network management API ---------- */ + +/** + * Get 802.11 device from wrapping network device + * + * @v netdev Wrapping network device + * @ret dev 802.11 device wrapped by network device, or NULL + * + * Returns NULL if the network device does not wrap an 802.11 device. + */ +struct net80211_device * net80211_get ( struct net_device *netdev ) +{ + struct net80211_device *dev; + + list_for_each_entry ( dev, &net80211_devices, list ) { + if ( netdev->priv == dev ) + return netdev->priv; + } + + return NULL; +} + +/** + * Set state of 802.11 device keeping management frames + * + * @v dev 802.11 device + * @v enable Whether to keep management frames + * @ret oldenab Whether management frames were enabled before this call + * + * If enable is TRUE, beacon, probe, and action frames will be kept + * and may be retrieved by calling net80211_mgmt_dequeue(). + */ +int net80211_keep_mgmt ( struct net80211_device *dev, int enable ) +{ + int oldenab = dev->keep_mgmt; + + dev->keep_mgmt = enable; + return oldenab; +} + +/** + * Get 802.11 management frame + * + * @v dev 802.11 device + * @ret signal Signal strength of returned management frame + * @ret iob I/O buffer, or NULL if no management frame is queued + * + * Frames will only be returned by this function if + * net80211_keep_mgmt() has been previously called with enable set to + * TRUE. + * + * The calling function takes ownership of the returned I/O buffer. + */ +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; + free ( rxi ); + + list_for_each_entry ( iobuf, &dev->mgmt_queue, list ) { + list_del ( &iobuf->list ); + return iobuf; + } + assert ( 0 ); + } + + return NULL; +} + +/** + * Transmit 802.11 management frame + * + * @v dev 802.11 device + * @v fc Frame Control flags for management frame + * @v dest Destination access point + * @v iob I/O buffer + * @ret rc Return status code + * + * The @a fc argument must contain at least an IEEE 802.11 management + * subtype number (e.g. IEEE80211_STYPE_PROBE_REQ). If it contains + * IEEE80211_FC_PROTECTED, the frame will be encrypted prior to + * transmission. + * + * It is required that @a iob have at least 24 bytes of headroom + * reserved before its data start. + */ +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 = 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 */ + memcpy ( hdr->addr3, dest, ETH_ALEN ); /* BSSID */ + + if ( fc & IEEE80211_FC_PROTECTED ) { + if ( ! dev->crypto ) + return -EINVAL_CRYPTO_REQUEST; + + struct io_buffer *eiob = dev->crypto->encrypt ( dev->crypto, + iob ); + free_iob ( iob ); + iob = eiob; + } + + return netdev_tx ( dev->netdev, iob ); +} + + +/* ---------- Driver API ---------- */ + +/** + * Allocate 802.11 device + * + * @v priv_size Size of driver-private allocation area + * @ret dev Newly allocated 802.11 device + * + * This function allocates a net_device with space in its private area + * for both the net80211_device it will wrap and the driver-private + * data space requested. It initializes the link-layer-specific parts + * of the net_device, and links the net80211_device to the net_device + * appropriately. + */ +struct net80211_device * net80211_alloc ( size_t priv_size ) +{ + struct net80211_device *dev; + struct net_device *netdev = + alloc_netdev ( sizeof ( *dev ) + priv_size ); + + if ( ! netdev ) + return NULL; + + netdev->ll_protocol = &net80211_ll_protocol; + netdev->ll_broadcast = net80211_ll_broadcast; + netdev->max_pkt_len = IEEE80211_MAX_DATA_LEN; + netdev_init ( netdev, &net80211_netdev_ops ); + + dev = netdev->priv; + dev->netdev = netdev; + dev->priv = ( u8 * ) dev + sizeof ( *dev ); + dev->op = &net80211_null_ops; + + dev->proc_assoc.step = net80211_step_associate; + INIT_LIST_HEAD ( &dev->mgmt_queue ); + INIT_LIST_HEAD ( &dev->mgmt_info_queue ); + + return dev; +} + +/** + * Register 802.11 device with network stack + * + * @v dev 802.11 device + * @v ops 802.11 device operations + * @v hw 802.11 hardware information + * + * This also registers the wrapping net_device with the higher network + * layers. + */ +int net80211_register ( struct net80211_device *dev, + struct net80211_device_operations *ops, + struct net80211_hw_info *hw ) +{ + dev->op = ops; + dev->hw = malloc ( sizeof ( *hw ) ); + if ( ! dev->hw ) + return -ENOMEM; + + memcpy ( dev->hw, hw, sizeof ( *hw ) ); + memcpy ( dev->netdev->ll_addr, hw->hwaddr, ETH_ALEN ); + + list_add_tail ( &dev->list, &net80211_devices ); + return register_netdev ( dev->netdev ); +} + +/** + * Unregister 802.11 device from network stack + * + * @v dev 802.11 device + * + * After this call, the device operations are cleared so that they + * will not be called. + */ +void net80211_unregister ( struct net80211_device *dev ) +{ + unregister_netdev ( dev->netdev ); + list_del ( &dev->list ); + dev->op = &net80211_null_ops; +} + +/** + * Free 802.11 device + * + * @v dev 802.11 device + * + * The device should be unregistered before this function is called. + */ +void net80211_free ( struct net80211_device *dev ) +{ + free ( dev->hw ); + rc80211_free ( dev->rctl ); + netdev_nullify ( dev->netdev ); + netdev_put ( dev->netdev ); +} + + +/* ---------- 802.11 network management workhorse code ---------- */ + +/** + * Set state of 802.11 device + * + * @v dev 802.11 device + * @v clear Bitmask of flags to clear + * @v set Bitmask of flags to set + * @v status Status or reason code for most recent operation + * + * If @a status represents a reason code, it should be OR'ed with + * NET80211_IS_REASON. + * + * Clearing authentication also clears association; clearing + * association also clears security handshaking state. Clearing + * association removes the link-up flag from the wrapping net_device, + * but setting it does not automatically set the flag; that is left to + * the judgment of higher-level code. + */ +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 in most cases. Since clear + and set are generally passed as constants, the body of this + function can be reduced down to a few statements by the + compiler. */ + + const int statmsk = NET80211_STATUS_MASK | NET80211_IS_REASON; + + if ( clear & NET80211_PROBED ) + clear |= NET80211_AUTHENTICATED; + + 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 & ~statmsk ) | ( status & statmsk ); + + if ( clear & NET80211_ASSOCIATED ) + 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 ); + } +} + +/** + * Add channels to 802.11 device + * + * @v dev 802.11 device + * @v start First channel number to add + * @v len Number of channels to add + * @v txpower TX power (dBm) to allow on added channels + * + * To replace the current list of channels instead of adding to it, + * set the nr_channels field of the 802.11 device to 0 before calling + * this function. + */ +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; +} + +/** + * Update 802.11 device state to reflect received capabilities field + * + * @v dev 802.11 device + * @v capab Capabilities field in beacon, probe, or association frame + * @ret rc Return status code + */ +static int net80211_process_capab ( struct net80211_device *dev, + u16 capab ) +{ + u16 old_phy = dev->phy_flags; + + if ( ( capab & ( IEEE80211_CAPAB_MANAGED | IEEE80211_CAPAB_ADHOC ) ) != + IEEE80211_CAPAB_MANAGED ) { + DBGC ( dev, "802.11 %p cannot handle IBSS network\n", dev ); + return -ENOSYS; + } + + if ( capab & IEEE80211_CAPAB_SPECTRUM_MGMT ) { + DBGC ( dev, "802.11 %p cannot handle spectrum managed " + "network\n", dev ); + return -ENOSYS; + } + + dev->phy_flags &= ~( NET80211_PHY_USE_SHORT_PREAMBLE | + NET80211_PHY_USE_SHORT_SLOT ); + + if ( capab & IEEE80211_CAPAB_SHORT_PMBL ) + dev->phy_flags |= NET80211_PHY_USE_SHORT_PREAMBLE; + + if ( capab & IEEE80211_CAPAB_SHORT_SLOT ) + dev->phy_flags |= NET80211_PHY_USE_SHORT_SLOT; + + if ( old_phy != dev->phy_flags ) + dev->op->config ( dev, NET80211_CFG_PHY_PARAMS ); + + return 0; +} + +/** + * Update 802.11 device state to reflect received information elements + * + * @v dev 802.11 device + * @v ie Pointer to first information element + * @v ie_end Pointer to tail of packet I/O buffer + * @ret rc Return status code + */ +static int net80211_process_ie ( struct net80211_device *dev, + union ieee80211_ie *ie, void *ie_end ) +{ + u16 old_rate = dev->rates[dev->rate]; + u16 old_phy = dev->phy_flags; + int have_rates = 0, i; + int ds_channel = 0; + int changed = 0; + + if ( ( void * ) ie >= ie_end ) + return 0; + + for ( ; ie; ie = ieee80211_next_ie ( ie, ie_end ) ) { + 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 ); + + dev->rates[dev->nr_rates++] = rate; + } + + break; + + case IEEE80211_IE_DS_PARAM: + if ( dev->channel < dev->nr_channels && ds_channel == + dev->channels[dev->channel].channel_nr ) + break; + ds_channel = ie->ds_param.current_channel; + net80211_change_channel ( dev, ds_channel ); + break; + + case IEEE80211_IE_COUNTRY: + dev->nr_channels = 0; + + DBGC ( dev, "802.11 %p setting country regulations " + "for %c%c\n", dev, ie->country.name[0], + ie->country.name[1] ); + for ( i = 0; i < ( ie->len - 3 ) / 3; i++ ) { + union ieee80211_ie_country_triplet *t = + &ie->country.triplet[i]; + if ( t->first > 200 ) { + DBGC ( dev, "802.11 %p ignoring regulatory " + "extension information\n", dev ); + } else { + net80211_add_channels ( dev, + t->band.first_channel, + t->band.nr_channels, + t->band.max_txpower ); + } + } + break; + + case IEEE80211_IE_ERP_INFO: + dev->phy_flags &= ~( NET80211_PHY_USE_PROTECTION | + NET80211_PHY_USE_SHORT_PREAMBLE ); + if ( ie->erp_info & IEEE80211_ERP_USE_PROTECTION ) + dev->phy_flags |= NET80211_PHY_USE_PROTECTION; + if ( ! ( ie->erp_info & IEEE80211_ERP_BARKER_LONG ) ) + dev->phy_flags |= NET80211_PHY_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[j] == + 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; + + /* Sort available rates - sorted subclumps tend to already + exist, so insertion sort works well. */ + for ( i = 1; i < dev->nr_rates; i++ ) { + u16 rate = dev->rates[i]; + + for ( j = i - 1; j >= 0 && dev->rates[j] >= rate; j-- ) + dev->rates[j + 1] = dev->rates[j]; + dev->rates[j + 1] = rate; + } + + net80211_set_rtscts_rate ( dev ); + + if ( dev->rates[dev->rate] != old_rate ) + changed |= NET80211_CFG_RATE; + } + + if ( dev->hw->flags & NET80211_HW_NO_SHORT_PREAMBLE ) + dev->phy_flags &= ~NET80211_PHY_USE_SHORT_PREAMBLE; + if ( dev->hw->flags & NET80211_HW_NO_SHORT_SLOT ) + dev->phy_flags &= ~NET80211_PHY_USE_SHORT_SLOT; + + if ( old_phy != dev->phy_flags ) + changed |= NET80211_CFG_PHY_PARAMS; + + if ( changed ) + dev->op->config ( dev, changed ); + + return 0; +} + +/** + * Create information elements for outgoing probe or association packet + * + * @v dev 802.11 device + * @v ie Pointer to start of information element area + * @ret next_ie Pointer to first byte after added information elements + */ +static union ieee80211_ie * +net80211_marshal_request_info ( struct net80211_device *dev, + union ieee80211_ie *ie ) +{ + int i; + + ie->id = IEEE80211_IE_SSID; + ie->len = strlen ( dev->essid ); + memcpy ( ie->ssid, dev->essid, ie->len ); + + ie = ieee80211_next_ie ( ie, NULL ); + + ie->id = IEEE80211_IE_RATES; + ie->len = dev->nr_rates; + for ( i = 0; i < ie->len; i++ ) { + ie->rates[i] = dev->rates[i] / 5; + if ( dev->basic_rates & ( 1 << i ) ) + ie->rates[i] |= 0x80; + } + + if ( ie->len > 8 ) { + /* 802.11 requires we use an Extended Basic Rates IE + for the rates beyond the eighth. */ + int rates = ie->len; + + memmove ( ( void * ) ie + 2 + 8 + 2, ( void * ) ie + 2 + 8, + rates - 8 ); + ie->len = 8; + + ie = ieee80211_next_ie ( ie, NULL ); + + ie->id = IEEE80211_IE_EXT_RATES; + ie->len = rates - 8; + } + + ie = ieee80211_next_ie ( ie, NULL ); + + return ie; +} + +/** Seconds to wait after finding a network, to possibly find better APs for it + * + * This is used when a specific SSID to scan for is specified. + */ +#define NET80211_PROBE_GATHER 1 + +/** Seconds to wait after finding a network, to possibly find other networks + * + * 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 + +/** + * 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 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_probe_ctx * net80211_probe_start ( struct net80211_device *dev, + const char *essid, + int active ) +{ + struct net80211_probe_ctx *ctx = zalloc ( sizeof ( *ctx ) ); + + if ( ! ctx ) + return NULL; + + assert ( dev->netdev->state & NETDEV_OPEN ); + + 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 ( active ) { + struct ieee80211_probe_req *probe_req; + union ieee80211_ie *ie; + + ctx->probe = alloc_iob ( 128 ); + iob_reserve ( ctx->probe, IEEE80211_TYP_FRAME_HEADER_LEN ); + probe_req = ctx->probe->data; + + 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; + + ie = ieee80211_next_ie ( ie, NULL ); + + iob_put ( ctx->probe, ( void * ) ie - ctx->probe->data ); + } + + 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 ); + + 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; + } + } + } + + /* 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; + + if ( type != IEEE80211_STYPE_BEACON && + type != IEEE80211_STYPE_PROBE_RESP ) { + DBGC2 ( dev, "802.11 %p probe: non-beacon\n", dev ); + goto drop; + } + + if ( ( void * ) beacon->info_element >= iob->tail ) { + DBGC ( dev, "802.11 %p probe: beacon with no IEs\n", + dev ); + goto drop; + } + + ie = beacon->info_element; + while ( ie && ie->id != IEEE80211_IE_SSID ) + ie = ieee80211_next_ie ( ie, iob->tail ); + + if ( ! ie ) { + DBGC ( dev, "802.11 %p probe: beacon with no SSID\n", + dev ); + goto drop; + } + + 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, ssid ); + goto drop; + } + + /* 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; + } + + goto fill; + } + + /* 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->channel = dev->channels[dev->channel].channel_nr; + + /* 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; + + 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 ); + } + + return 0; +} + + +/** + * 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; + + if ( ! ctx ) + return NULL; + + 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; + + if ( ! ctx ) + return NULL; + + net80211_keep_mgmt ( ctx->dev, ctx->old_keep_mgmt ); + + if ( ctx->probe ) + free_iob ( ctx->probe ); + + free ( ctx ); + + return beacons; +} + + +/** + * Free WLAN structure + * + * @v wlan WLAN structure to free + */ +void net80211_free_wlan ( struct net80211_wlan *wlan ) +{ + if ( wlan ) { + free_iob ( wlan->beacon ); + free ( 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 ); +} + + +/** Number of ticks to wait for replies to association management frames */ +#define ASSOC_TIMEOUT TICKS_PER_SEC + +/** Number of times to try sending a particular association management frame */ +#define ASSOC_RETRIES 2 + +/** + * Step 802.11 association process + * + * @v proc Association process + */ +static 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; + + /* + * We use a sort of state machine implemented using bits in + * the dev->state variable. At each call, we take the + * logically first step that has not yet succeeded; either it + * has not been tried yet, it's being retried, or it failed. + * If it failed, we return an error indication; otherwise we + * perform the step. If it succeeds, RX handling code will set + * the appropriate status bit for us. + * + * Probe works a bit differently, since we have to step it + * on every call instead of waiting for a packet to arrive + * that will set the completion bit for us. + */ + + /* If we're waiting for a reply, check for timeout condition */ + if ( dev->state & NET80211_WAITING ) { + /* Sanity check */ + if ( ! dev->associating ) + return; + + if ( currticks() - dev->ctx.assoc->last_packet > ASSOC_TIMEOUT ) { + /* Timed out - fail if too many retries, or retry */ + dev->ctx.assoc->times_tried++; + if ( ++dev->ctx.assoc->times_tried > ASSOC_RETRIES ) { + rc = -ETIMEDOUT; + goto fail; + } + } else { + /* Didn't time out - let it keep going */ + return; + } + } else { + if ( dev->state & NET80211_PROBED ) + dev->ctx.assoc->times_tried = 0; + } + + if ( ! ( dev->state & NET80211_PROBED ) ) { + /* state: probe */ + + if ( ! dev->ctx.probe ) { + /* start probe */ + int active = fetch_intz_setting ( NULL, + &net80211_active_setting ); + int band = dev->hw->bands; + + if ( active ) + band &= ~NET80211_BAND_5GHZ; + + rc = net80211_prepare_probe ( dev, band, active ); + if ( rc ) + goto fail; + + 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 ) { + if ( rc > 0 ) /* "successful" probe found nothing */ + rc = -ETIMEDOUT; + goto fail; + } + + /* If we probed using a broadcast SSID, record that + fact for the settings applicator before we clobber + it with the specific SSID we've chosen. */ + if ( ! dev->essid[0] ) + dev->state |= NET80211_AUTO_SSID; + + DBGC ( dev, "802.11 %p found network %s (%s)\n", dev, + dev->associating->essid, + eth_ntoa ( dev->associating->bssid ) ); + + 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; + } + + /* Record time of sending the packet we're about to send, for timeout */ + dev->ctx.assoc->last_packet = currticks(); + + if ( ! ( dev->state & NET80211_AUTHENTICATED ) ) { + /* state: prepare and authenticate */ + + 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? */ + dev->ctx.assoc->method = + IEEE80211_AUTH_SHARED_KEY; + } else { + goto fail; + } + } + + DBGC ( dev, "802.11 %p authenticating with method %d\n", dev, + dev->ctx.assoc->method ); + + rc = net80211_prepare_assoc ( dev, dev->associating ); + if ( rc ) + goto fail; + + rc = net80211_send_auth ( dev, dev->associating, + dev->ctx.assoc->method ); + if ( rc ) + goto fail; + + return; + } + + if ( ! ( dev->state & NET80211_ASSOCIATED ) ) { + /* state: associate */ + + if ( status != IEEE80211_STATUS_SUCCESS ) + goto fail; + + DBGC ( dev, "802.11 %p associating\n", dev ); + + rc = net80211_send_assoc ( dev, dev->associating ); + if ( rc ) + goto fail; + + return; + } + + if ( ! ( dev->state & NET80211_CRYPTO_SYNCED ) ) { + /* state: crypto sync */ + DBGC ( dev, "802.11 %p security handshaking\n", dev ); + + dev->state |= NET80211_CRYPTO_SYNCED; + /* XXX need to actually do something here once we + support WPA */ + return; + } + + /* state: done! */ + netdev_link_up ( dev->netdev ); + dev->assoc_rc = 0; + dev->state &= ~NET80211_WORKING; + + free ( dev->ctx.assoc ); + dev->ctx.assoc = NULL; + + net80211_free_wlan ( dev->associating ); + dev->associating = NULL; + + dev->rctl = rc80211_init ( dev ); + + process_del ( proc ); + + DBGC ( dev, "802.11 %p associated with %s (%s)\n", dev, + dev->essid, eth_ntoa ( dev->bssid ) ); + + return; + + fail: + dev->state &= ~( NET80211_WORKING | NET80211_WAITING ); + if ( rc ) + dev->assoc_rc = rc; + + netdev_link_err ( dev->netdev, dev->assoc_rc ); + + /* We never reach here from the middle of a probe, so we don't + need to worry about freeing dev->ctx.probe. */ + + if ( dev->state & NET80211_PROBED ) { + free ( dev->ctx.assoc ); + dev->ctx.assoc = NULL; + } + + 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 ) ); + + /* Try it again: */ + net80211_autoassociate ( dev ); +} + +/** + * 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. + */ +static int net80211_check_ssid_update ( void ) +{ + struct net80211_device *dev; + char ssid[IEEE80211_MAX_SSID_LEN + 1]; + + list_for_each_entry ( dev, &net80211_devices, list ) { + if ( ! ( dev->netdev->state & NETDEV_OPEN ) ) + continue; + + fetch_string_setting ( netdev_settings ( dev->netdev ), + &net80211_ssid_setting, ssid, + IEEE80211_MAX_SSID_LEN + 1 ); + + if ( strcmp ( ssid, dev->essid ) != 0 && + ! ( ! ssid[0] && ( dev->state & NET80211_AUTO_SSID ) ) ) { + DBGC ( dev, "802.11 %p updating association: " + "%s -> %s\n", dev, dev->essid, ssid ); + net80211_autoassociate ( dev ); + } + } + + return 0; +} + +/** + * Start 802.11 association process + * + * @v dev 802.11 device + * + * If the association process is running, it will be restarted. + */ +void net80211_autoassociate ( struct net80211_device *dev ) +{ + if ( ! ( dev->state & NET80211_WORKING ) ) { + DBGC2 ( dev, "802.11 %p spawning association process\n", dev ); + process_add ( &dev->proc_assoc ); + } + + /* Clean up everything an earlier association process might + have been in the middle of using */ + if ( dev->associating ) + net80211_free_wlan ( dev->associating ); + + if ( ! ( dev->state & NET80211_PROBED ) ) + net80211_free_wlan ( + net80211_probe_finish_best ( dev->ctx.probe ) ); + else + free ( dev->ctx.assoc ); + + /* Reset to a clean state */ + fetch_string_setting ( netdev_settings ( dev->netdev ), + &net80211_ssid_setting, dev->essid, + IEEE80211_MAX_SSID_LEN + 1 ); + dev->ctx.probe = NULL; + dev->associating = NULL; + net80211_set_state ( dev, NET80211_PROBED, NET80211_WORKING, 0 ); +} + +/** + * Pick TX rate for RTS/CTS packets based on data rate + * + * @v dev 802.11 device + * + * The RTS/CTS rate is the fastest TX rate marked as "basic" that is + * not faster than the data rate. + */ +static void net80211_set_rtscts_rate ( struct net80211_device *dev ) +{ + u16 datarate = dev->rates[dev->rate]; + u16 rtsrate = 0; + int rts_idx = -1; + int i; + + for ( i = 0; i < dev->nr_rates; i++ ) { + u16 rate = dev->rates[i]; + + if ( ! ( dev->basic_rates & ( 1 << i ) ) || rate > datarate ) + continue; + + if ( rate > rtsrate ) { + rtsrate = rate; + rts_idx = i; + } + } + + /* If this is in initialization, we might not have any basic + rates; just use the first data rate in that case. */ + if ( rts_idx < 0 ) + rts_idx = 0; + + dev->rtscts_rate = rts_idx; +} + +/** + * Set data transmission rate for 802.11 device + * + * @v dev 802.11 device + * @v rate Rate to set, as index into @c dev->rates array + */ +void net80211_set_rate_idx ( struct net80211_device *dev, int rate ) +{ + assert ( dev->netdev->state & NETDEV_OPEN ); + + if ( rate >= 0 && rate < dev->nr_rates && rate != dev->rate ) { + DBGC2 ( dev, "802.11 %p changing rate from %d->%d Mbps\n", + dev, dev->rates[dev->rate] / 10, + dev->rates[rate] / 10 ); + + dev->rate = rate; + net80211_set_rtscts_rate ( dev ); + dev->op->config ( dev, NET80211_CFG_RATE ); + } +} + +/** + * Configure 802.11 device to transmit on a certain channel + * + * @v dev 802.11 device + * @v channel Channel number (1-11 for 2.4GHz) to transmit on + */ +int net80211_change_channel ( struct net80211_device *dev, int channel ) +{ + int i, oldchan = dev->channel; + + assert ( dev->netdev->state & NETDEV_OPEN ); + + for ( i = 0; i < dev->nr_channels; i++ ) { + if ( dev->channels[i].channel_nr == channel ) { + dev->channel = i; + break; + } + } + + if ( i == dev->nr_channels ) + return -ENOENT; + + if ( i != oldchan ) + return dev->op->config ( dev, NET80211_CFG_CHANNEL ); + + return 0; +} + +/** + * Prepare 802.11 device channel and rate set for scanning + * + * @v dev 802.11 device + * @v band RF band(s) on which to prepare for scanning + * @v active Whether the scanning will be active + * @ret rc Return status code + */ +int net80211_prepare_probe ( struct net80211_device *dev, int band, + int active ) +{ + assert ( dev->netdev->state & NETDEV_OPEN ); + + if ( active && band != NET80211_BAND_2GHZ ) { + DBGC ( dev, "802.11 %p cannot perform active scanning on " + "5GHz band\n", dev ); + return -EINVAL_ACTIVE_SCAN; + } + + if ( band == 0 ) { + /* This can happen for a 5GHz-only card with 5GHz + scanning masked out by an active request. */ + DBGC ( dev, "802.11 %p asked to prepare for scanning nothing\n", + dev ); + return -EINVAL_ACTIVE_SCAN; + } + + 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 ); + } + + /* Use channel 1 for now */ + dev->channel = 0; + dev->op->config ( dev, NET80211_CFG_CHANNEL ); + + /* Always do active probes at 1Mbps */ + dev->rate = 0; + dev->nr_rates = 1; + dev->rates[0] = 10; + dev->op->config ( dev, NET80211_CFG_RATE ); + + return 0; +} + +/** + * Prepare 802.11 device channel and rate set for communication + * + * @v dev 802.11 device + * @v wlan WLAN to prepare for communication with + * @ret rc Return status code + */ +int net80211_prepare_assoc ( 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; + + assert ( dev->netdev->state & NETDEV_OPEN ); + + net80211_set_state ( dev, NET80211_ASSOCIATED, 0, 0 ); + memcpy ( dev->bssid, wlan->bssid, ETH_ALEN ); + strcpy ( dev->essid, wlan->essid ); + + dev->last_beacon_timestamp = beacon->timestamp; + dev->tx_beacon_interval = 1024 * beacon->beacon_interval; + + /* XXX do crypto setup here */ + + /* Barring an IE that tells us the channel outright, assume + the channel we heard this AP best on is the channel it's + communicating on. */ + net80211_change_channel ( dev, wlan->channel ); + + rc = net80211_process_capab ( dev, beacon->capability ); + if ( rc ) + return rc; + + rc = net80211_process_ie ( dev, beacon->info_element, + wlan->beacon->tail ); + if ( rc ) + return rc; + + /* Associate at the lowest rate so we know it'll get through */ + dev->rate = 0; + dev->op->config ( dev, NET80211_CFG_RATE ); + + return 0; +} + +/** + * Send 802.11 initial authentication frame + * + * @v dev 802.11 device + * @v wlan WLAN to authenticate with + * @v method Authentication method + * @ret rc Return status code + * + * @a method may be 0 for Open System authentication or 1 for Shared + * Key authentication. Open System provides no security in association + * whatsoever, relying on encryption for confidentiality, but Shared + * Key actively introduces security problems and is very rarely used. + */ +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; + + net80211_set_state ( dev, 0, NET80211_WAITING, 0 ); + iob_reserve ( iob, IEEE80211_TYP_FRAME_HEADER_LEN ); + auth = iob_put ( iob, sizeof ( *auth ) ); + auth->algorithm = method; + auth->tx_seq = 1; + auth->status = 0; + + return net80211_tx_mgmt ( dev, IEEE80211_STYPE_AUTH, wlan->bssid, iob ); +} + +/** + * Handle receipt of 802.11 authentication frame + * + * @v dev 802.11 device + * @v iob I/O buffer + * + * If the authentication method being used is Shared Key, and the + * frame that was received included challenge text, the frame is + * encrypted using the cryptographic algorithm currently in effect and + * sent back to the AP to complete the authentication. + */ +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 ) { + DBGC ( dev, "802.11 %p authentication received improperly " + "directed frame (seq. %d)\n", dev, auth->tx_seq ); + net80211_set_state ( dev, NET80211_WAITING, 0, + IEEE80211_STATUS_FAILURE ); + return; + } + + if ( auth->status != IEEE80211_STATUS_SUCCESS ) { + DBGC ( dev, "802.11 %p authentication failed: status %d\n", + dev, auth->status ); + net80211_set_state ( dev, NET80211_WAITING, 0, + auth->status ); + return; + } + + if ( auth->algorithm == IEEE80211_AUTH_SHARED_KEY && ! dev->crypto ) { + DBGC ( dev, "802.11 %p can't perform shared-key authentication " + "without a cryptosystem\n", dev ); + 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->crypto, iob ) ); + return; + } + + net80211_set_state ( dev, NET80211_WAITING, NET80211_AUTHENTICATED, + IEEE80211_STATUS_SUCCESS ); + + return; +} + +/** + * Send 802.11 association frame + * + * @v dev 802.11 device + * @v wlan WLAN to associate with + * @ret rc Return status code + */ +int net80211_send_assoc ( struct net80211_device *dev, + struct net80211_wlan *wlan ) +{ + struct io_buffer *iob = alloc_iob ( 128 ); + struct ieee80211_assoc_req *assoc; + union ieee80211_ie *ie; + + net80211_set_state ( dev, 0, NET80211_WAITING, 0 ); + + 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->crypto ) + assoc->capability |= IEEE80211_CAPAB_PRIVACY; + + assoc->listen_interval = 1; + + ie = net80211_marshal_request_info ( dev, assoc->info_element ); + + DBGP ( "802.11 %p about to send association request:\n", dev ); + DBGP_HD ( iob->data, ( void * ) ie - iob->data ); + + /* XXX add RSN ie for WPA support */ + + iob_put ( iob, ( void * ) ie - iob->data ); + + return net80211_tx_mgmt ( dev, IEEE80211_STYPE_ASSOC_REQ, + wlan->bssid, iob ); +} + +/** + * Handle receipt of 802.11 association reply frame + * + * @v dev 802.11 device + * @v iob I/O buffer + */ +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->tail ); + + if ( assoc->status != IEEE80211_STATUS_SUCCESS ) { + DBGC ( dev, "802.11 %p association failed: status %d\n", + dev, 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 ); +} + + +/** + * 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 ); +} + + +/** Smoothing factor (1-7) for link quality calculation */ +#define LQ_SMOOTH 7 + +/** + * Update link quality information based on received beacon + * + * @v dev 802.11 device + * @v iob I/O buffer containing beacon + * @ret rc Return status code + */ +static void net80211_update_link_quality ( struct net80211_device *dev, + struct io_buffer *iob ) +{ + struct ieee80211_frame *hdr = iob->data; + struct ieee80211_beacon *beacon; + u32 dt, rxi; + + if ( ! ( dev->state & NET80211_ASSOCIATED ) ) + return; + + beacon = ( struct ieee80211_beacon * ) hdr->data; + dt = ( u32 ) ( beacon->timestamp - dev->last_beacon_timestamp ); + rxi = dev->rx_beacon_interval; + + rxi = ( LQ_SMOOTH * rxi ) + ( ( 8 - LQ_SMOOTH ) * dt ); + dev->rx_beacon_interval = rxi >> 3; + + dev->last_beacon_timestamp = beacon->timestamp; +} + + +/** + * Handle receipt of 802.11 management frame + * + * @v dev 802.11 device + * @v iob I/O buffer + * @v signal Signal strength of received frame + */ +static void net80211_handle_mgmt ( struct net80211_device *dev, + struct io_buffer *iob, int signal ) +{ + struct ieee80211_frame *hdr = iob->data; + 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 ); + return; /* only handle management frames */ + } + + switch ( stype ) { + /* We reconnect on deauthentication and disassociation. */ + case IEEE80211_STYPE_DEAUTH: + case IEEE80211_STYPE_DISASSOC: + disassoc = ( struct ieee80211_disassoc * ) hdr->data; + net80211_set_state ( dev, is_deauth ? NET80211_AUTHENTICATED : + NET80211_ASSOCIATED, 0, + NET80211_IS_REASON | disassoc->reason ); + DBGC ( dev, "802.11 %p %s: reason %d\n", + dev, is_deauth ? "deauthenticated" : "disassociated", + disassoc->reason ); + + /* Try to reassociate, in case it's transient. */ + 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_BEACON: + net80211_update_link_quality ( dev, iob ); + /* fall through */ + case IEEE80211_STYPE_PROBE_RESP: + case IEEE80211_STYPE_ACTION: + if ( dev->keep_mgmt ) { + struct net80211_rx_info *rxinf; + rxinf = zalloc ( sizeof ( *rxinf ) ); + if ( ! rxinf ) { + DBGC ( dev, "802.11 %p out of memory\n", dev ); + 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_PROBE_REQ: + /* Some nodes send these broadcast. Ignore them. */ + break; + + case IEEE80211_STYPE_ASSOC_REQ: + case IEEE80211_STYPE_REASSOC_REQ: + /* We should never receive these, only send them. */ + DBGC ( dev, "802.11 %p received strange management request " + "(%04x)\n", dev, stype ); + break; + + default: + DBGC ( dev, "802.11 %p received unimplemented management " + "packet (%04x)\n", dev, stype ); + break; + } + + if ( ! keep ) + free_iob ( iob ); +} + +/* ---------- Packet handling functions ---------- */ + +/** + * Free buffers used by 802.11 fragment cache entry + * + * @v dev 802.11 device + * @v fcid Fragment cache entry index + * + * After this function, the referenced entry will be marked unused. + */ +static void net80211_free_frags ( struct net80211_device *dev, int fcid ) +{ + int j; + struct net80211_frag_cache *frag = &dev->frags[fcid]; + + for ( j = 0; j < 16; j++ ) { + if ( frag->iob[j] ) { + free_iob ( frag->iob[j] ); + frag->iob[j] = NULL; + } + } + + frag->seqnr = 0; + frag->start_ticks = 0; + frag->in_use = 0; +} + +/** + * Accumulate 802.11 fragments into one I/O buffer + * + * @v dev 802.11 device + * @v fcid Fragment cache entry index + * @v nfrags Number of fragments received + * @v size Sum of sizes of all fragments, including headers + * @ret iob I/O buffer containing reassembled packet + * + * This function does not free the fragment buffers. + */ +static struct io_buffer *net80211_accum_frags ( struct net80211_device *dev, + int fcid, int nfrags, int size ) +{ + struct net80211_frag_cache *frag = &dev->frags[fcid]; + int hdrsize = IEEE80211_TYP_FRAME_HEADER_LEN; + int nsize = size - hdrsize * ( nfrags - 1 ); + int i; + + struct io_buffer *niob = alloc_iob ( nsize ); + struct ieee80211_frame *hdr; + + /* Add the header from the first one... */ + memcpy ( iob_put ( niob, hdrsize ), frag->iob[0]->data, hdrsize ); + + /* ... and all the data from all of them. */ + for ( i = 0; i < nfrags; i++ ) { + int len = iob_len ( frag->iob[i] ) - hdrsize; + memcpy ( iob_put ( niob, len ), + frag->iob[i]->data + hdrsize, len ); + } + + /* Turn off the fragment bit. */ + hdr = niob->data; + hdr->fc &= ~IEEE80211_FC_MORE_FRAG; + + return niob; +} + +/** + * Handle receipt of 802.11 fragment + * + * @v dev 802.11 device + * @v iob I/O buffer containing fragment + * @v signal Signal strength with which fragment was received + */ +static void net80211_rx_frag ( struct net80211_device *dev, + struct io_buffer *iob, int signal ) +{ + struct ieee80211_frame *hdr = iob->data; + int fragnr = IEEE80211_FRAG ( hdr->seq ); + + if ( fragnr == 0 && ( hdr->fc & IEEE80211_FC_MORE_FRAG ) ) { + /* start a frag cache entry */ + int i, newest = -1; + u32 curr_ticks = currticks(), newest_ticks = 0; + u32 timeout = ticks_per_sec() * NET80211_FRAG_TIMEOUT; + + for ( i = 0; i < NET80211_NR_CONCURRENT_FRAGS; i++ ) { + if ( dev->frags[i].in_use == 0 ) + break; + + if ( dev->frags[i].start_ticks + timeout >= + curr_ticks ) { + net80211_free_frags ( dev, i ); + break; + } + + if ( dev->frags[i].start_ticks > newest_ticks ) { + newest = i; + newest_ticks = dev->frags[i].start_ticks; + } + } + + /* If we're being sent more concurrent fragmented + packets than we can handle, drop the newest so the + older ones have time to complete. */ + if ( i == NET80211_NR_CONCURRENT_FRAGS ) { + i = newest; + net80211_free_frags ( dev, i ); + } + + dev->frags[i].in_use = 1; + dev->frags[i].seqnr = IEEE80211_SEQNR ( hdr->seq ); + dev->frags[i].start_ticks = currticks(); + dev->frags[i].iob[0] = iob; + return; + } else { + int i; + for ( i = 0; i < NET80211_NR_CONCURRENT_FRAGS; i++ ) { + if ( dev->frags[i].in_use && dev->frags[i].seqnr == + IEEE80211_SEQNR ( hdr->seq ) ) + break; + } + if ( i == NET80211_NR_CONCURRENT_FRAGS ) { + /* Drop non-first not-in-cache fragments */ + DBGC ( dev, "802.11 %p dropped fragment fc=%04x " + "seq=%04x\n", dev, hdr->fc, hdr->seq ); + free_iob ( iob ); + return; + } + + dev->frags[i].iob[fragnr] = iob; + + if ( ! ( hdr->fc & IEEE80211_FC_MORE_FRAG ) ) { + int j, size = 0; + for ( j = 0; j < fragnr; j++ ) { + size += iob_len ( dev->frags[i].iob[j] ); + if ( dev->frags[i].iob[j] == NULL ) + break; + } + if ( j == fragnr ) { + /* We've got everything */ + struct io_buffer *niob = + net80211_accum_frags ( dev, i, fragnr, + size ); + net80211_free_frags ( dev, i ); + net80211_rx ( dev, niob, signal, 0 ); + } else { + DBGC ( dev, "802.11 %p dropping fragmented " + "packet due to out-of-order arrival, " + "fc=%04x seq=%04x\n", dev, hdr->fc, + hdr->seq ); + net80211_free_frags ( dev, i ); + } + } + } +} + +/** + * Handle receipt of 802.11 frame + * + * @v dev 802.11 device + * @v iob I/O buffer + * @v signal Received signal strength + * @v rate Bitrate at which frame was received, in 100 kbps units + * + * If the rate or signal is unknown, 0 should be passed. + */ +void net80211_rx ( struct net80211_device *dev, struct io_buffer *iob, + int signal, u16 rate ) +{ + struct ieee80211_frame *hdr = iob->data; + u16 type = hdr->fc & IEEE80211_FC_TYPE; + if ( ( hdr->fc & IEEE80211_FC_VERSION ) != IEEE80211_THIS_VERSION ) + goto drop; /* drop invalid-version packets */ + + if ( type == IEEE80211_TYPE_CTRL ) + goto drop; /* we don't handle control packets, + the hardware does */ + + if ( dev->last_rx_seq == hdr->seq ) + goto drop; /* avoid duplicate packet */ + dev->last_rx_seq = hdr->seq; + + if ( dev->hw->flags & NET80211_HW_RX_HAS_FCS ) { + /* discard the FCS */ + iob_unput ( iob, 4 ); + } + + if ( hdr->fc & IEEE80211_FC_PROTECTED ) { + struct io_buffer *niob; + if ( ! dev->crypto ) + goto drop; /* can't decrypt packets on an open network */ + + niob = dev->crypto->decrypt ( dev->crypto, iob ); + if ( ! niob ) + goto drop; /* drop failed decryption */ + free_iob ( iob ); + iob = niob; + } + + dev->last_signal = signal; + + /* 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, signal ); + return; + } + + /* Management frames get handled, enqueued, or dropped. */ + if ( type == IEEE80211_TYPE_MGMT ) { + net80211_handle_mgmt ( dev, iob, signal ); + return; + } + + /* 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 */ + + /* Update rate-control algorithm */ + if ( dev->rctl ) + rc80211_update_rx ( dev, hdr->fc & IEEE80211_FC_RETRY, rate ); + + /* Pass packet onward */ + if ( netdev_link_ok ( dev->netdev ) ) { + netdev_rx ( dev->netdev, iob ); + return; + } + + drop: + DBGC2 ( dev, "802.11 %p dropped packet fc=%04x seq=%04x\n", dev, + hdr->fc, hdr->seq ); + free_iob ( iob ); + return; +} + +/** Indicate an error in receiving a packet + * + * @v dev 802.11 device + * @v iob I/O buffer with received packet, or NULL + * @v rc Error code + * + * This logs the error with the wrapping net_device, and frees iob if + * it is passed. + */ +void net80211_rx_err ( struct net80211_device *dev, + struct io_buffer *iob, int rc ) +{ + netdev_rx_err ( dev->netdev, iob, rc ); +} + +/** Indicate the completed transmission of a packet + * + * @v dev 802.11 device + * @v iob I/O buffer of transmitted packet + * @v retries Number of times this packet was retransmitted + * @v rc Error code, or 0 for success + * + * This logs an error with the wrapping net_device if one occurred, + * and removes and frees the I/O buffer from its TX queue. The + * provided retry information is used to tune our transmission rate. + * + * If the packet did not need to be retransmitted because it was + * properly ACKed the first time, @a retries should be 0. + */ +void net80211_tx_complete ( struct net80211_device *dev, + struct io_buffer *iob, int retries, int rc ) +{ + /* Update rate-control algorithm */ + if ( dev->rctl ) + rc80211_update_tx ( dev, retries, rc ); + + /* Pass completion onward */ + netdev_tx_complete_err ( dev->netdev, iob, rc ); +} diff --git a/src/net/80211/rc80211.c b/src/net/80211/rc80211.c new file mode 100644 index 00000000..5bd19143 --- /dev/null +++ b/src/net/80211/rc80211.c @@ -0,0 +1,371 @@ +/* + * Simple 802.11 rate-control algorithm for gPXE. + * + * Copyright (c) 2009 Joshua Oreman . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include + +/** + * @file + * + * Simple 802.11 rate-control algorithm + */ + +/** @page rc80211 Rate control philosophy + * + * We want to maximize our transmission speed, to the extent that we + * can do that without dropping undue numbers of packets. We also + * don't want to take up very much code space, so our algorithm has to + * be pretty simple + * + * When we receive a packet, we know what rate it was transmitted at, + * and whether it had to be retransmitted to get to us. + * + * When we send a packet, we hear back how many times it had to be + * retried to get through, and whether it got through at all. + * + * Indications of TX success are more reliable than RX success, but RX + * information helps us know where to start. + * + * To handle all of this, we keep for each rate and each direction (TX + * and RX separately) some state information for the most recent + * packets on that rate and the number of packets for which we have + * information. The state is a 32-bit unsigned integer in which two + * bits represent a packet: 11 if it went through well, 10 if it went + * through with one retry, 01 if it went through with more than one + * retry, or 00 if it didn't go through at all. We define the + * "goodness" for a particular (rate, direction) combination as the + * sum of all the 2-bit fields, times 33, divided by the number of + * 2-bit fields containing valid information (16 except when we're + * starting out). The number produced is between 0 and 99; we use -1 + * for rates with less than 4 RX packets or 1 TX, as an indicator that + * we do not have enough information to rely on them. + * + * In deciding which rates are best, we find the weighted average of + * TX and RX goodness, where the weighting is by number of packets + * with data and TX packets are worth 4 times as much as RX packets. + * The weighted average is called "net goodness" and is also a number + * between 0 and 99. If 3 consecutive packets fail transmission + * outright, we automatically ratchet down the rate; otherwise, we + * switch to the best rate whenever the current rate's goodness falls + * below some threshold, and try increasing our rate when the goodness + * is very high. + * + * This system is optimized for gPXE's style of usage. Because normal + * operation always involves receiving something, we'll make our way + * to the best rate pretty quickly. We tend to follow the lead of the + * sending AP in choosing rates, but we won't use rates for long that + * don't work well for us in transmission. We assume gPXE won't be + * running for long enough that rate patterns will change much, so we + * don't have to keep time counters or the like. And if this doesn't + * work well in practice there are many ways it could be tweaked. + * + * To avoid staying at 1Mbps for a long time, we don't track any + * transmitted packets until we've set our rate based on received + * packets. + */ + +/** Two-bit packet status indicator for a packet with no retries */ +#define RC_PKT_OK 0x3 + +/** Two-bit packet status indicator for a packet with one retry */ +#define RC_PKT_RETRIED_ONCE 0x2 + +/** Two-bit packet status indicator for a TX packet with multiple retries + * + * It is not possible to tell whether an RX packet had one or multiple + * retries; we rely instead on the fact that failed RX packets won't + * get to us at all, so if we receive a lot of RX packets on a certain + * rate it must be pretty good. + */ +#define RC_PKT_RETRIED_MULTI 0x1 + +/** Two-bit packet status indicator for a TX packet that was never ACKed + * + * It is not possible to tell whether an RX packet was setn if it + * didn't get through to us, but if we don't see one we won't increase + * the goodness for its rate. This asymmetry is part of why TX packets + * are weighted much more heavily than RX. + */ +#define RC_PKT_FAILED 0x0 + +/** Number of times to weight TX packets more heavily than RX packets */ +#define RC_TX_FACTOR 4 + +/** Number of consecutive failed TX packets that cause an automatic rate drop */ +#define RC_TX_EMERG_FAIL 3 + +/** Minimum net goodness below which we will search for a better rate */ +#define RC_GOODNESS_MIN 85 + +/** Maximum net goodness above which we will try to increase our rate */ +#define RC_GOODNESS_MAX 95 + +/** Minimum (num RX + @c RC_TX_FACTOR * num TX) to use a certain rate */ +#define RC_UNCERTAINTY_THRESH 4 + +/** TX direction */ +#define TX 0 + +/** RX direction */ +#define RX 1 + +/** A rate control context */ +struct rc80211_ctx +{ + /** Goodness state for each rate, TX and RX */ + u32 goodness[2][NET80211_MAX_RATES]; + + /** Number of packets recorded for each rate */ + u8 count[2][NET80211_MAX_RATES]; + + /** Indication of whether we've set the device rate yet */ + int started; + + /** Counter of all packets sent and received */ + int packets; +}; + +/** + * Initialize rate-control algorithm + * + * @v dev 802.11 device + * @ret ctx Rate-control context, to be stored in @c dev->rctl + */ +struct rc80211_ctx * rc80211_init ( struct net80211_device *dev __unused ) +{ + struct rc80211_ctx *ret = zalloc ( sizeof ( *ret ) ); + return ret; +} + +/** + * Calculate net goodness for a certain rate + * + * @v ctx Rate-control context + * @v rate_idx Index of rate to calculate net goodness for + */ +static int rc80211_calc_net_goodness ( struct rc80211_ctx *ctx, + int rate_idx ) +{ + int sum[2], num[2], dir, pkt; + + for ( dir = 0; dir < 2; dir++ ) { + u32 good = ctx->goodness[dir][rate_idx]; + + num[dir] = ctx->count[dir][rate_idx]; + sum[dir] = 0; + + for ( pkt = 0; pkt < num[dir]; pkt++ ) + sum[dir] += ( good >> ( 2 * pkt ) ) & 0x3; + } + + if ( ( num[TX] * RC_TX_FACTOR + num[RX] ) < RC_UNCERTAINTY_THRESH ) + return -1; + + return ( 33 * ( sum[TX] * RC_TX_FACTOR + sum[RX] ) / + ( num[TX] * RC_TX_FACTOR + num[RX] ) ); +} + +/** + * Determine the best rate to switch to and return it + * + * @v dev 802.11 device + * @ret rate_idx Index of the best rate to switch to + */ +static int rc80211_pick_best ( struct net80211_device *dev ) +{ + struct rc80211_ctx *ctx = dev->rctl; + int best_net_good = 0, best_rate = -1, i; + + for ( i = 0; i < dev->nr_rates; i++ ) { + int net_good = rc80211_calc_net_goodness ( ctx, i ); + + if ( net_good > best_net_good || + ( best_net_good > RC_GOODNESS_MIN && + net_good > RC_GOODNESS_MIN ) ) { + best_net_good = net_good; + best_rate = i; + } + } + + if ( best_rate >= 0 ) { + int old_good = rc80211_calc_net_goodness ( ctx, dev->rate ); + if ( old_good != best_net_good ) + DBGC ( ctx, "802.11 RC %p switching from goodness " + "%d to %d\n", ctx, old_good, best_net_good ); + + ctx->started = 1; + return best_rate; + } + + return dev->rate; +} + +/** + * Set 802.11 device rate + * + * @v dev 802.11 device + * @v rate_idx Index of rate to switch to + * + * This is a thin wrapper around net80211_set_rate_idx to insert a + * debugging message where appropriate. + */ +static inline void rc80211_set_rate ( struct net80211_device *dev, + int rate_idx ) +{ + DBGC ( dev->rctl, "802.11 RC %p changing rate %d->%d Mbps\n", dev->rctl, + dev->rates[dev->rate] / 10, dev->rates[rate_idx] / 10 ); + + net80211_set_rate_idx ( dev, rate_idx ); +} + +/** + * Check rate-control state and change rate if necessary + * + * @v dev 802.11 device + */ +static void rc80211_maybe_set_new ( struct net80211_device *dev ) +{ + struct rc80211_ctx *ctx = dev->rctl; + int net_good; + + net_good = rc80211_calc_net_goodness ( ctx, dev->rate ); + + if ( ! ctx->started ) { + rc80211_set_rate ( dev, rc80211_pick_best ( dev ) ); + return; + } + + if ( net_good < 0 ) /* insufficient data */ + return; + + if ( net_good > RC_GOODNESS_MAX && dev->rate + 1 < dev->nr_rates ) { + int higher = rc80211_calc_net_goodness ( ctx, dev->rate + 1 ); + if ( higher > net_good || higher < 0 ) + rc80211_set_rate ( dev, dev->rate + 1 ); + else + rc80211_set_rate ( dev, rc80211_pick_best ( dev ) ); + } + + if ( net_good < RC_GOODNESS_MIN ) { + rc80211_set_rate ( dev, rc80211_pick_best ( dev ) ); + } +} + +/** + * Update rate-control state + * + * @v dev 802.11 device + * @v direction One of the direction constants TX or RX + * @v rate_idx Index of rate at which packet was sent or received + * @v retries Number of times packet was retried before success + * @v failed If nonzero, the packet failed to get through + */ +static void rc80211_update ( struct net80211_device *dev, int direction, + int rate_idx, int retries, int failed ) +{ + struct rc80211_ctx *ctx = dev->rctl; + u32 goodness = ctx->goodness[direction][rate_idx]; + + if ( ctx->count[direction][rate_idx] < 16 ) + ctx->count[direction][rate_idx]++; + + goodness <<= 2; + if ( failed ) + goodness |= RC_PKT_FAILED; + else if ( retries > 1 ) + goodness |= RC_PKT_RETRIED_MULTI; + else if ( retries ) + goodness |= RC_PKT_RETRIED_ONCE; + else + goodness |= RC_PKT_OK; + + ctx->goodness[direction][rate_idx] = goodness; + + ctx->packets++; + + rc80211_maybe_set_new ( dev ); +} + +/** + * Update rate-control state for transmitted packet + * + * @v dev 802.11 device + * @v retries Number of times packet was transmitted before success + * @v rc Return status code for transmission + */ +void rc80211_update_tx ( struct net80211_device *dev, int retries, int rc ) +{ + struct rc80211_ctx *ctx = dev->rctl; + + if ( ! ctx->started ) + return; + + rc80211_update ( dev, TX, dev->rate, retries, rc ); + + /* Check if the last RC_TX_EMERG_FAIL packets have all failed */ + if ( ! ( ctx->goodness[TX][dev->rate] & + ( ( 1 << ( 2 * RC_TX_EMERG_FAIL ) ) - 1 ) ) ) { + if ( dev->rate == 0 ) + DBGC ( dev->rctl, "802.11 RC %p saw %d consecutive " + "failed TX, but cannot lower rate any further\n", + dev->rctl, RC_TX_EMERG_FAIL ); + else { + DBGC ( dev->rctl, "802.11 RC %p lowering rate (%d->%d " + "Mbps) due to %d consecutive TX failures\n", + dev->rctl, dev->rates[dev->rate] / 10, + dev->rates[dev->rate - 1] / 10, + RC_TX_EMERG_FAIL ); + + rc80211_set_rate ( dev, dev->rate - 1 ); + } + } +} + +/** + * Update rate-control state for received packet + * + * @v dev 802.11 device + * @v retry Whether the received packet had been retransmitted + * @v rate Rate at which packet was received, in 100 kbps units + */ +void rc80211_update_rx ( struct net80211_device *dev, int retry, u16 rate ) +{ + int ridx; + + for ( ridx = 0; ridx < dev->nr_rates && dev->rates[ridx] != rate; + ridx++ ) + ; + if ( ridx >= dev->nr_rates ) + return; /* couldn't find the rate */ + + rc80211_update ( dev, RX, ridx, retry, 0 ); +} + +/** + * Free rate-control context + * + * @v ctx Rate-control context + */ +void rc80211_free ( struct rc80211_ctx *ctx ) +{ + free ( ctx ); +}