Disable network block temporarily on authentication failures
authorJouni Malinen <j@w1.fi>
Sun, 26 Aug 2012 20:01:26 +0000 (23:01 +0300)
committerJouni Malinen <j@w1.fi>
Sun, 26 Aug 2012 20:35:07 +0000 (23:35 +0300)
If 4-way handshake fails due to likely PSK failure or if EAP
authentication fails, disable the network block temporarily. Use longer
duration if multiple consecutive failures are seen.

Signed-hostap: Jouni Malinen <j@w1.fi>

src/common/wpa_ctrl.h
src/eapol_supp/eapol_supp_sm.c
src/eapol_supp/eapol_supp_sm.h
wpa_supplicant/config_ssid.h
wpa_supplicant/ctrl_iface.c
wpa_supplicant/events.c
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpa_supplicant_i.h
wpa_supplicant/wps_supplicant.c

index 59a4256..c2243f1 100644 (file)
@@ -48,6 +48,10 @@ extern "C" {
 #define WPA_EVENT_EAP_SUCCESS "CTRL-EVENT-EAP-SUCCESS "
 /** EAP authentication failed (EAP-Failure received) */
 #define WPA_EVENT_EAP_FAILURE "CTRL-EVENT-EAP-FAILURE "
+/** Network block temporarily disabled (e.g., due to authentication failure) */
+#define WPA_EVENT_TEMP_DISABLED "CTRL-EVENT-SSID-TEMP-DISABLED "
+/** Temporarily disabled network block re-enabled */
+#define WPA_EVENT_REENABLED "CTRL-EVENT-SSID-REENABLED "
 /** New scan results available */
 #define WPA_EVENT_SCAN_RESULTS "CTRL-EVENT-SCAN-RESULTS "
 /** wpa_supplicant state change */
index 07ef83b..8503554 100644 (file)
@@ -1920,3 +1920,11 @@ void eapol_sm_set_ext_pw_ctx(struct eapol_sm *sm,
        if (sm && sm->eap)
                eap_sm_set_ext_pw_ctx(sm->eap, ext);
 }
+
+
+int eapol_sm_failed(struct eapol_sm *sm)
+{
+       if (sm == NULL)
+               return 0;
+       return !sm->eapSuccess && sm->eapFail;
+}
index b69dd97..d2a4b94 100644 (file)
@@ -278,6 +278,7 @@ void eapol_sm_invalidate_cached_session(struct eapol_sm *sm);
 const char * eapol_sm_get_method_name(struct eapol_sm *sm);
 void eapol_sm_set_ext_pw_ctx(struct eapol_sm *sm,
                             struct ext_password_data *ext);
+int eapol_sm_failed(struct eapol_sm *sm);
 #else /* IEEE8021X_EAPOL */
 static inline struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx)
 {
@@ -373,6 +374,10 @@ static inline void eapol_sm_set_ext_pw_ctx(struct eapol_sm *sm,
                                           struct ext_password_data *ext)
 {
 }
+static inline int eapol_sm_failed(struct eapol_sm *sm)
+{
+       return 0;
+}
 #endif /* IEEE8021X_EAPOL */
 
 #endif /* EAPOL_SUPP_SM_H */
index 232c9c0..796b3d9 100644 (file)
@@ -519,6 +519,16 @@ struct wpa_ssid {
         * By default: 2
         */
        int dtim_period;
+
+       /**
+        * auth_failures - Number of consecutive authentication failures
+        */
+       unsigned int auth_failures;
+
+       /**
+        * disabled_until - Network block disabled until this time if non-zero
+        */
+       struct os_time disabled_until;
 };
 
 #endif /* CONFIG_SSID_H */
index ec61b84..0279fb2 100644 (file)
@@ -1362,10 +1362,12 @@ static int wpa_supplicant_ctrl_iface_list_networks(
                if (ret < 0 || ret >= end - pos)
                        return pos - buf;
                pos += ret;
-               ret = os_snprintf(pos, end - pos, "\t%s%s%s",
+               ret = os_snprintf(pos, end - pos, "\t%s%s%s%s",
                                  ssid == wpa_s->current_ssid ?
                                  "[CURRENT]" : "",
                                  ssid->disabled ? "[DISABLED]" : "",
+                                 ssid->disabled_until.sec ?
+                                 "[TEMP-DISABLED]" : "",
                                  ssid->disabled == 2 ? "[P2P-PERSISTENT]" :
                                  "");
                if (ret < 0 || ret >= end - pos)
index d70eae7..315fc34 100644 (file)
 #include "offchannel.h"
 
 
+static int wpas_temp_disabled(struct wpa_supplicant *wpa_s,
+                             struct wpa_ssid *ssid)
+{
+       struct os_time now;
+
+       if (ssid == NULL || ssid->disabled_until.sec == 0)
+               return 0;
+
+       os_get_time(&now);
+       if (ssid->disabled_until.sec > now.sec)
+               return ssid->disabled_until.sec - now.sec;
+
+       wpas_clear_temp_disabled(wpa_s, ssid, 0);
+
+       return 0;
+}
+
+
 static int wpa_supplicant_select_config(struct wpa_supplicant *wpa_s)
 {
        struct wpa_ssid *ssid, *old_ssid;
+       int res;
 
        if (wpa_s->conf->ap_scan == 1 && wpa_s->current_ssid)
                return 0;
@@ -64,6 +83,13 @@ static int wpa_supplicant_select_config(struct wpa_supplicant *wpa_s)
                return -1;
        }
 
+       res = wpas_temp_disabled(wpa_s, ssid);
+       if (res > 0) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Selected network is temporarily "
+                       "disabled for %d second(s)", res);
+               return -1;
+       }
+
        wpa_dbg(wpa_s, MSG_DEBUG, "Network configuration found for the "
                "current AP");
        if (wpa_key_mgmt_wpa_any(ssid->key_mgmt)) {
@@ -652,12 +678,20 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s,
 
        for (ssid = group; ssid; ssid = ssid->pnext) {
                int check_ssid = wpa ? 1 : (ssid->ssid_len != 0);
+               int res;
 
                if (wpas_network_disabled(wpa_s, ssid)) {
                        wpa_dbg(wpa_s, MSG_DEBUG, "   skip - disabled");
                        continue;
                }
 
+               res = wpas_temp_disabled(wpa_s, ssid);
+               if (res > 0) {
+                       wpa_dbg(wpa_s, MSG_DEBUG, "   skip - disabled "
+                               "temporarily for %d second(s)", res);
+                       continue;
+               }
+
 #ifdef CONFIG_WPS
                if ((ssid->key_mgmt & WPA_KEY_MGMT_WPS) && e && e->count > 0) {
                        wpa_dbg(wpa_s, MSG_DEBUG, "   skip - blacklisted "
@@ -1735,6 +1769,7 @@ static void wpa_supplicant_event_disassoc(struct wpa_supplicant *wpa_s,
            wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt)) {
                wpa_msg(wpa_s, MSG_INFO, "WPA: 4-Way Handshake failed - "
                        "pre-shared key may be incorrect");
+               wpas_auth_failed(wpa_s);
        }
        if (!wpa_s->auto_reconnect_disabled ||
            wpa_s->key_mgmt == WPA_KEY_MGMT_WPS) {
@@ -2306,6 +2341,11 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 #endif /* CONFIG_AP */
                wpa_supplicant_event_disassoc(wpa_s, reason_code,
                                              locally_generated);
+               if (reason_code == WLAN_REASON_IEEE_802_1X_AUTH_FAILED ||
+                   ((wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) ||
+                     (wpa_s->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)) &&
+                    eapol_sm_failed(wpa_s->eapol)))
+                       wpas_auth_failed(wpa_s);
 #ifdef CONFIG_P2P
                if (event == EVENT_DEAUTH && data) {
                        wpas_p2p_deauth_notif(wpa_s, data->deauth_info.addr,
index c3ebfc1..4734161 100644 (file)
@@ -648,6 +648,7 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
                        ssid ? ssid->id : -1,
                        ssid && ssid->id_str ? ssid->id_str : "");
 #endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */
+               wpas_clear_temp_disabled(wpa_s, ssid, 1);
                wpa_s->new_connection = 0;
                wpa_s->reassociated_connection = 1;
                wpa_drv_set_operstate(wpa_s, 1);
@@ -1723,6 +1724,8 @@ void wpa_supplicant_enable_network(struct wpa_supplicant *wpa_s,
                        was_disabled = other_ssid->disabled;
 
                        other_ssid->disabled = 0;
+                       if (was_disabled)
+                               wpas_clear_temp_disabled(wpa_s, other_ssid, 0);
 
                        if (was_disabled != other_ssid->disabled)
                                wpas_notify_network_enabled_changed(
@@ -1743,6 +1746,7 @@ void wpa_supplicant_enable_network(struct wpa_supplicant *wpa_s,
                was_disabled = ssid->disabled;
 
                ssid->disabled = 0;
+               wpas_clear_temp_disabled(wpa_s, ssid, 1);
 
                if (was_disabled != ssid->disabled)
                        wpas_notify_network_enabled_changed(wpa_s, ssid);
@@ -1813,6 +1817,9 @@ void wpa_supplicant_select_network(struct wpa_supplicant *wpa_s,
                disconnected = 1;
        }
 
+       if (ssid)
+               wpas_clear_temp_disabled(wpa_s, ssid, 1);
+
        /*
         * Mark all other networks disabled or mark all networks enabled if no
         * network specified.
@@ -1824,6 +1831,8 @@ void wpa_supplicant_select_network(struct wpa_supplicant *wpa_s,
                        continue; /* do not change persistent P2P group data */
 
                other_ssid->disabled = ssid ? (ssid->id != other_ssid->id) : 0;
+               if (was_disabled && !other_ssid->disabled)
+                       wpas_clear_temp_disabled(wpa_s, other_ssid, 0);
 
                if (was_disabled != other_ssid->disabled)
                        wpas_notify_network_enabled_changed(wpa_s, other_ssid);
@@ -3543,3 +3552,63 @@ int wpas_is_p2p_prioritized(struct wpa_supplicant *wpa_s)
                return 0;
        return -1;
 }
+
+
+void wpas_auth_failed(struct wpa_supplicant *wpa_s)
+{
+       struct wpa_ssid *ssid = wpa_s->current_ssid;
+       int dur;
+       struct os_time now;
+
+       if (ssid == NULL) {
+               wpa_printf(MSG_DEBUG, "Authentication failure but no known "
+                          "SSID block");
+               return;
+       }
+
+       if (ssid->key_mgmt == WPA_KEY_MGMT_WPS)
+               return;
+
+       ssid->auth_failures++;
+       if (ssid->auth_failures > 50)
+               dur = 300;
+       else if (ssid->auth_failures > 20)
+               dur = 120;
+       else if (ssid->auth_failures > 10)
+               dur = 60;
+       else if (ssid->auth_failures > 5)
+               dur = 30;
+       else if (ssid->auth_failures > 1)
+               dur = 20;
+       else
+               dur = 10;
+
+       os_get_time(&now);
+       if (now.sec + dur <= ssid->disabled_until.sec)
+               return;
+
+       ssid->disabled_until.sec = now.sec + dur;
+
+       wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_TEMP_DISABLED
+               "id=%d ssid=\"%s\" auth_failures=%u duration=%d",
+               ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len),
+               ssid->auth_failures, dur);
+}
+
+
+void wpas_clear_temp_disabled(struct wpa_supplicant *wpa_s,
+                             struct wpa_ssid *ssid, int clear_failures)
+{
+       if (ssid == NULL)
+               return;
+
+       if (ssid->disabled_until.sec) {
+               wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_REENABLED
+                       "id=%d ssid=\"%s\"",
+                       ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
+       }
+       ssid->disabled_until.sec = 0;
+       ssid->disabled_until.usec = 0;
+       if (clear_failures)
+               ssid->auth_failures = 0;
+}
index b2457c7..01ea873 100644 (file)
@@ -652,6 +652,9 @@ void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s);
 void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid);
 int wpas_driver_bss_selection(struct wpa_supplicant *wpa_s);
 int wpas_is_p2p_prioritized(struct wpa_supplicant *wpa_s);
+void wpas_auth_failed(struct wpa_supplicant *wpa_s);
+void wpas_clear_temp_disabled(struct wpa_supplicant *wpa_s,
+                             struct wpa_ssid *ssid, int clear_failures);
 void wpa_supplicant_proc_40mhz_intolerant(struct wpa_supplicant *wpa_s);
 
 /**
index 4b3d155..c72da11 100644 (file)
@@ -267,6 +267,9 @@ static int wpa_supplicant_wps_cred(void *ctx,
                        ssid->temporary = 0;
                        ssid->bssid_set = 0;
                }
+               ssid->disabled_until.sec = 0;
+               ssid->disabled_until.usec = 0;
+               ssid->auth_failures = 0;
        } else {
                wpa_printf(MSG_DEBUG, "WPS: Create a new network based on the "
                           "received credential");