|
@@ -201,6 +201,24 @@ enum ndis_80211_priv_filter {
|
|
|
NDIS_80211_PRIV_8021X_WEP
|
|
|
};
|
|
|
|
|
|
+enum ndis_80211_status_type {
|
|
|
+ NDIS_80211_STATUSTYPE_AUTHENTICATION,
|
|
|
+ NDIS_80211_STATUSTYPE_MEDIASTREAMMODE,
|
|
|
+ NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST,
|
|
|
+ NDIS_80211_STATUSTYPE_RADIOSTATE,
|
|
|
+};
|
|
|
+
|
|
|
+enum ndis_80211_media_stream_mode {
|
|
|
+ NDIS_80211_MEDIA_STREAM_OFF,
|
|
|
+ NDIS_80211_MEDIA_STREAM_ON
|
|
|
+};
|
|
|
+
|
|
|
+enum ndis_80211_radio_status {
|
|
|
+ NDIS_80211_RADIO_STATUS_ON,
|
|
|
+ NDIS_80211_RADIO_STATUS_HARDWARE_OFF,
|
|
|
+ NDIS_80211_RADIO_STATUS_SOFTWARE_OFF,
|
|
|
+};
|
|
|
+
|
|
|
enum ndis_80211_addkey_bits {
|
|
|
NDIS_80211_ADDKEY_8021X_AUTH = cpu_to_le32(1 << 28),
|
|
|
NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ = cpu_to_le32(1 << 29),
|
|
@@ -213,6 +231,35 @@ enum ndis_80211_addwep_bits {
|
|
|
NDIS_80211_ADDWEP_TRANSMIT_KEY = cpu_to_le32(1 << 31)
|
|
|
};
|
|
|
|
|
|
+struct ndis_80211_auth_request {
|
|
|
+ __le32 length;
|
|
|
+ u8 bssid[6];
|
|
|
+ u8 padding[2];
|
|
|
+ __le32 flags;
|
|
|
+} __attribute__((packed));
|
|
|
+
|
|
|
+struct ndis_80211_pmkid_candidate {
|
|
|
+ u8 bssid[6];
|
|
|
+ u8 padding[2];
|
|
|
+ __le32 flags;
|
|
|
+} __attribute__((packed));
|
|
|
+
|
|
|
+struct ndis_80211_pmkid_cand_list {
|
|
|
+ __le32 version;
|
|
|
+ __le32 num_candidates;
|
|
|
+ struct ndis_80211_pmkid_candidate candidate_list[0];
|
|
|
+} __attribute__((packed));
|
|
|
+
|
|
|
+struct ndis_80211_status_indication {
|
|
|
+ __le32 status_type;
|
|
|
+ union {
|
|
|
+ enum ndis_80211_media_stream_mode media_stream_mode;
|
|
|
+ enum ndis_80211_radio_status radio_status;
|
|
|
+ struct ndis_80211_auth_request auth_request[0];
|
|
|
+ struct ndis_80211_pmkid_cand_list cand_list;
|
|
|
+ } u;
|
|
|
+} __attribute__((packed));
|
|
|
+
|
|
|
struct ndis_80211_ssid {
|
|
|
__le32 length;
|
|
|
u8 essid[NDIS_802_11_LENGTH_SSID];
|
|
@@ -2211,16 +2258,195 @@ static void rndis_wlan_set_multicast_list(struct net_device *dev)
|
|
|
queue_work(priv->workqueue, &priv->work);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+static void rndis_wlan_auth_indication(struct usbnet *usbdev,
|
|
|
+ struct ndis_80211_status_indication *indication,
|
|
|
+ int len)
|
|
|
+{
|
|
|
+ u8 *buf;
|
|
|
+ const char *type;
|
|
|
+ int flags, buflen;
|
|
|
+ bool pairwise_error, group_error;
|
|
|
+ struct ndis_80211_auth_request *auth_req;
|
|
|
+
|
|
|
+ /* must have at least one array entry */
|
|
|
+ if (len < offsetof(struct ndis_80211_status_indication, u) +
|
|
|
+ sizeof(struct ndis_80211_auth_request)) {
|
|
|
+ devinfo(usbdev, "authentication indication: "
|
|
|
+ "too short message (%i)", len);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf = (void *)&indication->u.auth_request[0];
|
|
|
+ buflen = len - offsetof(struct ndis_80211_status_indication, u);
|
|
|
+
|
|
|
+ while (buflen >= sizeof(*auth_req)) {
|
|
|
+ auth_req = (void *)buf;
|
|
|
+ type = "unknown";
|
|
|
+ flags = le32_to_cpu(auth_req->flags);
|
|
|
+ pairwise_error = false;
|
|
|
+ group_error = false;
|
|
|
+
|
|
|
+ if (flags & 0x1)
|
|
|
+ type = "reauth request";
|
|
|
+ if (flags & 0x2)
|
|
|
+ type = "key update request";
|
|
|
+ if (flags & 0x6) {
|
|
|
+ pairwise_error = true;
|
|
|
+ type = "pairwise_error";
|
|
|
+ }
|
|
|
+ if (flags & 0xe) {
|
|
|
+ group_error = true;
|
|
|
+ type = "group_error";
|
|
|
+ }
|
|
|
+
|
|
|
+ devinfo(usbdev, "authentication indication: %s (0x%08x)", type,
|
|
|
+ le32_to_cpu(auth_req->flags));
|
|
|
+
|
|
|
+ if (pairwise_error || group_error) {
|
|
|
+ union iwreq_data wrqu;
|
|
|
+ struct iw_michaelmicfailure micfailure;
|
|
|
+
|
|
|
+ memset(&micfailure, 0, sizeof(micfailure));
|
|
|
+ if (pairwise_error)
|
|
|
+ micfailure.flags |= IW_MICFAILURE_PAIRWISE;
|
|
|
+ if (group_error)
|
|
|
+ micfailure.flags |= IW_MICFAILURE_GROUP;
|
|
|
+
|
|
|
+ memcpy(micfailure.src_addr.sa_data, auth_req->bssid,
|
|
|
+ ETH_ALEN);
|
|
|
+
|
|
|
+ memset(&wrqu, 0, sizeof(wrqu));
|
|
|
+ wrqu.data.length = sizeof(micfailure);
|
|
|
+ wireless_send_event(usbdev->net, IWEVMICHAELMICFAILURE,
|
|
|
+ &wrqu, (u8 *)&micfailure);
|
|
|
+ }
|
|
|
+
|
|
|
+ buflen -= le32_to_cpu(auth_req->length);
|
|
|
+ buf += le32_to_cpu(auth_req->length);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void rndis_wlan_pmkid_cand_list_indication(struct usbnet *usbdev,
|
|
|
+ struct ndis_80211_status_indication *indication,
|
|
|
+ int len)
|
|
|
+{
|
|
|
+ struct ndis_80211_pmkid_cand_list *cand_list;
|
|
|
+ int list_len, expected_len, i;
|
|
|
+
|
|
|
+ if (len < offsetof(struct ndis_80211_status_indication, u) +
|
|
|
+ sizeof(struct ndis_80211_pmkid_cand_list)) {
|
|
|
+ devinfo(usbdev, "pmkid candidate list indication: "
|
|
|
+ "too short message (%i)", len);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ list_len = le32_to_cpu(indication->u.cand_list.num_candidates) *
|
|
|
+ sizeof(struct ndis_80211_pmkid_candidate);
|
|
|
+ expected_len = sizeof(struct ndis_80211_pmkid_cand_list) + list_len +
|
|
|
+ offsetof(struct ndis_80211_status_indication, u);
|
|
|
+
|
|
|
+ if (len < expected_len) {
|
|
|
+ devinfo(usbdev, "pmkid candidate list indication: "
|
|
|
+ "list larger than buffer (%i < %i)",
|
|
|
+ len, expected_len);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ cand_list = &indication->u.cand_list;
|
|
|
+
|
|
|
+ devinfo(usbdev, "pmkid candidate list indication: "
|
|
|
+ "version %i, candidates %i",
|
|
|
+ le32_to_cpu(cand_list->version),
|
|
|
+ le32_to_cpu(cand_list->num_candidates));
|
|
|
+
|
|
|
+ if (le32_to_cpu(cand_list->version) != 1)
|
|
|
+ return;
|
|
|
+
|
|
|
+ for (i = 0; i < le32_to_cpu(cand_list->num_candidates); i++) {
|
|
|
+ struct iw_pmkid_cand pcand;
|
|
|
+ union iwreq_data wrqu;
|
|
|
+ struct ndis_80211_pmkid_candidate *cand =
|
|
|
+ &cand_list->candidate_list[i];
|
|
|
+
|
|
|
+ devdbg(usbdev, "cand[%i]: flags: 0x%08x, bssid: %pM",
|
|
|
+ i, le32_to_cpu(cand->flags), cand->bssid);
|
|
|
+
|
|
|
+ memset(&pcand, 0, sizeof(pcand));
|
|
|
+ if (le32_to_cpu(cand->flags) & 0x01)
|
|
|
+ pcand.flags |= IW_PMKID_CAND_PREAUTH;
|
|
|
+ pcand.index = i;
|
|
|
+ memcpy(pcand.bssid.sa_data, cand->bssid, ETH_ALEN);
|
|
|
+
|
|
|
+ memset(&wrqu, 0, sizeof(wrqu));
|
|
|
+ wrqu.data.length = sizeof(pcand);
|
|
|
+ wireless_send_event(usbdev->net, IWEVPMKIDCAND, &wrqu,
|
|
|
+ (u8 *)&pcand);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void rndis_wlan_media_specific_indication(struct usbnet *usbdev,
|
|
|
+ struct rndis_indicate *msg, int buflen)
|
|
|
+{
|
|
|
+ struct ndis_80211_status_indication *indication;
|
|
|
+ int len, offset;
|
|
|
+
|
|
|
+ offset = offsetof(struct rndis_indicate, status) +
|
|
|
+ le32_to_cpu(msg->offset);
|
|
|
+ len = le32_to_cpu(msg->length);
|
|
|
+
|
|
|
+ if (len < 8) {
|
|
|
+ devinfo(usbdev, "media specific indication, "
|
|
|
+ "ignore too short message (%i < 8)", len);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (offset + len > buflen) {
|
|
|
+ devinfo(usbdev, "media specific indication, "
|
|
|
+ "too large to fit to buffer (%i > %i)",
|
|
|
+ offset + len, buflen);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ indication = (void *)((u8 *)msg + offset);
|
|
|
+
|
|
|
+ switch (le32_to_cpu(indication->status_type)) {
|
|
|
+ case NDIS_80211_STATUSTYPE_RADIOSTATE:
|
|
|
+ devinfo(usbdev, "radio state indication: %i",
|
|
|
+ le32_to_cpu(indication->u.radio_status));
|
|
|
+ return;
|
|
|
+
|
|
|
+ case NDIS_80211_STATUSTYPE_MEDIASTREAMMODE:
|
|
|
+ devinfo(usbdev, "media stream mode indication: %i",
|
|
|
+ le32_to_cpu(indication->u.media_stream_mode));
|
|
|
+ return;
|
|
|
+
|
|
|
+ case NDIS_80211_STATUSTYPE_AUTHENTICATION:
|
|
|
+ rndis_wlan_auth_indication(usbdev, indication, len);
|
|
|
+ return;
|
|
|
+
|
|
|
+ case NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST:
|
|
|
+ rndis_wlan_pmkid_cand_list_indication(usbdev, indication, len);
|
|
|
+ return;
|
|
|
+
|
|
|
+ default:
|
|
|
+ devinfo(usbdev, "media specific indication: "
|
|
|
+ "unknown status type 0x%08x",
|
|
|
+ le32_to_cpu(indication->status_type));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen)
|
|
|
{
|
|
|
struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
|
|
|
struct rndis_indicate *msg = ind;
|
|
|
|
|
|
- /* queue work to avoid recursive calls into rndis_command */
|
|
|
switch (msg->status) {
|
|
|
case RNDIS_STATUS_MEDIA_CONNECT:
|
|
|
devinfo(usbdev, "media connect");
|
|
|
|
|
|
+ /* queue work to avoid recursive calls into rndis_command */
|
|
|
set_bit(WORK_LINK_UP, &priv->work_pending);
|
|
|
queue_work(priv->workqueue, &priv->work);
|
|
|
break;
|
|
@@ -2228,10 +2454,15 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen)
|
|
|
case RNDIS_STATUS_MEDIA_DISCONNECT:
|
|
|
devinfo(usbdev, "media disconnect");
|
|
|
|
|
|
+ /* queue work to avoid recursive calls into rndis_command */
|
|
|
set_bit(WORK_LINK_DOWN, &priv->work_pending);
|
|
|
queue_work(priv->workqueue, &priv->work);
|
|
|
break;
|
|
|
|
|
|
+ case RNDIS_STATUS_MEDIA_SPECIFIC_INDICATION:
|
|
|
+ rndis_wlan_media_specific_indication(usbdev, msg, buflen);
|
|
|
+ break;
|
|
|
+
|
|
|
default:
|
|
|
devinfo(usbdev, "indication: 0x%08x",
|
|
|
le32_to_cpu(msg->status));
|