I find myself regularly asking people to run 'ifconfig iwm0 debug' to figure out reasons behind association failures from dmesg output.
I would like to make this information more accessible. Making it part of regular ifconfig output seems like a reasonable thing to do. This becomes possible now that node information is cached across scans. The current net80211 code is already computing failure reason codes which are visible in 'ifconfig debug' output. So far such codes were magic numbers. We can simply create macros for them and expose them in ieee80211_ioctl.h, and add another code to represent the "wrong-WPA-key" situation. To avoid changing the existing ifconfig output too much, the following association failure reasons are not displayed: - wrong SSID - open vs. encrypted network (They remain available at the ioctl layer which could be useful for third-party wifi tools in the ports tree.) I have never heard from people having problems figuring out the above two failure cases so I don't think it's worth adding them to virtually every line of 'ifconfig scan' output. However, the following cases are causing regular cries for help: - wrong WPA protocol version (number 1 reason for cries for help) - wrong WPA key - user forgets about previously hard-coded channel - user forgets about previously hard-coded BSSID And, of course, any of these problems can occur in combination. With the diff below, ifconfig output in such situations looks as follows: # ifconfig iwm0 nwid stsp.name wpakey wrong-key Now ifconfig iwm0 scan indicates wrong WPA key after a failed WPA handshake: nwid stsp.name chan 52 bssid 28:af:44:01:4f:38 71% HT-MCS15 privacy,short_slottime,wpa2 !(wpakey) (Configure AP to run WPA1 only.) # ifconfig iwm0 nwid stsp.name wpakey correct-key ifconfig iwm0 scan indicates wrong WPA protocol version: nwid stsp.name chan 52 bssid 28:af:44:01:4f:38 52% HT-MCS15 privacy,short_slottime,wpa1 !(wpaproto) (user hard-codes the channel) # ifconfig iwm0 chan 36 Now ifconfig iwm0 scan indicates WPA1-only AP on wrong channel: nwid stsp.name chan 52 bssid 28:af:44:01:4f:38 79% HT-MCS15 privacy,short_slottime,wpa1 !(chan,wpaproto) (user hard-codes BSSID) WPA1-only AP on wrong channel with wrong BSSID: # ifconfig iwm0 bssid 00:1c:c2:08:c6:65 now ifconfig iwm0 scan indicates WPA1-only AP, wrong channel, and wrong BSSID: nwid stsp.name chan 52 bssid 28:af:44:01:4f:38 56% HT-MCS15 privacy,short_slottime,wpa1 !(chan,bssid,wpaproto) Is this going in the right direction? There are no man page changes yet but the various reason failure codes would have to be properly documented, of course. To test the diff below, recompile the patched kernel, install the patched ieee80211_ioctl.h header (e.g. via 'make includes'), and recompile ifconfig. If ifconfig displays garbage with this patch applied, your kernel and ifconfig binary are out of sync. diff refs/heads/master refs/heads/assocfail blob - 543e09f0d6e6b4e75456a21d4218468729b61a5e blob + 03e4818ef5fa11d7e5dbf8307affb9f0ea5522a0 --- sbin/ifconfig/ifconfig.c +++ sbin/ifconfig/ifconfig.c @@ -2348,10 +2348,27 @@ print_cipherset(u_int32_t cipherset) } void +print_assoc_failures(uint32_t assoc_fail, const char *intro, const char *outro) +{ + /* Filter out the most obvious failure cases. */ + assoc_fail &= ~IEEE80211_NODEREQ_ASSOCFAIL_ESSID; + if (assoc_fail & IEEE80211_NODEREQ_ASSOCFAIL_PRIVACY) + assoc_fail &= ~IEEE80211_NODEREQ_ASSOCFAIL_WPA_PROTO; + assoc_fail &= ~IEEE80211_NODEREQ_ASSOCFAIL_PRIVACY; + + if (assoc_fail == 0) + return; + + printf("%s", intro); + printb_status(assoc_fail, IEEE80211_NODEREQ_ASSOCFAIL_BITS); + printf("%s", outro); +} + +void ieee80211_status(void) { int len, inwid, ijoin, inwkey, ipsk, ichan, ipwr; - int ibssid, iwpa; + int ibssid, iwpa, assocfail = 0; struct ieee80211_nwid nwid; struct ieee80211_join join; struct ieee80211_nwkey nwkey; @@ -2431,11 +2448,15 @@ ieee80211_status(void) bzero(&nr, sizeof(nr)); bcopy(bssid.i_bssid, &nr.nr_macaddr, sizeof(nr.nr_macaddr)); strlcpy(nr.nr_ifname, name, sizeof(nr.nr_ifname)); - if (ioctl(s, SIOCG80211NODE, &nr) == 0 && nr.nr_rssi) { - if (nr.nr_max_rssi) - printf(" %u%%", IEEE80211_NODEREQ_RSSI(&nr)); - else - printf(" %ddBm", nr.nr_rssi); + if (ioctl(s, SIOCG80211NODE, &nr) == 0) { + if (nr.nr_rssi) { + if (nr.nr_max_rssi) + printf(" %u%%", + IEEE80211_NODEREQ_RSSI(&nr)); + else + printf(" %ddBm", nr.nr_rssi); + } + assocfail = nr.nr_assoc_fail; } } @@ -2478,6 +2499,10 @@ ieee80211_status(void) putchar(' '); printb_status(ifr.ifr_flags, IEEE80211_F_USERBITS); } + + if (assocfail) + print_assoc_failures(assocfail, " !(", ")"); + putchar('\n'); if (show_join) join_status(); @@ -2751,6 +2776,8 @@ ieee80211_printnode(struct ieee80211_nodereq *nr) if ((nr->nr_flags & IEEE80211_NODEREQ_AP) == 0) printb_status(IEEE80211_NODEREQ_STATE(nr->nr_state), IEEE80211_NODEREQ_STATE_BITS); + else if (nr->nr_assoc_fail) + print_assoc_failures(nr->nr_assoc_fail, "!(", ") "); } void blob - 425ee5f5a726e3fe0445c82debb0469d4878407f blob + 6b3b8a5834db39d5b2f2dc7693f42d0b5adbcfbb --- sys/net80211/ieee80211_ioctl.c +++ sys/net80211/ieee80211_ioctl.c @@ -104,6 +104,7 @@ ieee80211_node2req(struct ieee80211com *ic, const stru nr->nr_txseq = ni->ni_txseq; nr->nr_rxseq = ni->ni_rxseq; nr->nr_fails = ni->ni_fails; + nr->nr_assoc_fail = ni->ni_assoc_fail; /* flag values are the same */ nr->nr_inact = ni->ni_inact; nr->nr_txrate = ni->ni_txrate; nr->nr_state = ni->ni_state; @@ -821,7 +822,11 @@ ieee80211_ioctl(struct ifnet *ifp, u_long cmd, caddr_t break; case SIOCG80211NODE: nr = (struct ieee80211_nodereq *)data; - ni = ieee80211_find_node(ic, nr->nr_macaddr); + if (ic->ic_bss && + IEEE80211_ADDR_EQ(nr->nr_macaddr, ic->ic_bss->ni_macaddr)) + ni = ic->ic_bss; + else + ni = ieee80211_find_node(ic, nr->nr_macaddr); if (ni == NULL) { error = ENOENT; break; blob - 575a573d2e21e9863bdab5276471f32fd189cc57 blob + 4b5b8cb5d273de964469fcffb03532c349ecca84 --- sys/net80211/ieee80211_ioctl.h +++ sys/net80211/ieee80211_ioctl.h @@ -359,6 +359,8 @@ struct ieee80211_nodereq { /* VHT */ uint8_t nr_vht_ss; + + u_int32_t nr_assoc_fail; /* association failure reasons */ }; #define IEEE80211_NODEREQ_STATE(_s) (1 << _s) @@ -378,6 +380,17 @@ struct ieee80211_nodereq { #define SIOCG80211NODE _IOWR('i', 211, struct ieee80211_nodereq) #define SIOCS80211NODE _IOW('i', 212, struct ieee80211_nodereq) #define SIOCS80211DELNODE _IOW('i', 213, struct ieee80211_nodereq) + +#define IEEE80211_NODEREQ_ASSOCFAIL_CHAN 0x01 +#define IEEE80211_NODEREQ_ASSOCFAIL_IBSS 0x02 +#define IEEE80211_NODEREQ_ASSOCFAIL_PRIVACY 0x04 +#define IEEE80211_NODEREQ_ASSOCFAIL_BASIC_RATE 0x08 +#define IEEE80211_NODEREQ_ASSOCFAIL_ESSID 0x10 +#define IEEE80211_NODEREQ_ASSOCFAIL_BSSID 0x20 +#define IEEE80211_NODEREQ_ASSOCFAIL_WPA_PROTO 0x40 +#define IEEE80211_NODEREQ_ASSOCFAIL_WPA_KEY 0x80 +#define IEEE80211_NODEREQ_ASSOCFAIL_BITS \ + "\20\1CHAN\2IBSS\3PRIVACY\4BASICRATE\5ESSID\6BSSID\7WPAPROTO\10WPAKEY" /* get the entire node cache */ struct ieee80211_nodereq_all { blob - 99b17a709fec1326d108be8e39ae8596f5b7f0f6 blob + ac031edda4662154bf384a3bd6d722c72547f136 --- sys/net80211/ieee80211_node.c +++ sys/net80211/ieee80211_node.c @@ -523,28 +523,40 @@ ieee80211_match_ess(struct ieee80211_ess *ess, struct { if (ess->esslen != 0 && (ess->esslen != ni->ni_esslen || - memcmp(ess->essid, ni->ni_essid, ess->esslen) != 0)) + memcmp(ess->essid, ni->ni_essid, ess->esslen) != 0)) { + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_ESSID; return 0; + } if (ess->flags & (IEEE80211_F_PSK | IEEE80211_F_RSNON)) { /* Ensure same WPA version. */ if ((ni->ni_rsnprotos & IEEE80211_PROTO_RSN) && - (ess->rsnprotos & IEEE80211_PROTO_RSN) == 0) + (ess->rsnprotos & IEEE80211_PROTO_RSN) == 0) { + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; return 0; + } if ((ni->ni_rsnprotos & IEEE80211_PROTO_WPA) && - (ess->rsnprotos & IEEE80211_PROTO_WPA) == 0) + (ess->rsnprotos & IEEE80211_PROTO_WPA) == 0) { + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; return 0; + } } else if (ess->flags & IEEE80211_F_WEPON) { - if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) + if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) { + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_PRIVACY; return 0; + } } else { - if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) != 0) + if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) != 0) { + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_PRIVACY; return 0; + } } if (ess->esslen == 0 && - (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) != 0) + (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) != 0) { + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_PRIVACY; return 0; + } return 1; } @@ -780,7 +792,7 @@ ieee80211_reset_scan(struct ifnet *ifp) } /* - * Increase a node's inactitivy counter. + * Increase a node's inactivity counter. * This counter get reset to zero if a frame is received. * This function is intended for station mode only. * See ieee80211_node_cache_timeout() for hostap mode. @@ -1000,41 +1012,41 @@ ieee80211_match_bss(struct ieee80211com *ic, struct ie fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) - fail |= 0x01; + fail |= IEEE80211_NODE_ASSOCFAIL_CHAN; if (ic->ic_des_chan != IEEE80211_CHAN_ANYC && ni->ni_chan != ic->ic_des_chan) - fail |= 0x01; + fail |= IEEE80211_NODE_ASSOCFAIL_CHAN; #ifndef IEEE80211_STA_ONLY if (ic->ic_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) - fail |= 0x02; + fail |= IEEE80211_NODE_ASSOCFAIL_IBSS; } else #endif { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) - fail |= 0x02; + fail |= IEEE80211_NODE_ASSOCFAIL_IBSS; } if (ic->ic_flags & (IEEE80211_F_WEPON | IEEE80211_F_RSNON)) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) - fail |= 0x04; + fail |= IEEE80211_NODE_ASSOCFAIL_PRIVACY; } else { if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) - fail |= 0x04; + fail |= IEEE80211_NODE_ASSOCFAIL_PRIVACY; } rate = ieee80211_fix_rate(ic, ni, IEEE80211_F_DONEGO); if (rate & IEEE80211_RATE_BASIC) - fail |= 0x08; + fail |= IEEE80211_NODE_ASSOCFAIL_BASIC_RATE; if (ISSET(ic->ic_flags, IEEE80211_F_AUTO_JOIN) && ic->ic_des_esslen == 0) - fail |= 0x10; + fail |= IEEE80211_NODE_ASSOCFAIL_ESSID; if (ic->ic_des_esslen != 0 && (ni->ni_esslen != ic->ic_des_esslen || memcmp(ni->ni_essid, ic->ic_des_essid, ic->ic_des_esslen) != 0)) - fail |= 0x10; + fail |= IEEE80211_NODE_ASSOCFAIL_ESSID; if ((ic->ic_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(ic->ic_des_bssid, ni->ni_bssid)) - fail |= 0x20; + fail |= IEEE80211_NODE_ASSOCFAIL_BSSID; if (ic->ic_flags & IEEE80211_F_RSNON) { /* @@ -1043,66 +1055,71 @@ ieee80211_match_bss(struct ieee80211com *ic, struct ie * decline to associate with that AP. */ if ((ni->ni_rsnprotos & ic->ic_rsnprotos) == 0) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; if ((ni->ni_rsnakms & ic->ic_rsnakms) == 0) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; if ((ni->ni_rsnakms & ic->ic_rsnakms & ~(IEEE80211_AKM_PSK | IEEE80211_AKM_SHA256_PSK)) == 0) { /* AP only supports PSK AKMPs */ if (!(ic->ic_flags & IEEE80211_F_PSK)) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; } if (ni->ni_rsngroupcipher != IEEE80211_CIPHER_WEP40 && ni->ni_rsngroupcipher != IEEE80211_CIPHER_TKIP && ni->ni_rsngroupcipher != IEEE80211_CIPHER_CCMP && ni->ni_rsngroupcipher != IEEE80211_CIPHER_WEP104) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; if ((ni->ni_rsnciphers & ic->ic_rsnciphers) == 0) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; /* we only support BIP as the IGTK cipher */ if ((ni->ni_rsncaps & IEEE80211_RSNCAP_MFPC) && ni->ni_rsngroupmgmtcipher != IEEE80211_CIPHER_BIP) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; /* we do not support MFP but AP requires it */ if (!(ic->ic_caps & IEEE80211_C_MFP) && (ni->ni_rsncaps & IEEE80211_RSNCAP_MFPR)) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; /* we require MFP but AP does not support it */ if ((ic->ic_caps & IEEE80211_C_MFP) && (ic->ic_flags & IEEE80211_F_MFPR) && !(ni->ni_rsncaps & IEEE80211_RSNCAP_MFPC)) - fail |= 0x40; + fail |= IEEE80211_NODE_ASSOCFAIL_WPA_PROTO; } if (ic->ic_if.if_flags & IFF_DEBUG) { printf("%s: %c %s%c", ic->ic_if.if_xname, fail ? '-' : '+', ether_sprintf(ni->ni_bssid), - fail & 0x20 ? '!' : ' '); + fail & IEEE80211_NODE_ASSOCFAIL_BSSID ? '!' : ' '); printf(" %3d%c", ieee80211_chan2ieee(ic, ni->ni_chan), - fail & 0x01 ? '!' : ' '); + fail & IEEE80211_NODE_ASSOCFAIL_CHAN ? '!' : ' '); printf(" %+4d", ni->ni_rssi); printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, - fail & 0x08 ? '!' : ' '); + fail & IEEE80211_NODE_ASSOCFAIL_BASIC_RATE ? '!' : ' '); printf(" %4s%c", (ni->ni_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "????", - fail & 0x02 ? '!' : ' '); + fail & IEEE80211_NODE_ASSOCFAIL_IBSS ? '!' : ' '); printf(" %7s%c ", (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "privacy" : "no", - fail & 0x04 ? '!' : ' '); + fail & IEEE80211_NODE_ASSOCFAIL_PRIVACY ? '!' : ' '); printf(" %3s%c ", (ic->ic_flags & IEEE80211_F_RSNON) ? "rsn" : "no", - fail & 0x40 ? '!' : ' '); + fail & IEEE80211_NODE_ASSOCFAIL_WPA_PROTO ? '!' : ' '); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); - printf("%s\n", fail & 0x10 ? "!" : ""); + printf("%s\n", + fail & IEEE80211_NODE_ASSOCFAIL_ESSID ? "!" : ""); } + ni->ni_assoc_fail |= fail; + if ((fail & IEEE80211_NODE_ASSOCFAIL_ESSID) == 0) + ic->ic_bss->ni_assoc_fail |= ni->ni_assoc_fail; + return fail; } @@ -1160,14 +1177,24 @@ ieee80211_node_join_bss(struct ieee80211com *ic, struc { enum ieee80211_phymode mode; struct ieee80211_node *ni; + uint32_t assoc_fail = 0; /* Reinitialize media mode and channels if needed. */ mode = ieee80211_chan2mode(ic, selbs->ni_chan); if (mode != ic->ic_curmode) ieee80211_setmode(ic, mode); + /* Keep recorded association failures for this BSS/ESS intact. */ + if (IEEE80211_ADDR_EQ(ic->ic_bss->ni_macaddr, selbs->ni_macaddr) || + (ic->ic_des_esslen > 0 && ic->ic_des_esslen == selbs->ni_esslen && + memcmp(ic->ic_des_essid, selbs->ni_essid, selbs->ni_esslen) == 0)) + assoc_fail = ic->ic_bss->ni_assoc_fail; + (*ic->ic_node_copy)(ic, ic->ic_bss, selbs); ni = ic->ic_bss; + ni->ni_assoc_fail |= assoc_fail; + + ic->ic_curmode = ieee80211_chan2mode(ic, ni->ni_chan); /* Make sure we send valid rates in an association request. */ if (ic->ic_opmode == IEEE80211_M_STA) blob - b2b80656a4b4aa907b4d729ecaecb81827739c52 blob + ddd746729a48b15fa3c21d85390682263d2934d9 --- sys/net80211/ieee80211_node.h +++ sys/net80211/ieee80211_node.h @@ -353,6 +353,16 @@ struct ieee80211_node { u_int16_t ni_qos_txseqs[IEEE80211_NUM_TID]; u_int16_t ni_qos_rxseqs[IEEE80211_NUM_TID]; int ni_fails; /* failure count to associate */ + uint32_t ni_assoc_fail; /* assoc failure reasons */ +#define IEEE80211_NODE_ASSOCFAIL_CHAN 0x01 +#define IEEE80211_NODE_ASSOCFAIL_IBSS 0x02 +#define IEEE80211_NODE_ASSOCFAIL_PRIVACY 0x04 +#define IEEE80211_NODE_ASSOCFAIL_BASIC_RATE 0x08 +#define IEEE80211_NODE_ASSOCFAIL_ESSID 0x10 +#define IEEE80211_NODE_ASSOCFAIL_BSSID 0x20 +#define IEEE80211_NODE_ASSOCFAIL_WPA_PROTO 0x40 +#define IEEE80211_NODE_ASSOCFAIL_WPA_KEY 0x80 + int ni_inact; /* inactivity mark count */ int ni_txrate; /* index to ni_rates[] */ int ni_state; blob - ba8c1872a2df596a0667ecb7c306f7e7f9dd0ffc blob + daf43cd6457134728ce5dd485229036ea4822445 --- sys/net80211/ieee80211_pae_input.c +++ sys/net80211/ieee80211_pae_input.c @@ -650,6 +650,7 @@ ieee80211_recv_4way_msg3(struct ieee80211com *ic, ether_sprintf(ni->ni_macaddr))); ni->ni_port_valid = 1; ieee80211_set_link_state(ic, LINK_STATE_UP); + ni->ni_assoc_fail = 0; } } deauth: blob - 30c784c7b2f404dcdb35d37a8b1b29928f628184 blob + 5ab07bfe2215a99449a86e073bc474ed238ae6b3 --- sys/net80211/ieee80211_proto.c +++ sys/net80211/ieee80211_proto.c @@ -939,6 +939,30 @@ ieee80211_stop_ampdu_tx(struct ieee80211com *ic, struc } } +void +ieee80211_check_wpa_supplicant_failure(struct ieee80211com *ic, + struct ieee80211_node *ni) +{ + struct ieee80211_node *ni2; + + if (ic->ic_opmode != IEEE80211_M_STA && + ic->ic_opmode != IEEE80211_M_IBSS) + return; + + if (ni->ni_rsn_supp_state != RSNA_SUPP_PTKNEGOTIATING) + return; + + ni->ni_assoc_fail |= IEEE80211_NODE_ASSOCFAIL_WPA_KEY; + + if (ni != ic->ic_bss) + return; + + /* Also update the copy of our AP's node in the node cache. */ + ni2 = ieee80211_find_node(ic, ic->ic_bss->ni_macaddr); + if (ni2) + ni2->ni_assoc_fail |= ic->ic_bss->ni_assoc_fail; +} + int ieee80211_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int mgt) @@ -1037,6 +1061,7 @@ justcleanup: break; } ni->ni_rsn_supp_state = RSNA_SUPP_INITIALIZE; + ni->ni_assoc_fail = 0; if (ic->ic_flags & IEEE80211_F_RSNON) ieee80211_crypto_clear_groupkeys(ic); break; @@ -1097,6 +1122,8 @@ justcleanup: } break; case IEEE80211_S_AUTH: + if (ostate == IEEE80211_S_RUN) + ieee80211_check_wpa_supplicant_failure(ic, ni); ni->ni_rsn_supp_state = RSNA_SUPP_INITIALIZE; if (ic->ic_flags & IEEE80211_F_RSNON) ieee80211_crypto_clear_groupkeys(ic); @@ -1218,6 +1245,7 @@ justcleanup: * the link up until the port is valid. */ ieee80211_set_link_state(ic, LINK_STATE_UP); + ni->ni_assoc_fail = 0; } ic->ic_mgt_timer = 0; ieee80211_set_beacon_miss_threshold(ic);