diff options
Diffstat (limited to 'drivers/net/wireless/rndis_wlan.c')
-rw-r--r-- | drivers/net/wireless/rndis_wlan.c | 233 |
1 files changed, 232 insertions, 1 deletions
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 6b6452b0e8c4..7a50cfa18843 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c | |||
@@ -201,6 +201,24 @@ enum ndis_80211_priv_filter { | |||
201 | NDIS_80211_PRIV_8021X_WEP | 201 | NDIS_80211_PRIV_8021X_WEP |
202 | }; | 202 | }; |
203 | 203 | ||
204 | enum ndis_80211_status_type { | ||
205 | NDIS_80211_STATUSTYPE_AUTHENTICATION, | ||
206 | NDIS_80211_STATUSTYPE_MEDIASTREAMMODE, | ||
207 | NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST, | ||
208 | NDIS_80211_STATUSTYPE_RADIOSTATE, | ||
209 | }; | ||
210 | |||
211 | enum ndis_80211_media_stream_mode { | ||
212 | NDIS_80211_MEDIA_STREAM_OFF, | ||
213 | NDIS_80211_MEDIA_STREAM_ON | ||
214 | }; | ||
215 | |||
216 | enum ndis_80211_radio_status { | ||
217 | NDIS_80211_RADIO_STATUS_ON, | ||
218 | NDIS_80211_RADIO_STATUS_HARDWARE_OFF, | ||
219 | NDIS_80211_RADIO_STATUS_SOFTWARE_OFF, | ||
220 | }; | ||
221 | |||
204 | enum ndis_80211_addkey_bits { | 222 | enum ndis_80211_addkey_bits { |
205 | NDIS_80211_ADDKEY_8021X_AUTH = cpu_to_le32(1 << 28), | 223 | NDIS_80211_ADDKEY_8021X_AUTH = cpu_to_le32(1 << 28), |
206 | NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ = cpu_to_le32(1 << 29), | 224 | NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ = cpu_to_le32(1 << 29), |
@@ -213,6 +231,35 @@ enum ndis_80211_addwep_bits { | |||
213 | NDIS_80211_ADDWEP_TRANSMIT_KEY = cpu_to_le32(1 << 31) | 231 | NDIS_80211_ADDWEP_TRANSMIT_KEY = cpu_to_le32(1 << 31) |
214 | }; | 232 | }; |
215 | 233 | ||
234 | struct ndis_80211_auth_request { | ||
235 | __le32 length; | ||
236 | u8 bssid[6]; | ||
237 | u8 padding[2]; | ||
238 | __le32 flags; | ||
239 | } __attribute__((packed)); | ||
240 | |||
241 | struct ndis_80211_pmkid_candidate { | ||
242 | u8 bssid[6]; | ||
243 | u8 padding[2]; | ||
244 | __le32 flags; | ||
245 | } __attribute__((packed)); | ||
246 | |||
247 | struct ndis_80211_pmkid_cand_list { | ||
248 | __le32 version; | ||
249 | __le32 num_candidates; | ||
250 | struct ndis_80211_pmkid_candidate candidate_list[0]; | ||
251 | } __attribute__((packed)); | ||
252 | |||
253 | struct ndis_80211_status_indication { | ||
254 | __le32 status_type; | ||
255 | union { | ||
256 | enum ndis_80211_media_stream_mode media_stream_mode; | ||
257 | enum ndis_80211_radio_status radio_status; | ||
258 | struct ndis_80211_auth_request auth_request[0]; | ||
259 | struct ndis_80211_pmkid_cand_list cand_list; | ||
260 | } u; | ||
261 | } __attribute__((packed)); | ||
262 | |||
216 | struct ndis_80211_ssid { | 263 | struct ndis_80211_ssid { |
217 | __le32 length; | 264 | __le32 length; |
218 | u8 essid[NDIS_802_11_LENGTH_SSID]; | 265 | u8 essid[NDIS_802_11_LENGTH_SSID]; |
@@ -2211,16 +2258,195 @@ static void rndis_wlan_set_multicast_list(struct net_device *dev) | |||
2211 | queue_work(priv->workqueue, &priv->work); | 2258 | queue_work(priv->workqueue, &priv->work); |
2212 | } | 2259 | } |
2213 | 2260 | ||
2261 | |||
2262 | static void rndis_wlan_auth_indication(struct usbnet *usbdev, | ||
2263 | struct ndis_80211_status_indication *indication, | ||
2264 | int len) | ||
2265 | { | ||
2266 | u8 *buf; | ||
2267 | const char *type; | ||
2268 | int flags, buflen; | ||
2269 | bool pairwise_error, group_error; | ||
2270 | struct ndis_80211_auth_request *auth_req; | ||
2271 | |||
2272 | /* must have at least one array entry */ | ||
2273 | if (len < offsetof(struct ndis_80211_status_indication, u) + | ||
2274 | sizeof(struct ndis_80211_auth_request)) { | ||
2275 | devinfo(usbdev, "authentication indication: " | ||
2276 | "too short message (%i)", len); | ||
2277 | return; | ||
2278 | } | ||
2279 | |||
2280 | buf = (void *)&indication->u.auth_request[0]; | ||
2281 | buflen = len - offsetof(struct ndis_80211_status_indication, u); | ||
2282 | |||
2283 | while (buflen >= sizeof(*auth_req)) { | ||
2284 | auth_req = (void *)buf; | ||
2285 | type = "unknown"; | ||
2286 | flags = le32_to_cpu(auth_req->flags); | ||
2287 | pairwise_error = false; | ||
2288 | group_error = false; | ||
2289 | |||
2290 | if (flags & 0x1) | ||
2291 | type = "reauth request"; | ||
2292 | if (flags & 0x2) | ||
2293 | type = "key update request"; | ||
2294 | if (flags & 0x6) { | ||
2295 | pairwise_error = true; | ||
2296 | type = "pairwise_error"; | ||
2297 | } | ||
2298 | if (flags & 0xe) { | ||
2299 | group_error = true; | ||
2300 | type = "group_error"; | ||
2301 | } | ||
2302 | |||
2303 | devinfo(usbdev, "authentication indication: %s (0x%08x)", type, | ||
2304 | le32_to_cpu(auth_req->flags)); | ||
2305 | |||
2306 | if (pairwise_error || group_error) { | ||
2307 | union iwreq_data wrqu; | ||
2308 | struct iw_michaelmicfailure micfailure; | ||
2309 | |||
2310 | memset(&micfailure, 0, sizeof(micfailure)); | ||
2311 | if (pairwise_error) | ||
2312 | micfailure.flags |= IW_MICFAILURE_PAIRWISE; | ||
2313 | if (group_error) | ||
2314 | micfailure.flags |= IW_MICFAILURE_GROUP; | ||
2315 | |||
2316 | memcpy(micfailure.src_addr.sa_data, auth_req->bssid, | ||
2317 | ETH_ALEN); | ||
2318 | |||
2319 | memset(&wrqu, 0, sizeof(wrqu)); | ||
2320 | wrqu.data.length = sizeof(micfailure); | ||
2321 | wireless_send_event(usbdev->net, IWEVMICHAELMICFAILURE, | ||
2322 | &wrqu, (u8 *)&micfailure); | ||
2323 | } | ||
2324 | |||
2325 | buflen -= le32_to_cpu(auth_req->length); | ||
2326 | buf += le32_to_cpu(auth_req->length); | ||
2327 | } | ||
2328 | } | ||
2329 | |||
2330 | static void rndis_wlan_pmkid_cand_list_indication(struct usbnet *usbdev, | ||
2331 | struct ndis_80211_status_indication *indication, | ||
2332 | int len) | ||
2333 | { | ||
2334 | struct ndis_80211_pmkid_cand_list *cand_list; | ||
2335 | int list_len, expected_len, i; | ||
2336 | |||
2337 | if (len < offsetof(struct ndis_80211_status_indication, u) + | ||
2338 | sizeof(struct ndis_80211_pmkid_cand_list)) { | ||
2339 | devinfo(usbdev, "pmkid candidate list indication: " | ||
2340 | "too short message (%i)", len); | ||
2341 | return; | ||
2342 | } | ||
2343 | |||
2344 | list_len = le32_to_cpu(indication->u.cand_list.num_candidates) * | ||
2345 | sizeof(struct ndis_80211_pmkid_candidate); | ||
2346 | expected_len = sizeof(struct ndis_80211_pmkid_cand_list) + list_len + | ||
2347 | offsetof(struct ndis_80211_status_indication, u); | ||
2348 | |||
2349 | if (len < expected_len) { | ||
2350 | devinfo(usbdev, "pmkid candidate list indication: " | ||
2351 | "list larger than buffer (%i < %i)", | ||
2352 | len, expected_len); | ||
2353 | return; | ||
2354 | } | ||
2355 | |||
2356 | cand_list = &indication->u.cand_list; | ||
2357 | |||
2358 | devinfo(usbdev, "pmkid candidate list indication: " | ||
2359 | "version %i, candidates %i", | ||
2360 | le32_to_cpu(cand_list->version), | ||
2361 | le32_to_cpu(cand_list->num_candidates)); | ||
2362 | |||
2363 | if (le32_to_cpu(cand_list->version) != 1) | ||
2364 | return; | ||
2365 | |||
2366 | for (i = 0; i < le32_to_cpu(cand_list->num_candidates); i++) { | ||
2367 | struct iw_pmkid_cand pcand; | ||
2368 | union iwreq_data wrqu; | ||
2369 | struct ndis_80211_pmkid_candidate *cand = | ||
2370 | &cand_list->candidate_list[i]; | ||
2371 | |||
2372 | devdbg(usbdev, "cand[%i]: flags: 0x%08x, bssid: %pM", | ||
2373 | i, le32_to_cpu(cand->flags), cand->bssid); | ||
2374 | |||
2375 | memset(&pcand, 0, sizeof(pcand)); | ||
2376 | if (le32_to_cpu(cand->flags) & 0x01) | ||
2377 | pcand.flags |= IW_PMKID_CAND_PREAUTH; | ||
2378 | pcand.index = i; | ||
2379 | memcpy(pcand.bssid.sa_data, cand->bssid, ETH_ALEN); | ||
2380 | |||
2381 | memset(&wrqu, 0, sizeof(wrqu)); | ||
2382 | wrqu.data.length = sizeof(pcand); | ||
2383 | wireless_send_event(usbdev->net, IWEVPMKIDCAND, &wrqu, | ||
2384 | (u8 *)&pcand); | ||
2385 | } | ||
2386 | } | ||
2387 | |||
2388 | static void rndis_wlan_media_specific_indication(struct usbnet *usbdev, | ||
2389 | struct rndis_indicate *msg, int buflen) | ||
2390 | { | ||
2391 | struct ndis_80211_status_indication *indication; | ||
2392 | int len, offset; | ||
2393 | |||
2394 | offset = offsetof(struct rndis_indicate, status) + | ||
2395 | le32_to_cpu(msg->offset); | ||
2396 | len = le32_to_cpu(msg->length); | ||
2397 | |||
2398 | if (len < 8) { | ||
2399 | devinfo(usbdev, "media specific indication, " | ||
2400 | "ignore too short message (%i < 8)", len); | ||
2401 | return; | ||
2402 | } | ||
2403 | |||
2404 | if (offset + len > buflen) { | ||
2405 | devinfo(usbdev, "media specific indication, " | ||
2406 | "too large to fit to buffer (%i > %i)", | ||
2407 | offset + len, buflen); | ||
2408 | return; | ||
2409 | } | ||
2410 | |||
2411 | indication = (void *)((u8 *)msg + offset); | ||
2412 | |||
2413 | switch (le32_to_cpu(indication->status_type)) { | ||
2414 | case NDIS_80211_STATUSTYPE_RADIOSTATE: | ||
2415 | devinfo(usbdev, "radio state indication: %i", | ||
2416 | le32_to_cpu(indication->u.radio_status)); | ||
2417 | return; | ||
2418 | |||
2419 | case NDIS_80211_STATUSTYPE_MEDIASTREAMMODE: | ||
2420 | devinfo(usbdev, "media stream mode indication: %i", | ||
2421 | le32_to_cpu(indication->u.media_stream_mode)); | ||
2422 | return; | ||
2423 | |||
2424 | case NDIS_80211_STATUSTYPE_AUTHENTICATION: | ||
2425 | rndis_wlan_auth_indication(usbdev, indication, len); | ||
2426 | return; | ||
2427 | |||
2428 | case NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST: | ||
2429 | rndis_wlan_pmkid_cand_list_indication(usbdev, indication, len); | ||
2430 | return; | ||
2431 | |||
2432 | default: | ||
2433 | devinfo(usbdev, "media specific indication: " | ||
2434 | "unknown status type 0x%08x", | ||
2435 | le32_to_cpu(indication->status_type)); | ||
2436 | } | ||
2437 | } | ||
2438 | |||
2439 | |||
2214 | static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) | 2440 | static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) |
2215 | { | 2441 | { |
2216 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); | 2442 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); |
2217 | struct rndis_indicate *msg = ind; | 2443 | struct rndis_indicate *msg = ind; |
2218 | 2444 | ||
2219 | /* queue work to avoid recursive calls into rndis_command */ | ||
2220 | switch (msg->status) { | 2445 | switch (msg->status) { |
2221 | case RNDIS_STATUS_MEDIA_CONNECT: | 2446 | case RNDIS_STATUS_MEDIA_CONNECT: |
2222 | devinfo(usbdev, "media connect"); | 2447 | devinfo(usbdev, "media connect"); |
2223 | 2448 | ||
2449 | /* queue work to avoid recursive calls into rndis_command */ | ||
2224 | set_bit(WORK_LINK_UP, &priv->work_pending); | 2450 | set_bit(WORK_LINK_UP, &priv->work_pending); |
2225 | queue_work(priv->workqueue, &priv->work); | 2451 | queue_work(priv->workqueue, &priv->work); |
2226 | break; | 2452 | break; |
@@ -2228,10 +2454,15 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) | |||
2228 | case RNDIS_STATUS_MEDIA_DISCONNECT: | 2454 | case RNDIS_STATUS_MEDIA_DISCONNECT: |
2229 | devinfo(usbdev, "media disconnect"); | 2455 | devinfo(usbdev, "media disconnect"); |
2230 | 2456 | ||
2457 | /* queue work to avoid recursive calls into rndis_command */ | ||
2231 | set_bit(WORK_LINK_DOWN, &priv->work_pending); | 2458 | set_bit(WORK_LINK_DOWN, &priv->work_pending); |
2232 | queue_work(priv->workqueue, &priv->work); | 2459 | queue_work(priv->workqueue, &priv->work); |
2233 | break; | 2460 | break; |
2234 | 2461 | ||
2462 | case RNDIS_STATUS_MEDIA_SPECIFIC_INDICATION: | ||
2463 | rndis_wlan_media_specific_indication(usbdev, msg, buflen); | ||
2464 | break; | ||
2465 | |||
2235 | default: | 2466 | default: |
2236 | devinfo(usbdev, "indication: 0x%08x", | 2467 | devinfo(usbdev, "indication: 0x%08x", |
2237 | le32_to_cpu(msg->status)); | 2468 | le32_to_cpu(msg->status)); |