Merged the hostap_2.6 updates, and the Leap of Faith work, from the hostap_update...
[mech_eap.git] / libeap / src / radius / radius_client.c
index 171af29..06c804d 100644 (file)
@@ -1,15 +1,9 @@
 /*
  * RADIUS client
- * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2015, Jouni Malinen <j@w1.fi>
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Alternatively, this software may be distributed under the terms of BSD
- * license.
- *
- * See README and COPYING for more details.
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
  */
 
 #include "includes.h"
@@ -128,7 +122,7 @@ struct radius_msg_list {
        /**
         * last_attempt - Time of the last transmission attempt
         */
-       struct os_time last_attempt;
+       struct os_reltime last_attempt;
 
        /**
         * shared_secret - Shared secret with the target RADIUS server
@@ -232,6 +226,16 @@ struct radius_client_data {
         * next_radius_identifier - Next RADIUS message identifier to use
         */
        u8 next_radius_identifier;
+
+       /**
+        * interim_error_cb - Interim accounting error callback
+        */
+       void (*interim_error_cb)(const u8 *addr, void *ctx);
+
+       /**
+        * interim_error_cb_ctx - interim_error_cb() context data
+        */
+       void *interim_error_cb_ctx;
 };
 
 
@@ -242,6 +246,8 @@ radius_change_server(struct radius_client_data *radius,
                     int sock, int sock6, int auth);
 static int radius_client_init_acct(struct radius_client_data *radius);
 static int radius_client_init_auth(struct radius_client_data *radius);
+static void radius_client_auth_failover(struct radius_client_data *radius);
+static void radius_client_acct_failover(struct radius_client_data *radius);
 
 
 static void radius_client_msg_free(struct radius_msg_list *req)
@@ -287,8 +293,8 @@ int radius_client_register(struct radius_client_data *radius,
                num = &radius->num_auth_handlers;
        }
 
-       newh = os_realloc(*handlers,
-                         (*num + 1) * sizeof(struct radius_rx_handler));
+       newh = os_realloc_array(*handlers, *num + 1,
+                               sizeof(struct radius_rx_handler));
        if (newh == NULL)
                return -1;
 
@@ -301,26 +307,53 @@ int radius_client_register(struct radius_client_data *radius,
 }
 
 
-static void radius_client_handle_send_error(struct radius_client_data *radius,
-                                           int s, RadiusType msg_type)
+/**
+ * radius_client_set_interim_erro_cb - Register an interim acct error callback
+ * @radius: RADIUS client context from radius_client_init()
+ * @addr: Station address from the failed message
+ * @cb: Handler for interim accounting errors
+ * @ctx: Context pointer for handler callbacks
+ *
+ * This function is used to register a handler for processing failed
+ * transmission attempts of interim accounting update messages.
+ */
+void radius_client_set_interim_error_cb(struct radius_client_data *radius,
+                                       void (*cb)(const u8 *addr, void *ctx),
+                                       void *ctx)
+{
+       radius->interim_error_cb = cb;
+       radius->interim_error_cb_ctx = ctx;
+}
+
+
+/*
+ * Returns >0 if message queue was flushed (i.e., the message that triggered
+ * the error is not available anymore)
+ */
+static int radius_client_handle_send_error(struct radius_client_data *radius,
+                                          int s, RadiusType msg_type)
 {
 #ifndef CONFIG_NATIVE_WINDOWS
        int _errno = errno;
-       perror("send[RADIUS]");
+       wpa_printf(MSG_INFO, "send[RADIUS,s=%d]: %s", s, strerror(errno));
        if (_errno == ENOTCONN || _errno == EDESTADDRREQ || _errno == EINVAL ||
-           _errno == EBADF) {
+           _errno == EBADF || _errno == ENETUNREACH || _errno == EACCES) {
                hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
                               HOSTAPD_LEVEL_INFO,
                               "Send failed - maybe interface status changed -"
                               " try to connect again");
-               eloop_unregister_read_sock(s);
-               close(s);
-               if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM)
+               if (msg_type == RADIUS_ACCT ||
+                   msg_type == RADIUS_ACCT_INTERIM) {
                        radius_client_init_acct(radius);
-               else
+                       return 0;
+               } else {
                        radius_client_init_auth(radius);
+                       return 1;
+               }
        }
 #endif /* CONFIG_NATIVE_WINDOWS */
+
+       return 0;
 }
 
 
@@ -331,9 +364,20 @@ static int radius_client_retransmit(struct radius_client_data *radius,
        struct hostapd_radius_servers *conf = radius->conf;
        int s;
        struct wpabuf *buf;
+       size_t prev_num_msgs;
+       u8 *acct_delay_time;
+       size_t acct_delay_time_len;
 
        if (entry->msg_type == RADIUS_ACCT ||
            entry->msg_type == RADIUS_ACCT_INTERIM) {
+               if (radius->acct_sock < 0)
+                       radius_client_init_acct(radius);
+               if (radius->acct_sock < 0 && conf->num_acct_servers > 1) {
+                       prev_num_msgs = radius->num_msgs;
+                       radius_client_acct_failover(radius);
+                       if (prev_num_msgs != radius->num_msgs)
+                               return 0;
+               }
                s = radius->acct_sock;
                if (entry->attempts == 0)
                        conf->acct_server->requests++;
@@ -342,6 +386,14 @@ static int radius_client_retransmit(struct radius_client_data *radius,
                        conf->acct_server->retransmissions++;
                }
        } else {
+               if (radius->auth_sock < 0)
+                       radius_client_init_auth(radius);
+               if (radius->auth_sock < 0 && conf->num_auth_servers > 1) {
+                       prev_num_msgs = radius->num_msgs;
+                       radius_client_auth_failover(radius);
+                       if (prev_num_msgs != radius->num_msgs)
+                               return 0;
+               }
                s = radius->auth_sock;
                if (entry->attempts == 0)
                        conf->auth_server->requests++;
@@ -351,24 +403,71 @@ static int radius_client_retransmit(struct radius_client_data *radius,
                }
        }
 
+       if (entry->msg_type == RADIUS_ACCT_INTERIM) {
+               wpa_printf(MSG_DEBUG,
+                          "RADIUS: Failed to transmit interim accounting update to "
+                          MACSTR " - drop message and request a new update",
+                          MAC2STR(entry->addr));
+               if (radius->interim_error_cb)
+                       radius->interim_error_cb(entry->addr,
+                                                radius->interim_error_cb_ctx);
+               return 1;
+       }
+
+       if (s < 0) {
+               wpa_printf(MSG_INFO,
+                          "RADIUS: No valid socket for retransmission");
+               return 1;
+       }
+
+       if (entry->msg_type == RADIUS_ACCT &&
+           radius_msg_get_attr_ptr(entry->msg, RADIUS_ATTR_ACCT_DELAY_TIME,
+                                   &acct_delay_time, &acct_delay_time_len,
+                                   NULL) == 0 &&
+           acct_delay_time_len == 4) {
+               struct radius_hdr *hdr;
+               u32 delay_time;
+
+               /*
+                * Need to assign a new identifier since attribute contents
+                * changes.
+                */
+               hdr = radius_msg_get_hdr(entry->msg);
+               hdr->identifier = radius_client_get_id(radius);
+
+               /* Update Acct-Delay-Time to show wait time in queue */
+               delay_time = now - entry->first_try;
+               WPA_PUT_BE32(acct_delay_time, delay_time);
+
+               wpa_printf(MSG_DEBUG,
+                          "RADIUS: Updated Acct-Delay-Time to %u for retransmission",
+                          delay_time);
+               radius_msg_finish_acct(entry->msg, entry->shared_secret,
+                                      entry->shared_secret_len);
+               if (radius->conf->msg_dumps)
+                       radius_msg_dump(entry->msg);
+       }
+
        /* retransmit; remove entry if too many attempts */
        entry->attempts++;
        hostapd_logger(radius->ctx, entry->addr, HOSTAPD_MODULE_RADIUS,
                       HOSTAPD_LEVEL_DEBUG, "Resending RADIUS message (id=%d)",
                       radius_msg_get_hdr(entry->msg)->identifier);
 
-       os_get_time(&entry->last_attempt);
+       os_get_reltime(&entry->last_attempt);
        buf = radius_msg_get_buf(entry->msg);
-       if (send(s, wpabuf_head(buf), wpabuf_len(buf), 0) < 0)
-               radius_client_handle_send_error(radius, s, entry->msg_type);
+       if (send(s, wpabuf_head(buf), wpabuf_len(buf), 0) < 0) {
+               if (radius_client_handle_send_error(radius, s, entry->msg_type)
+                   > 0)
+                       return 0;
+       }
 
        entry->next_try = now + entry->next_wait;
        entry->next_wait *= 2;
        if (entry->next_wait > RADIUS_CLIENT_MAX_WAIT)
                entry->next_wait = RADIUS_CLIENT_MAX_WAIT;
        if (entry->attempts >= RADIUS_CLIENT_MAX_RETRIES) {
-               printf("Removing un-ACKed RADIUS message due to too many "
-                      "failed retransmit attempts\n");
+               wpa_printf(MSG_INFO, "RADIUS: Removing un-ACKed message due to too many failed retransmit attempts");
                return 1;
        }
 
@@ -379,22 +478,23 @@ static int radius_client_retransmit(struct radius_client_data *radius,
 static void radius_client_timer(void *eloop_ctx, void *timeout_ctx)
 {
        struct radius_client_data *radius = eloop_ctx;
-       struct hostapd_radius_servers *conf = radius->conf;
-       struct os_time now;
+       struct os_reltime now;
        os_time_t first;
        struct radius_msg_list *entry, *prev, *tmp;
        int auth_failover = 0, acct_failover = 0;
-       char abuf[50];
+       size_t prev_num_msgs;
+       int s;
 
        entry = radius->msgs;
        if (!entry)
                return;
 
-       os_get_time(&now);
+       os_get_reltime(&now);
        first = 0;
 
        prev = NULL;
        while (entry) {
+               prev_num_msgs = radius->num_msgs;
                if (now.sec >= entry->next_try &&
                    radius_client_retransmit(radius, entry, now.sec)) {
                        if (prev)
@@ -409,7 +509,18 @@ static void radius_client_timer(void *eloop_ctx, void *timeout_ctx)
                        continue;
                }
 
-               if (entry->attempts > RADIUS_CLIENT_NUM_FAILOVER) {
+               if (prev_num_msgs != radius->num_msgs) {
+                       wpa_printf(MSG_DEBUG,
+                                  "RADIUS: Message removed from queue - restart from beginning");
+                       entry = radius->msgs;
+                       prev = NULL;
+                       continue;
+               }
+
+               s = entry->msg_type == RADIUS_AUTH ? radius->auth_sock :
+                       radius->acct_sock;
+               if (entry->attempts > RADIUS_CLIENT_NUM_FAILOVER ||
+                   (s < 0 && entry->attempts > 0)) {
                        if (entry->msg_type == RADIUS_ACCT ||
                            entry->msg_type == RADIUS_ACCT_INTERIM)
                                acct_failover++;
@@ -435,60 +546,76 @@ static void radius_client_timer(void *eloop_ctx, void *timeout_ctx)
                               (long int) (first - now.sec));
        }
 
-       if (auth_failover && conf->num_auth_servers > 1) {
-               struct hostapd_radius_server *next, *old;
-               old = conf->auth_server;
-               hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
-                              HOSTAPD_LEVEL_NOTICE,
-                              "No response from Authentication server "
-                              "%s:%d - failover",
-                              hostapd_ip_txt(&old->addr, abuf, sizeof(abuf)),
-                              old->port);
-
-               for (entry = radius->msgs; entry; entry = entry->next) {
-                       if (entry->msg_type == RADIUS_AUTH)
-                               old->timeouts++;
-               }
+       if (auth_failover)
+               radius_client_auth_failover(radius);
+
+       if (acct_failover)
+               radius_client_acct_failover(radius);
+}
+
+
+static void radius_client_auth_failover(struct radius_client_data *radius)
+{
+       struct hostapd_radius_servers *conf = radius->conf;
+       struct hostapd_radius_server *next, *old;
+       struct radius_msg_list *entry;
+       char abuf[50];
 
-               next = old + 1;
-               if (next > &(conf->auth_servers[conf->num_auth_servers - 1]))
-                       next = conf->auth_servers;
-               conf->auth_server = next;
-               radius_change_server(radius, next, old,
-                                    radius->auth_serv_sock,
-                                    radius->auth_serv_sock6, 1);
+       old = conf->auth_server;
+       hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
+                      HOSTAPD_LEVEL_NOTICE,
+                      "No response from Authentication server %s:%d - failover",
+                      hostapd_ip_txt(&old->addr, abuf, sizeof(abuf)),
+                      old->port);
+
+       for (entry = radius->msgs; entry; entry = entry->next) {
+               if (entry->msg_type == RADIUS_AUTH)
+                       old->timeouts++;
        }
 
-       if (acct_failover && conf->num_acct_servers > 1) {
-               struct hostapd_radius_server *next, *old;
-               old = conf->acct_server;
-               hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
-                              HOSTAPD_LEVEL_NOTICE,
-                              "No response from Accounting server "
-                              "%s:%d - failover",
-                              hostapd_ip_txt(&old->addr, abuf, sizeof(abuf)),
-                              old->port);
+       next = old + 1;
+       if (next > &(conf->auth_servers[conf->num_auth_servers - 1]))
+               next = conf->auth_servers;
+       conf->auth_server = next;
+       radius_change_server(radius, next, old,
+                            radius->auth_serv_sock,
+                            radius->auth_serv_sock6, 1);
+}
 
-               for (entry = radius->msgs; entry; entry = entry->next) {
-                       if (entry->msg_type == RADIUS_ACCT ||
-                           entry->msg_type == RADIUS_ACCT_INTERIM)
-                               old->timeouts++;
-               }
 
-               next = old + 1;
-               if (next > &conf->acct_servers[conf->num_acct_servers - 1])
-                       next = conf->acct_servers;
-               conf->acct_server = next;
-               radius_change_server(radius, next, old,
-                                    radius->acct_serv_sock,
-                                    radius->acct_serv_sock6, 0);
+static void radius_client_acct_failover(struct radius_client_data *radius)
+{
+       struct hostapd_radius_servers *conf = radius->conf;
+       struct hostapd_radius_server *next, *old;
+       struct radius_msg_list *entry;
+       char abuf[50];
+
+       old = conf->acct_server;
+       hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
+                      HOSTAPD_LEVEL_NOTICE,
+                      "No response from Accounting server %s:%d - failover",
+                      hostapd_ip_txt(&old->addr, abuf, sizeof(abuf)),
+                      old->port);
+
+       for (entry = radius->msgs; entry; entry = entry->next) {
+               if (entry->msg_type == RADIUS_ACCT ||
+                   entry->msg_type == RADIUS_ACCT_INTERIM)
+                       old->timeouts++;
        }
+
+       next = old + 1;
+       if (next > &conf->acct_servers[conf->num_acct_servers - 1])
+               next = conf->acct_servers;
+       conf->acct_server = next;
+       radius_change_server(radius, next, old,
+                            radius->acct_serv_sock,
+                            radius->acct_serv_sock6, 0);
 }
 
 
 static void radius_client_update_timeout(struct radius_client_data *radius)
 {
-       struct os_time now;
+       struct os_reltime now;
        os_time_t first;
        struct radius_msg_list *entry;
 
@@ -504,14 +631,14 @@ static void radius_client_update_timeout(struct radius_client_data *radius)
                        first = entry->next_try;
        }
 
-       os_get_time(&now);
+       os_get_reltime(&now);
        if (first < now.sec)
                first = now.sec;
        eloop_register_timeout(first - now.sec, 0, radius_client_timer, radius,
                               NULL);
        hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
                       HOSTAPD_LEVEL_DEBUG, "Next RADIUS client retransmit in"
-                      " %ld seconds\n", (long int) (first - now.sec));
+                      " %ld seconds", (long int) (first - now.sec));
 }
 
 
@@ -532,7 +659,7 @@ static void radius_client_list_add(struct radius_client_data *radius,
 
        entry = os_zalloc(sizeof(*entry));
        if (entry == NULL) {
-               printf("Failed to add RADIUS packet into retransmit list\n");
+               wpa_printf(MSG_INFO, "RADIUS: Failed to add packet into retransmit list");
                radius_msg_free(msg);
                return;
        }
@@ -543,7 +670,7 @@ static void radius_client_list_add(struct radius_client_data *radius,
        entry->msg_type = msg_type;
        entry->shared_secret = shared_secret;
        entry->shared_secret_len = shared_secret_len;
-       os_get_time(&entry->last_attempt);
+       os_get_reltime(&entry->last_attempt);
        entry->first_try = entry->last_attempt.sec;
        entry->next_try = entry->first_try + RADIUS_CLIENT_FIRST_WAIT;
        entry->attempts = 1;
@@ -553,8 +680,7 @@ static void radius_client_list_add(struct radius_client_data *radius,
        radius_client_update_timeout(radius);
 
        if (radius->num_msgs >= RADIUS_CLIENT_MAX_ENTRIES) {
-               printf("Removing the oldest un-ACKed RADIUS packet due to "
-                      "retransmit list limits.\n");
+               wpa_printf(MSG_INFO, "RADIUS: Removing the oldest un-ACKed packet due to retransmit list limits");
                prev = NULL;
                while (entry->next) {
                        prev = entry;
@@ -569,39 +695,6 @@ static void radius_client_list_add(struct radius_client_data *radius,
 }
 
 
-static void radius_client_list_del(struct radius_client_data *radius,
-                                  RadiusType msg_type, const u8 *addr)
-{
-       struct radius_msg_list *entry, *prev, *tmp;
-
-       if (addr == NULL)
-               return;
-
-       entry = radius->msgs;
-       prev = NULL;
-       while (entry) {
-               if (entry->msg_type == msg_type &&
-                   os_memcmp(entry->addr, addr, ETH_ALEN) == 0) {
-                       if (prev)
-                               prev->next = entry->next;
-                       else
-                               radius->msgs = entry->next;
-                       tmp = entry;
-                       entry = entry->next;
-                       hostapd_logger(radius->ctx, addr,
-                                      HOSTAPD_MODULE_RADIUS,
-                                      HOSTAPD_LEVEL_DEBUG,
-                                      "Removing matching RADIUS message");
-                       radius_client_msg_free(tmp);
-                       radius->num_msgs--;
-                       continue;
-               }
-               prev = entry;
-               entry = entry->next;
-       }
-}
-
-
 /**
  * radius_client_send - Send a RADIUS request
  * @radius: RADIUS client context from radius_client_init()
@@ -613,16 +706,19 @@ static void radius_client_list_del(struct radius_client_data *radius,
  * This function is used to transmit a RADIUS authentication (RADIUS_AUTH) or
  * accounting request (RADIUS_ACCT or RADIUS_ACCT_INTERIM). The only difference
  * between accounting and interim accounting messages is that the interim
- * message will override any pending interim accounting updates while a new
- * accounting message does not remove any pending messages.
+ * message will not be retransmitted. Instead, a callback is used to indicate
+ * that the transmission failed for the specific station @addr so that a new
+ * interim accounting update message can be generated with up-to-date session
+ * data instead of trying to resend old information.
  *
  * The message is added on the retransmission queue and will be retransmitted
  * automatically until a response is received or maximum number of retries
- * (RADIUS_CLIENT_MAX_RETRIES) is reached.
+ * (RADIUS_CLIENT_MAX_RETRIES) is reached. No such retries are used with
+ * RADIUS_ACCT_INTERIM, i.e., such a pending message is removed from the queue
+ * automatically on transmission failure.
  *
  * The related device MAC address can be used to identify pending messages that
- * can be removed with radius_client_flush_auth() or with interim accounting
- * updates.
+ * can be removed with radius_client_flush_auth().
  */
 int radius_client_send(struct radius_client_data *radius,
                       struct radius_msg *msg, RadiusType msg_type,
@@ -635,13 +731,12 @@ int radius_client_send(struct radius_client_data *radius,
        int s, res;
        struct wpabuf *buf;
 
-       if (msg_type == RADIUS_ACCT_INTERIM) {
-               /* Remove any pending interim acct update for the same STA. */
-               radius_client_list_del(radius, msg_type, addr);
-       }
-
        if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM) {
-               if (conf->acct_server == NULL) {
+               if (conf->acct_server && radius->acct_sock < 0)
+                       radius_client_init_acct(radius);
+
+               if (conf->acct_server == NULL || radius->acct_sock < 0 ||
+                   conf->acct_server->shared_secret == NULL) {
                        hostapd_logger(radius->ctx, NULL,
                                       HOSTAPD_MODULE_RADIUS,
                                       HOSTAPD_LEVEL_INFO,
@@ -655,7 +750,11 @@ int radius_client_send(struct radius_client_data *radius,
                s = radius->acct_sock;
                conf->acct_server->requests++;
        } else {
-               if (conf->auth_server == NULL) {
+               if (conf->auth_server && radius->auth_sock < 0)
+                       radius_client_init_auth(radius);
+
+               if (conf->auth_server == NULL || radius->auth_sock < 0 ||
+                   conf->auth_server->shared_secret == NULL) {
                        hostapd_logger(radius->ctx, NULL,
                                       HOSTAPD_MODULE_RADIUS,
                                       HOSTAPD_LEVEL_INFO,
@@ -684,7 +783,7 @@ int radius_client_send(struct radius_client_data *radius,
        radius_client_list_add(radius, msg, msg_type, shared_secret,
                               shared_secret_len, addr);
 
-       return res;
+       return 0;
 }
 
 
@@ -700,7 +799,7 @@ static void radius_client_receive(int sock, void *eloop_ctx, void *sock_ctx)
        struct radius_rx_handler *handlers;
        size_t num_handlers, i;
        struct radius_msg_list *req, *prev_req;
-       struct os_time now;
+       struct os_reltime now;
        struct hostapd_radius_server *rconf;
        int invalid_authenticator = 0;
 
@@ -716,21 +815,20 @@ static void radius_client_receive(int sock, void *eloop_ctx, void *sock_ctx)
 
        len = recv(sock, buf, sizeof(buf), MSG_DONTWAIT);
        if (len < 0) {
-               perror("recv[RADIUS]");
+               wpa_printf(MSG_INFO, "recv[RADIUS]: %s", strerror(errno));
                return;
        }
        hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
                       HOSTAPD_LEVEL_DEBUG, "Received %d bytes from RADIUS "
                       "server", len);
        if (len == sizeof(buf)) {
-               printf("Possibly too long UDP frame for our buffer - "
-                      "dropping it\n");
+               wpa_printf(MSG_INFO, "RADIUS: Possibly too long UDP frame for our buffer - dropping it");
                return;
        }
 
        msg = radius_msg_parse(buf, len);
        if (msg == NULL) {
-               printf("Parsing incoming RADIUS frame failed\n");
+               wpa_printf(MSG_INFO, "RADIUS: Parsing incoming frame failed");
                rconf->malformed_responses++;
                return;
        }
@@ -781,7 +879,7 @@ static void radius_client_receive(int sock, void *eloop_ctx, void *sock_ctx)
                goto fail;
        }
 
-       os_get_time(&now);
+       os_get_reltime(&now);
        roundtrip = (now.sec - req->last_attempt.sec) * 100 +
                (now.usec - req->last_attempt.usec) / 10000;
        hostapd_logger(radius->ctx, req->addr, HOSTAPD_MODULE_RADIUS,
@@ -952,6 +1050,9 @@ radius_change_server(struct radius_client_data *radius,
        int sel_sock;
        struct radius_msg_list *entry;
        struct hostapd_radius_servers *conf = radius->conf;
+       struct sockaddr_in disconnect_addr = {
+               .sin_family = AF_UNSPEC,
+       };
 
        hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
                       HOSTAPD_LEVEL_INFO,
@@ -960,9 +1061,16 @@ radius_change_server(struct radius_client_data *radius,
                       hostapd_ip_txt(&nserv->addr, abuf, sizeof(abuf)),
                       nserv->port);
 
-       if (!oserv || nserv->shared_secret_len != oserv->shared_secret_len ||
-           os_memcmp(nserv->shared_secret, oserv->shared_secret,
-                     nserv->shared_secret_len) != 0) {
+       if (oserv && oserv == nserv) {
+               /* Reconnect to same server, flush */
+               if (auth)
+                       radius_client_flush(radius, 1);
+       }
+
+       if (oserv && oserv != nserv &&
+           (nserv->shared_secret_len != oserv->shared_secret_len ||
+            os_memcmp(nserv->shared_secret, oserv->shared_secret,
+                      nserv->shared_secret_len) != 0)) {
                /* Pending RADIUS packets used different shared secret, so
                 * they need to be modified. Update accounting message
                 * authenticators here. Authentication messages are removed
@@ -980,7 +1088,8 @@ radius_change_server(struct radius_client_data *radius,
        }
 
        /* Reset retry counters for the new server */
-       for (entry = radius->msgs; entry; entry = entry->next) {
+       for (entry = radius->msgs; oserv && oserv != nserv && entry;
+            entry = entry->next) {
                if ((auth && entry->msg_type != RADIUS_AUTH) ||
                    (!auth && entry->msg_type != RADIUS_ACCT))
                        continue;
@@ -1021,6 +1130,13 @@ radius_change_server(struct radius_client_data *radius,
                return -1;
        }
 
+       if (sel_sock < 0) {
+               wpa_printf(MSG_INFO,
+                          "RADIUS: No server socket available (af=%d sock=%d sock6=%d auth=%d",
+                          nserv->addr.af, sock, sock6, auth);
+               return -1;
+       }
+
        if (conf->force_client_addr) {
                switch (conf->client_addr.af) {
                case AF_INET:
@@ -1047,13 +1163,19 @@ radius_change_server(struct radius_client_data *radius,
                }
 
                if (bind(sel_sock, cl_addr, claddrlen) < 0) {
-                       perror("bind[radius]");
+                       wpa_printf(MSG_INFO, "bind[radius]: %s",
+                                  strerror(errno));
                        return -1;
                }
        }
 
+       /* Force a reconnect by disconnecting the socket first */
+       if (connect(sel_sock, (struct sockaddr *) &disconnect_addr,
+                   sizeof(disconnect_addr)) < 0)
+               wpa_printf(MSG_INFO, "disconnect[radius]: %s", strerror(errno));
+
        if (connect(sel_sock, addr, addrlen) < 0) {
-               perror("connect[radius]");
+               wpa_printf(MSG_INFO, "connect[radius]: %s", strerror(errno));
                return -1;
        }
 
@@ -1061,19 +1183,23 @@ radius_change_server(struct radius_client_data *radius,
        switch (nserv->addr.af) {
        case AF_INET:
                claddrlen = sizeof(claddr);
-               getsockname(sel_sock, (struct sockaddr *) &claddr, &claddrlen);
-               wpa_printf(MSG_DEBUG, "RADIUS local address: %s:%u",
-                          inet_ntoa(claddr.sin_addr), ntohs(claddr.sin_port));
+               if (getsockname(sel_sock, (struct sockaddr *) &claddr,
+                               &claddrlen) == 0) {
+                       wpa_printf(MSG_DEBUG, "RADIUS local address: %s:%u",
+                                  inet_ntoa(claddr.sin_addr),
+                                  ntohs(claddr.sin_port));
+               }
                break;
 #ifdef CONFIG_IPV6
        case AF_INET6: {
                claddrlen = sizeof(claddr6);
-               getsockname(sel_sock, (struct sockaddr *) &claddr6,
-                           &claddrlen);
-               wpa_printf(MSG_DEBUG, "RADIUS local address: %s:%u",
-                          inet_ntop(AF_INET6, &claddr6.sin6_addr,
-                                    abuf, sizeof(abuf)),
-                          ntohs(claddr6.sin6_port));
+               if (getsockname(sel_sock, (struct sockaddr *) &claddr6,
+                               &claddrlen) == 0) {
+                       wpa_printf(MSG_DEBUG, "RADIUS local address: %s:%u",
+                                  inet_ntop(AF_INET6, &claddr6.sin6_addr,
+                                            abuf, sizeof(abuf)),
+                                  ntohs(claddr6.sin6_port));
+               }
                break;
        }
 #endif /* CONFIG_IPV6 */
@@ -1099,18 +1225,28 @@ static void radius_retry_primary_timer(void *eloop_ctx, void *timeout_ctx)
            conf->auth_server != conf->auth_servers) {
                oserv = conf->auth_server;
                conf->auth_server = conf->auth_servers;
-               radius_change_server(radius, conf->auth_server, oserv,
-                                    radius->auth_serv_sock,
-                                    radius->auth_serv_sock6, 1);
+               if (radius_change_server(radius, conf->auth_server, oserv,
+                                        radius->auth_serv_sock,
+                                        radius->auth_serv_sock6, 1) < 0) {
+                       conf->auth_server = oserv;
+                       radius_change_server(radius, oserv, conf->auth_server,
+                                            radius->auth_serv_sock,
+                                            radius->auth_serv_sock6, 1);
+               }
        }
 
        if (radius->acct_sock >= 0 && conf->acct_servers &&
            conf->acct_server != conf->acct_servers) {
                oserv = conf->acct_server;
                conf->acct_server = conf->acct_servers;
-               radius_change_server(radius, conf->acct_server, oserv,
-                                    radius->acct_serv_sock,
-                                    radius->acct_serv_sock6, 0);
+               if (radius_change_server(radius, conf->acct_server, oserv,
+                                        radius->acct_serv_sock,
+                                        radius->acct_serv_sock6, 0) < 0) {
+                       conf->acct_server = oserv;
+                       radius_change_server(radius, oserv, conf->acct_server,
+                                            radius->acct_serv_sock,
+                                            radius->acct_serv_sock6, 0);
+               }
        }
 
        if (conf->retry_primary_interval)
@@ -1129,21 +1265,62 @@ static int radius_client_disable_pmtu_discovery(int s)
        r = setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action,
                       sizeof(action));
        if (r == -1)
-               wpa_printf(MSG_ERROR, "Failed to set IP_MTU_DISCOVER: "
-                          "%s", strerror(errno));
+               wpa_printf(MSG_ERROR, "RADIUS: Failed to set IP_MTU_DISCOVER: %s",
+                          strerror(errno));
 #endif
        return r;
 }
 
 
+static void radius_close_auth_sockets(struct radius_client_data *radius)
+{
+       radius->auth_sock = -1;
+
+       if (radius->auth_serv_sock >= 0) {
+               eloop_unregister_read_sock(radius->auth_serv_sock);
+               close(radius->auth_serv_sock);
+               radius->auth_serv_sock = -1;
+       }
+#ifdef CONFIG_IPV6
+       if (radius->auth_serv_sock6 >= 0) {
+               eloop_unregister_read_sock(radius->auth_serv_sock6);
+               close(radius->auth_serv_sock6);
+               radius->auth_serv_sock6 = -1;
+       }
+#endif /* CONFIG_IPV6 */
+}
+
+
+static void radius_close_acct_sockets(struct radius_client_data *radius)
+{
+       radius->acct_sock = -1;
+
+       if (radius->acct_serv_sock >= 0) {
+               eloop_unregister_read_sock(radius->acct_serv_sock);
+               close(radius->acct_serv_sock);
+               radius->acct_serv_sock = -1;
+       }
+#ifdef CONFIG_IPV6
+       if (radius->acct_serv_sock6 >= 0) {
+               eloop_unregister_read_sock(radius->acct_serv_sock6);
+               close(radius->acct_serv_sock6);
+               radius->acct_serv_sock6 = -1;
+       }
+#endif /* CONFIG_IPV6 */
+}
+
+
 static int radius_client_init_auth(struct radius_client_data *radius)
 {
        struct hostapd_radius_servers *conf = radius->conf;
        int ok = 0;
 
+       radius_close_auth_sockets(radius);
+
        radius->auth_serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
        if (radius->auth_serv_sock < 0)
-               perror("socket[PF_INET,SOCK_DGRAM]");
+               wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s",
+                          strerror(errno));
        else {
                radius_client_disable_pmtu_discovery(radius->auth_serv_sock);
                ok++;
@@ -1152,7 +1329,8 @@ static int radius_client_init_auth(struct radius_client_data *radius)
 #ifdef CONFIG_IPV6
        radius->auth_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
        if (radius->auth_serv_sock6 < 0)
-               perror("socket[PF_INET6,SOCK_DGRAM]");
+               wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s",
+                          strerror(errno));
        else
                ok++;
 #endif /* CONFIG_IPV6 */
@@ -1168,8 +1346,8 @@ static int radius_client_init_auth(struct radius_client_data *radius)
            eloop_register_read_sock(radius->auth_serv_sock,
                                     radius_client_receive, radius,
                                     (void *) RADIUS_AUTH)) {
-               printf("Could not register read socket for authentication "
-                      "server\n");
+               wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server");
+               radius_close_auth_sockets(radius);
                return -1;
        }
 
@@ -1178,8 +1356,8 @@ static int radius_client_init_auth(struct radius_client_data *radius)
            eloop_register_read_sock(radius->auth_serv_sock6,
                                     radius_client_receive, radius,
                                     (void *) RADIUS_AUTH)) {
-               printf("Could not register read socket for authentication "
-                      "server\n");
+               wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server");
+               radius_close_auth_sockets(radius);
                return -1;
        }
 #endif /* CONFIG_IPV6 */
@@ -1193,9 +1371,12 @@ static int radius_client_init_acct(struct radius_client_data *radius)
        struct hostapd_radius_servers *conf = radius->conf;
        int ok = 0;
 
+       radius_close_acct_sockets(radius);
+
        radius->acct_serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
        if (radius->acct_serv_sock < 0)
-               perror("socket[PF_INET,SOCK_DGRAM]");
+               wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s",
+                          strerror(errno));
        else {
                radius_client_disable_pmtu_discovery(radius->acct_serv_sock);
                ok++;
@@ -1204,7 +1385,8 @@ static int radius_client_init_acct(struct radius_client_data *radius)
 #ifdef CONFIG_IPV6
        radius->acct_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
        if (radius->acct_serv_sock6 < 0)
-               perror("socket[PF_INET6,SOCK_DGRAM]");
+               wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s",
+                          strerror(errno));
        else
                ok++;
 #endif /* CONFIG_IPV6 */
@@ -1220,8 +1402,8 @@ static int radius_client_init_acct(struct radius_client_data *radius)
            eloop_register_read_sock(radius->acct_serv_sock,
                                     radius_client_receive, radius,
                                     (void *) RADIUS_ACCT)) {
-               printf("Could not register read socket for accounting "
-                      "server\n");
+               wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server");
+               radius_close_acct_sockets(radius);
                return -1;
        }
 
@@ -1230,8 +1412,8 @@ static int radius_client_init_acct(struct radius_client_data *radius)
            eloop_register_read_sock(radius->acct_serv_sock6,
                                     radius_client_receive, radius,
                                     (void *) RADIUS_ACCT)) {
-               printf("Could not register read socket for accounting "
-                      "server\n");
+               wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server");
+               radius_close_acct_sockets(radius);
                return -1;
        }
 #endif /* CONFIG_IPV6 */
@@ -1293,16 +1475,8 @@ void radius_client_deinit(struct radius_client_data *radius)
        if (!radius)
                return;
 
-       if (radius->auth_serv_sock >= 0)
-               eloop_unregister_read_sock(radius->auth_serv_sock);
-       if (radius->acct_serv_sock >= 0)
-               eloop_unregister_read_sock(radius->acct_serv_sock);
-#ifdef CONFIG_IPV6
-       if (radius->auth_serv_sock6 >= 0)
-               eloop_unregister_read_sock(radius->auth_serv_sock6);
-       if (radius->acct_serv_sock6 >= 0)
-               eloop_unregister_read_sock(radius->acct_serv_sock6);
-#endif /* CONFIG_IPV6 */
+       radius_close_auth_sockets(radius);
+       radius_close_acct_sockets(radius);
 
        eloop_cancel_timeout(radius_retry_primary_timer, radius, NULL);
 
@@ -1462,11 +1636,16 @@ static int radius_client_dump_acct_server(char *buf, size_t buflen,
 int radius_client_get_mib(struct radius_client_data *radius, char *buf,
                          size_t buflen)
 {
-       struct hostapd_radius_servers *conf = radius->conf;
+       struct hostapd_radius_servers *conf;
        int i;
        struct hostapd_radius_server *serv;
        int count = 0;
 
+       if (!radius)
+               return 0;
+
+       conf = radius->conf;
+
        if (conf->auth_servers) {
                for (i = 0; i < conf->num_auth_servers; i++) {
                        serv = &conf->auth_servers[i];
@@ -1489,3 +1668,11 @@ int radius_client_get_mib(struct radius_client_data *radius, char *buf,
 
        return count;
 }
+
+
+void radius_client_reconfig(struct radius_client_data *radius,
+                           struct hostapd_radius_servers *conf)
+{
+       if (radius)
+               radius->conf = conf;
+}