27ebadccfb
In the previous RADIUS client implementation, when there are multiple RADIUS servers, we kept trying the next server when the current message can not be acked. It leads to endless retry when all the RADIUS servers are down. Fix this by keeping a counter for the accumulated retransmit attempts for the message, and guarantee that after all the servers failover RADIUS_CLIENT_MAX_FAILOVER times the message will be dropped. Another issue with the previous code was that the decision regarding whether the server should fail over was made immediately after we send out the message. This patch guarantees we consider whether a server needs failover after pending ack times out. Signed-off-by: Bo Chen<bochen@meraki.com>
1700 lines
45 KiB
C
1700 lines
45 KiB
C
/*
|
|
* RADIUS client
|
|
* Copyright (c) 2002-2015, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include "common.h"
|
|
#include "radius.h"
|
|
#include "radius_client.h"
|
|
#include "eloop.h"
|
|
|
|
/* Defaults for RADIUS retransmit values (exponential backoff) */
|
|
|
|
/**
|
|
* RADIUS_CLIENT_FIRST_WAIT - RADIUS client timeout for first retry in seconds
|
|
*/
|
|
#define RADIUS_CLIENT_FIRST_WAIT 3
|
|
|
|
/**
|
|
* RADIUS_CLIENT_MAX_WAIT - RADIUS client maximum retry timeout in seconds
|
|
*/
|
|
#define RADIUS_CLIENT_MAX_WAIT 120
|
|
|
|
/**
|
|
* RADIUS_CLIENT_MAX_FAILOVER - RADIUS client maximum retries
|
|
*
|
|
* Maximum number of server failovers before the entry is removed from
|
|
* retransmit list.
|
|
*/
|
|
#define RADIUS_CLIENT_MAX_FAILOVER 3
|
|
|
|
/**
|
|
* RADIUS_CLIENT_MAX_ENTRIES - RADIUS client maximum pending messages
|
|
*
|
|
* Maximum number of entries in retransmit list (oldest entries will be
|
|
* removed, if this limit is exceeded).
|
|
*/
|
|
#define RADIUS_CLIENT_MAX_ENTRIES 30
|
|
|
|
/**
|
|
* RADIUS_CLIENT_NUM_FAILOVER - RADIUS client failover point
|
|
*
|
|
* The number of failed retry attempts after which the RADIUS server will be
|
|
* changed (if one of more backup servers are configured).
|
|
*/
|
|
#define RADIUS_CLIENT_NUM_FAILOVER 4
|
|
|
|
|
|
/**
|
|
* struct radius_rx_handler - RADIUS client RX handler
|
|
*
|
|
* This data structure is used internally inside the RADIUS client module to
|
|
* store registered RX handlers. These handlers are registered by calls to
|
|
* radius_client_register() and unregistered when the RADIUS client is
|
|
* deinitialized with a call to radius_client_deinit().
|
|
*/
|
|
struct radius_rx_handler {
|
|
/**
|
|
* handler - Received RADIUS message handler
|
|
*/
|
|
RadiusRxResult (*handler)(struct radius_msg *msg,
|
|
struct radius_msg *req,
|
|
const u8 *shared_secret,
|
|
size_t shared_secret_len,
|
|
void *data);
|
|
|
|
/**
|
|
* data - Context data for the handler
|
|
*/
|
|
void *data;
|
|
};
|
|
|
|
|
|
/**
|
|
* struct radius_msg_list - RADIUS client message retransmit list
|
|
*
|
|
* This data structure is used internally inside the RADIUS client module to
|
|
* store pending RADIUS requests that may still need to be retransmitted.
|
|
*/
|
|
struct radius_msg_list {
|
|
/**
|
|
* addr - STA/client address
|
|
*
|
|
* This is used to find RADIUS messages for the same STA.
|
|
*/
|
|
u8 addr[ETH_ALEN];
|
|
|
|
/**
|
|
* msg - RADIUS message
|
|
*/
|
|
struct radius_msg *msg;
|
|
|
|
/**
|
|
* msg_type - Message type
|
|
*/
|
|
RadiusType msg_type;
|
|
|
|
/**
|
|
* first_try - Time of the first transmission attempt
|
|
*/
|
|
os_time_t first_try;
|
|
|
|
/**
|
|
* next_try - Time for the next transmission attempt
|
|
*/
|
|
os_time_t next_try;
|
|
|
|
/**
|
|
* attempts - Number of transmission attempts for one server
|
|
*/
|
|
int attempts;
|
|
|
|
/**
|
|
* accu_attempts - Number of accumulated attempts
|
|
*/
|
|
int accu_attempts;
|
|
|
|
/**
|
|
* next_wait - Next retransmission wait time in seconds
|
|
*/
|
|
int next_wait;
|
|
|
|
/**
|
|
* last_attempt - Time of the last transmission attempt
|
|
*/
|
|
struct os_reltime last_attempt;
|
|
|
|
/**
|
|
* shared_secret - Shared secret with the target RADIUS server
|
|
*/
|
|
const u8 *shared_secret;
|
|
|
|
/**
|
|
* shared_secret_len - shared_secret length in octets
|
|
*/
|
|
size_t shared_secret_len;
|
|
|
|
/* TODO: server config with failover to backup server(s) */
|
|
|
|
/**
|
|
* next - Next message in the list
|
|
*/
|
|
struct radius_msg_list *next;
|
|
};
|
|
|
|
|
|
/**
|
|
* struct radius_client_data - Internal RADIUS client data
|
|
*
|
|
* This data structure is used internally inside the RADIUS client module.
|
|
* External users allocate this by calling radius_client_init() and free it by
|
|
* calling radius_client_deinit(). The pointer to this opaque data is used in
|
|
* calls to other functions as an identifier for the RADIUS client instance.
|
|
*/
|
|
struct radius_client_data {
|
|
/**
|
|
* ctx - Context pointer for hostapd_logger() callbacks
|
|
*/
|
|
void *ctx;
|
|
|
|
/**
|
|
* conf - RADIUS client configuration (list of RADIUS servers to use)
|
|
*/
|
|
struct hostapd_radius_servers *conf;
|
|
|
|
/**
|
|
* auth_serv_sock - IPv4 socket for RADIUS authentication messages
|
|
*/
|
|
int auth_serv_sock;
|
|
|
|
/**
|
|
* acct_serv_sock - IPv4 socket for RADIUS accounting messages
|
|
*/
|
|
int acct_serv_sock;
|
|
|
|
/**
|
|
* auth_serv_sock6 - IPv6 socket for RADIUS authentication messages
|
|
*/
|
|
int auth_serv_sock6;
|
|
|
|
/**
|
|
* acct_serv_sock6 - IPv6 socket for RADIUS accounting messages
|
|
*/
|
|
int acct_serv_sock6;
|
|
|
|
/**
|
|
* auth_sock - Currently used socket for RADIUS authentication server
|
|
*/
|
|
int auth_sock;
|
|
|
|
/**
|
|
* acct_sock - Currently used socket for RADIUS accounting server
|
|
*/
|
|
int acct_sock;
|
|
|
|
/**
|
|
* auth_handlers - Authentication message handlers
|
|
*/
|
|
struct radius_rx_handler *auth_handlers;
|
|
|
|
/**
|
|
* num_auth_handlers - Number of handlers in auth_handlers
|
|
*/
|
|
size_t num_auth_handlers;
|
|
|
|
/**
|
|
* acct_handlers - Accounting message handlers
|
|
*/
|
|
struct radius_rx_handler *acct_handlers;
|
|
|
|
/**
|
|
* num_acct_handlers - Number of handlers in acct_handlers
|
|
*/
|
|
size_t num_acct_handlers;
|
|
|
|
/**
|
|
* msgs - Pending outgoing RADIUS messages
|
|
*/
|
|
struct radius_msg_list *msgs;
|
|
|
|
/**
|
|
* num_msgs - Number of pending messages in the msgs list
|
|
*/
|
|
size_t num_msgs;
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
|
|
static int
|
|
radius_change_server(struct radius_client_data *radius,
|
|
struct hostapd_radius_server *nserv,
|
|
struct hostapd_radius_server *oserv,
|
|
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)
|
|
{
|
|
radius_msg_free(req->msg);
|
|
os_free(req);
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_register - Register a RADIUS client RX handler
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
* @msg_type: RADIUS client type (RADIUS_AUTH or RADIUS_ACCT)
|
|
* @handler: Handler for received RADIUS messages
|
|
* @data: Context pointer for handler callbacks
|
|
* Returns: 0 on success, -1 on failure
|
|
*
|
|
* This function is used to register a handler for processing received RADIUS
|
|
* authentication and accounting messages. The handler() callback function will
|
|
* be called whenever a RADIUS message is received from the active server.
|
|
*
|
|
* There can be multiple registered RADIUS message handlers. The handlers will
|
|
* be called in order until one of them indicates that it has processed or
|
|
* queued the message.
|
|
*/
|
|
int radius_client_register(struct radius_client_data *radius,
|
|
RadiusType msg_type,
|
|
RadiusRxResult (*handler)(struct radius_msg *msg,
|
|
struct radius_msg *req,
|
|
const u8 *shared_secret,
|
|
size_t shared_secret_len,
|
|
void *data),
|
|
void *data)
|
|
{
|
|
struct radius_rx_handler **handlers, *newh;
|
|
size_t *num;
|
|
|
|
if (msg_type == RADIUS_ACCT) {
|
|
handlers = &radius->acct_handlers;
|
|
num = &radius->num_acct_handlers;
|
|
} else {
|
|
handlers = &radius->auth_handlers;
|
|
num = &radius->num_auth_handlers;
|
|
}
|
|
|
|
newh = os_realloc_array(*handlers, *num + 1,
|
|
sizeof(struct radius_rx_handler));
|
|
if (newh == NULL)
|
|
return -1;
|
|
|
|
newh[*num].handler = handler;
|
|
newh[*num].data = data;
|
|
(*num)++;
|
|
*handlers = newh;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
wpa_printf(MSG_INFO, "send[RADIUS,s=%d]: %s", s, strerror(errno));
|
|
if (_errno == ENOTCONN || _errno == EDESTADDRREQ || _errno == EINVAL ||
|
|
_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");
|
|
if (msg_type == RADIUS_ACCT ||
|
|
msg_type == RADIUS_ACCT_INTERIM) {
|
|
radius_client_init_acct(radius);
|
|
return 0;
|
|
} else {
|
|
radius_client_init_auth(radius);
|
|
return 1;
|
|
}
|
|
}
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int radius_client_retransmit(struct radius_client_data *radius,
|
|
struct radius_msg_list *entry,
|
|
os_time_t now)
|
|
{
|
|
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;
|
|
int num_servers;
|
|
|
|
if (entry->msg_type == RADIUS_ACCT ||
|
|
entry->msg_type == RADIUS_ACCT_INTERIM) {
|
|
num_servers = conf->num_acct_servers;
|
|
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++;
|
|
else {
|
|
conf->acct_server->timeouts++;
|
|
conf->acct_server->retransmissions++;
|
|
}
|
|
} else {
|
|
num_servers = conf->num_auth_servers;
|
|
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++;
|
|
else {
|
|
conf->auth_server->timeouts++;
|
|
conf->auth_server->retransmissions++;
|
|
}
|
|
}
|
|
|
|
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 */
|
|
if (entry->accu_attempts > RADIUS_CLIENT_MAX_FAILOVER *
|
|
RADIUS_CLIENT_NUM_FAILOVER * num_servers) {
|
|
wpa_printf(MSG_INFO,
|
|
"RADIUS: Removing un-ACKed message due to too many failed retransmit attempts");
|
|
return 1;
|
|
}
|
|
|
|
entry->attempts++;
|
|
entry->accu_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_reltime(&entry->last_attempt);
|
|
buf = radius_msg_get_buf(entry->msg);
|
|
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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void radius_client_timer(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct radius_client_data *radius = eloop_ctx;
|
|
struct os_reltime now;
|
|
os_time_t first;
|
|
struct radius_msg_list *entry, *prev, *tmp;
|
|
int auth_failover = 0, acct_failover = 0;
|
|
size_t prev_num_msgs;
|
|
int s;
|
|
|
|
entry = radius->msgs;
|
|
if (!entry)
|
|
return;
|
|
|
|
os_get_reltime(&now);
|
|
|
|
while (entry) {
|
|
if (now.sec >= entry->next_try) {
|
|
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++;
|
|
else
|
|
auth_failover++;
|
|
}
|
|
}
|
|
entry = entry->next;
|
|
}
|
|
|
|
if (auth_failover)
|
|
radius_client_auth_failover(radius);
|
|
|
|
if (acct_failover)
|
|
radius_client_acct_failover(radius);
|
|
|
|
entry = radius->msgs;
|
|
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)
|
|
prev->next = entry->next;
|
|
else
|
|
radius->msgs = entry->next;
|
|
|
|
tmp = entry;
|
|
entry = entry->next;
|
|
radius_client_msg_free(tmp);
|
|
radius->num_msgs--;
|
|
continue;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (first == 0 || entry->next_try < first)
|
|
first = entry->next_try;
|
|
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
|
|
if (radius->msgs) {
|
|
if (first < now.sec)
|
|
first = now.sec;
|
|
eloop_cancel_timeout(radius_client_timer, radius, NULL);
|
|
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",
|
|
(long int) (first - now.sec));
|
|
}
|
|
}
|
|
|
|
|
|
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];
|
|
|
|
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++;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
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_reltime now;
|
|
os_time_t first;
|
|
struct radius_msg_list *entry;
|
|
|
|
eloop_cancel_timeout(radius_client_timer, radius, NULL);
|
|
|
|
if (radius->msgs == NULL) {
|
|
return;
|
|
}
|
|
|
|
first = 0;
|
|
for (entry = radius->msgs; entry; entry = entry->next) {
|
|
if (first == 0 || entry->next_try < first)
|
|
first = entry->next_try;
|
|
}
|
|
|
|
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", (long int) (first - now.sec));
|
|
}
|
|
|
|
|
|
static void radius_client_list_add(struct radius_client_data *radius,
|
|
struct radius_msg *msg,
|
|
RadiusType msg_type,
|
|
const u8 *shared_secret,
|
|
size_t shared_secret_len, const u8 *addr)
|
|
{
|
|
struct radius_msg_list *entry, *prev;
|
|
|
|
if (eloop_terminated()) {
|
|
/* No point in adding entries to retransmit queue since event
|
|
* loop has already been terminated. */
|
|
radius_msg_free(msg);
|
|
return;
|
|
}
|
|
|
|
entry = os_zalloc(sizeof(*entry));
|
|
if (entry == NULL) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Failed to add packet into retransmit list");
|
|
radius_msg_free(msg);
|
|
return;
|
|
}
|
|
|
|
if (addr)
|
|
os_memcpy(entry->addr, addr, ETH_ALEN);
|
|
entry->msg = msg;
|
|
entry->msg_type = msg_type;
|
|
entry->shared_secret = shared_secret;
|
|
entry->shared_secret_len = shared_secret_len;
|
|
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;
|
|
entry->accu_attempts = 1;
|
|
entry->next_wait = RADIUS_CLIENT_FIRST_WAIT * 2;
|
|
if (entry->next_wait > RADIUS_CLIENT_MAX_WAIT)
|
|
entry->next_wait = RADIUS_CLIENT_MAX_WAIT;
|
|
entry->next = radius->msgs;
|
|
radius->msgs = entry;
|
|
radius_client_update_timeout(radius);
|
|
|
|
if (radius->num_msgs >= RADIUS_CLIENT_MAX_ENTRIES) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Removing the oldest un-ACKed packet due to retransmit list limits");
|
|
prev = NULL;
|
|
while (entry->next) {
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
if (prev) {
|
|
prev->next = NULL;
|
|
radius_client_msg_free(entry);
|
|
}
|
|
} else
|
|
radius->num_msgs++;
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_send - Send a RADIUS request
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
* @msg: RADIUS message to be sent
|
|
* @msg_type: Message type (RADIUS_AUTH, RADIUS_ACCT, RADIUS_ACCT_INTERIM)
|
|
* @addr: MAC address of the device related to this message or %NULL
|
|
* Returns: 0 on success, -1 on failure
|
|
*
|
|
* 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 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_FAILOVER * RADIUS_CLIENT_NUM_FAILOVER) 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().
|
|
*/
|
|
int radius_client_send(struct radius_client_data *radius,
|
|
struct radius_msg *msg, RadiusType msg_type,
|
|
const u8 *addr)
|
|
{
|
|
struct hostapd_radius_servers *conf = radius->conf;
|
|
const u8 *shared_secret;
|
|
size_t shared_secret_len;
|
|
char *name;
|
|
int s, res;
|
|
struct wpabuf *buf;
|
|
|
|
if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM) {
|
|
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,
|
|
"No accounting server configured");
|
|
return -1;
|
|
}
|
|
shared_secret = conf->acct_server->shared_secret;
|
|
shared_secret_len = conf->acct_server->shared_secret_len;
|
|
radius_msg_finish_acct(msg, shared_secret, shared_secret_len);
|
|
name = "accounting";
|
|
s = radius->acct_sock;
|
|
conf->acct_server->requests++;
|
|
} else {
|
|
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,
|
|
"No authentication server configured");
|
|
return -1;
|
|
}
|
|
shared_secret = conf->auth_server->shared_secret;
|
|
shared_secret_len = conf->auth_server->shared_secret_len;
|
|
radius_msg_finish(msg, shared_secret, shared_secret_len);
|
|
name = "authentication";
|
|
s = radius->auth_sock;
|
|
conf->auth_server->requests++;
|
|
}
|
|
|
|
hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
|
|
HOSTAPD_LEVEL_DEBUG, "Sending RADIUS message to %s "
|
|
"server", name);
|
|
if (conf->msg_dumps)
|
|
radius_msg_dump(msg);
|
|
|
|
buf = radius_msg_get_buf(msg);
|
|
res = send(s, wpabuf_head(buf), wpabuf_len(buf), 0);
|
|
if (res < 0)
|
|
radius_client_handle_send_error(radius, s, msg_type);
|
|
|
|
radius_client_list_add(radius, msg, msg_type, shared_secret,
|
|
shared_secret_len, addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void radius_client_receive(int sock, void *eloop_ctx, void *sock_ctx)
|
|
{
|
|
struct radius_client_data *radius = eloop_ctx;
|
|
struct hostapd_radius_servers *conf = radius->conf;
|
|
RadiusType msg_type = (RadiusType) sock_ctx;
|
|
int len, roundtrip;
|
|
unsigned char buf[3000];
|
|
struct radius_msg *msg;
|
|
struct radius_hdr *hdr;
|
|
struct radius_rx_handler *handlers;
|
|
size_t num_handlers, i;
|
|
struct radius_msg_list *req, *prev_req;
|
|
struct os_reltime now;
|
|
struct hostapd_radius_server *rconf;
|
|
int invalid_authenticator = 0;
|
|
|
|
if (msg_type == RADIUS_ACCT) {
|
|
handlers = radius->acct_handlers;
|
|
num_handlers = radius->num_acct_handlers;
|
|
rconf = conf->acct_server;
|
|
} else {
|
|
handlers = radius->auth_handlers;
|
|
num_handlers = radius->num_auth_handlers;
|
|
rconf = conf->auth_server;
|
|
}
|
|
|
|
len = recv(sock, buf, sizeof(buf), MSG_DONTWAIT);
|
|
if (len < 0) {
|
|
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)) {
|
|
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) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Parsing incoming frame failed");
|
|
rconf->malformed_responses++;
|
|
return;
|
|
}
|
|
hdr = radius_msg_get_hdr(msg);
|
|
|
|
hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
|
|
HOSTAPD_LEVEL_DEBUG, "Received RADIUS message");
|
|
if (conf->msg_dumps)
|
|
radius_msg_dump(msg);
|
|
|
|
switch (hdr->code) {
|
|
case RADIUS_CODE_ACCESS_ACCEPT:
|
|
rconf->access_accepts++;
|
|
break;
|
|
case RADIUS_CODE_ACCESS_REJECT:
|
|
rconf->access_rejects++;
|
|
break;
|
|
case RADIUS_CODE_ACCESS_CHALLENGE:
|
|
rconf->access_challenges++;
|
|
break;
|
|
case RADIUS_CODE_ACCOUNTING_RESPONSE:
|
|
rconf->responses++;
|
|
break;
|
|
}
|
|
|
|
prev_req = NULL;
|
|
req = radius->msgs;
|
|
while (req) {
|
|
/* TODO: also match by src addr:port of the packet when using
|
|
* alternative RADIUS servers (?) */
|
|
if ((req->msg_type == msg_type ||
|
|
(req->msg_type == RADIUS_ACCT_INTERIM &&
|
|
msg_type == RADIUS_ACCT)) &&
|
|
radius_msg_get_hdr(req->msg)->identifier ==
|
|
hdr->identifier)
|
|
break;
|
|
|
|
prev_req = req;
|
|
req = req->next;
|
|
}
|
|
|
|
if (req == NULL) {
|
|
hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
|
|
HOSTAPD_LEVEL_DEBUG,
|
|
"No matching RADIUS request found (type=%d "
|
|
"id=%d) - dropping packet",
|
|
msg_type, hdr->identifier);
|
|
goto fail;
|
|
}
|
|
|
|
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,
|
|
HOSTAPD_LEVEL_DEBUG,
|
|
"Received RADIUS packet matched with a pending "
|
|
"request, round trip time %d.%02d sec",
|
|
roundtrip / 100, roundtrip % 100);
|
|
rconf->round_trip_time = roundtrip;
|
|
|
|
/* Remove ACKed RADIUS packet from retransmit list */
|
|
if (prev_req)
|
|
prev_req->next = req->next;
|
|
else
|
|
radius->msgs = req->next;
|
|
radius->num_msgs--;
|
|
|
|
for (i = 0; i < num_handlers; i++) {
|
|
RadiusRxResult res;
|
|
res = handlers[i].handler(msg, req->msg, req->shared_secret,
|
|
req->shared_secret_len,
|
|
handlers[i].data);
|
|
switch (res) {
|
|
case RADIUS_RX_PROCESSED:
|
|
radius_msg_free(msg);
|
|
/* fall through */
|
|
case RADIUS_RX_QUEUED:
|
|
radius_client_msg_free(req);
|
|
return;
|
|
case RADIUS_RX_INVALID_AUTHENTICATOR:
|
|
invalid_authenticator++;
|
|
/* fall through */
|
|
case RADIUS_RX_UNKNOWN:
|
|
/* continue with next handler */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (invalid_authenticator)
|
|
rconf->bad_authenticators++;
|
|
else
|
|
rconf->unknown_types++;
|
|
hostapd_logger(radius->ctx, req->addr, HOSTAPD_MODULE_RADIUS,
|
|
HOSTAPD_LEVEL_DEBUG, "No RADIUS RX handler found "
|
|
"(type=%d code=%d id=%d)%s - dropping packet",
|
|
msg_type, hdr->code, hdr->identifier,
|
|
invalid_authenticator ? " [INVALID AUTHENTICATOR]" :
|
|
"");
|
|
radius_client_msg_free(req);
|
|
|
|
fail:
|
|
radius_msg_free(msg);
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_get_id - Get an identifier for a new RADIUS message
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
* Returns: Allocated identifier
|
|
*
|
|
* This function is used to fetch a unique (among pending requests) identifier
|
|
* for a new RADIUS message.
|
|
*/
|
|
u8 radius_client_get_id(struct radius_client_data *radius)
|
|
{
|
|
struct radius_msg_list *entry, *prev, *_remove;
|
|
u8 id = radius->next_radius_identifier++;
|
|
|
|
/* remove entries with matching id from retransmit list to avoid
|
|
* using new reply from the RADIUS server with an old request */
|
|
entry = radius->msgs;
|
|
prev = NULL;
|
|
while (entry) {
|
|
if (radius_msg_get_hdr(entry->msg)->identifier == id) {
|
|
hostapd_logger(radius->ctx, entry->addr,
|
|
HOSTAPD_MODULE_RADIUS,
|
|
HOSTAPD_LEVEL_DEBUG,
|
|
"Removing pending RADIUS message, "
|
|
"since its id (%d) is reused", id);
|
|
if (prev)
|
|
prev->next = entry->next;
|
|
else
|
|
radius->msgs = entry->next;
|
|
_remove = entry;
|
|
} else {
|
|
_remove = NULL;
|
|
prev = entry;
|
|
}
|
|
entry = entry->next;
|
|
|
|
if (_remove)
|
|
radius_client_msg_free(_remove);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_flush - Flush all pending RADIUS client messages
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
* @only_auth: Whether only authentication messages are removed
|
|
*/
|
|
void radius_client_flush(struct radius_client_data *radius, int only_auth)
|
|
{
|
|
struct radius_msg_list *entry, *prev, *tmp;
|
|
|
|
if (!radius)
|
|
return;
|
|
|
|
prev = NULL;
|
|
entry = radius->msgs;
|
|
|
|
while (entry) {
|
|
if (!only_auth || entry->msg_type == RADIUS_AUTH) {
|
|
if (prev)
|
|
prev->next = entry->next;
|
|
else
|
|
radius->msgs = entry->next;
|
|
|
|
tmp = entry;
|
|
entry = entry->next;
|
|
radius_client_msg_free(tmp);
|
|
radius->num_msgs--;
|
|
} else {
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
}
|
|
|
|
if (radius->msgs == NULL)
|
|
eloop_cancel_timeout(radius_client_timer, radius, NULL);
|
|
}
|
|
|
|
|
|
static void radius_client_update_acct_msgs(struct radius_client_data *radius,
|
|
const u8 *shared_secret,
|
|
size_t shared_secret_len)
|
|
{
|
|
struct radius_msg_list *entry;
|
|
|
|
if (!radius)
|
|
return;
|
|
|
|
for (entry = radius->msgs; entry; entry = entry->next) {
|
|
if (entry->msg_type == RADIUS_ACCT) {
|
|
entry->shared_secret = shared_secret;
|
|
entry->shared_secret_len = shared_secret_len;
|
|
radius_msg_finish_acct(entry->msg, shared_secret,
|
|
shared_secret_len);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
radius_change_server(struct radius_client_data *radius,
|
|
struct hostapd_radius_server *nserv,
|
|
struct hostapd_radius_server *oserv,
|
|
int sock, int sock6, int auth)
|
|
{
|
|
struct sockaddr_in serv, claddr;
|
|
#ifdef CONFIG_IPV6
|
|
struct sockaddr_in6 serv6, claddr6;
|
|
#endif /* CONFIG_IPV6 */
|
|
struct sockaddr *addr, *cl_addr;
|
|
socklen_t addrlen, claddrlen;
|
|
char abuf[50];
|
|
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,
|
|
"%s server %s:%d",
|
|
auth ? "Authentication" : "Accounting",
|
|
hostapd_ip_txt(&nserv->addr, abuf, sizeof(abuf)),
|
|
nserv->port);
|
|
|
|
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
|
|
* since they would require more changes and the new RADIUS
|
|
* server may not be prepared to receive them anyway due to
|
|
* missing state information. Client will likely retry
|
|
* authentication, so this should not be an issue. */
|
|
if (auth)
|
|
radius_client_flush(radius, 1);
|
|
else {
|
|
radius_client_update_acct_msgs(
|
|
radius, nserv->shared_secret,
|
|
nserv->shared_secret_len);
|
|
}
|
|
}
|
|
|
|
/* Reset retry counters */
|
|
for (entry = radius->msgs; oserv && entry; entry = entry->next) {
|
|
if ((auth && entry->msg_type != RADIUS_AUTH) ||
|
|
(!auth && entry->msg_type != RADIUS_ACCT))
|
|
continue;
|
|
entry->next_try = entry->first_try + RADIUS_CLIENT_FIRST_WAIT;
|
|
entry->attempts = 1;
|
|
entry->next_wait = RADIUS_CLIENT_FIRST_WAIT * 2;
|
|
}
|
|
|
|
if (radius->msgs) {
|
|
eloop_cancel_timeout(radius_client_timer, radius, NULL);
|
|
eloop_register_timeout(RADIUS_CLIENT_FIRST_WAIT, 0,
|
|
radius_client_timer, radius, NULL);
|
|
}
|
|
|
|
switch (nserv->addr.af) {
|
|
case AF_INET:
|
|
os_memset(&serv, 0, sizeof(serv));
|
|
serv.sin_family = AF_INET;
|
|
serv.sin_addr.s_addr = nserv->addr.u.v4.s_addr;
|
|
serv.sin_port = htons(nserv->port);
|
|
addr = (struct sockaddr *) &serv;
|
|
addrlen = sizeof(serv);
|
|
sel_sock = sock;
|
|
break;
|
|
#ifdef CONFIG_IPV6
|
|
case AF_INET6:
|
|
os_memset(&serv6, 0, sizeof(serv6));
|
|
serv6.sin6_family = AF_INET6;
|
|
os_memcpy(&serv6.sin6_addr, &nserv->addr.u.v6,
|
|
sizeof(struct in6_addr));
|
|
serv6.sin6_port = htons(nserv->port);
|
|
addr = (struct sockaddr *) &serv6;
|
|
addrlen = sizeof(serv6);
|
|
sel_sock = sock6;
|
|
break;
|
|
#endif /* CONFIG_IPV6 */
|
|
default:
|
|
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:
|
|
os_memset(&claddr, 0, sizeof(claddr));
|
|
claddr.sin_family = AF_INET;
|
|
claddr.sin_addr.s_addr = conf->client_addr.u.v4.s_addr;
|
|
claddr.sin_port = htons(0);
|
|
cl_addr = (struct sockaddr *) &claddr;
|
|
claddrlen = sizeof(claddr);
|
|
break;
|
|
#ifdef CONFIG_IPV6
|
|
case AF_INET6:
|
|
os_memset(&claddr6, 0, sizeof(claddr6));
|
|
claddr6.sin6_family = AF_INET6;
|
|
os_memcpy(&claddr6.sin6_addr, &conf->client_addr.u.v6,
|
|
sizeof(struct in6_addr));
|
|
claddr6.sin6_port = htons(0);
|
|
cl_addr = (struct sockaddr *) &claddr6;
|
|
claddrlen = sizeof(claddr6);
|
|
break;
|
|
#endif /* CONFIG_IPV6 */
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
if (bind(sel_sock, cl_addr, claddrlen) < 0) {
|
|
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) {
|
|
wpa_printf(MSG_INFO, "connect[radius]: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
#ifndef CONFIG_NATIVE_WINDOWS
|
|
switch (nserv->addr.af) {
|
|
case AF_INET:
|
|
claddrlen = sizeof(claddr);
|
|
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);
|
|
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 */
|
|
}
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|
|
|
|
if (auth)
|
|
radius->auth_sock = sel_sock;
|
|
else
|
|
radius->acct_sock = sel_sock;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void radius_retry_primary_timer(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct radius_client_data *radius = eloop_ctx;
|
|
struct hostapd_radius_servers *conf = radius->conf;
|
|
struct hostapd_radius_server *oserv;
|
|
|
|
if (radius->auth_sock >= 0 && conf->auth_servers &&
|
|
conf->auth_server != conf->auth_servers) {
|
|
oserv = conf->auth_server;
|
|
conf->auth_server = conf->auth_servers;
|
|
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;
|
|
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)
|
|
eloop_register_timeout(conf->retry_primary_interval, 0,
|
|
radius_retry_primary_timer, radius,
|
|
NULL);
|
|
}
|
|
|
|
|
|
static int radius_client_disable_pmtu_discovery(int s)
|
|
{
|
|
int r = -1;
|
|
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
|
|
/* Turn off Path MTU discovery on IPv4/UDP sockets. */
|
|
int action = IP_PMTUDISC_DONT;
|
|
r = setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action,
|
|
sizeof(action));
|
|
if (r == -1)
|
|
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)
|
|
wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s",
|
|
strerror(errno));
|
|
else {
|
|
radius_client_disable_pmtu_discovery(radius->auth_serv_sock);
|
|
ok++;
|
|
}
|
|
|
|
#ifdef CONFIG_IPV6
|
|
radius->auth_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
|
|
if (radius->auth_serv_sock6 < 0)
|
|
wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s",
|
|
strerror(errno));
|
|
else
|
|
ok++;
|
|
#endif /* CONFIG_IPV6 */
|
|
|
|
if (ok == 0)
|
|
return -1;
|
|
|
|
radius_change_server(radius, conf->auth_server, NULL,
|
|
radius->auth_serv_sock, radius->auth_serv_sock6,
|
|
1);
|
|
|
|
if (radius->auth_serv_sock >= 0 &&
|
|
eloop_register_read_sock(radius->auth_serv_sock,
|
|
radius_client_receive, radius,
|
|
(void *) RADIUS_AUTH)) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server");
|
|
radius_close_auth_sockets(radius);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef CONFIG_IPV6
|
|
if (radius->auth_serv_sock6 >= 0 &&
|
|
eloop_register_read_sock(radius->auth_serv_sock6,
|
|
radius_client_receive, radius,
|
|
(void *) RADIUS_AUTH)) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server");
|
|
radius_close_auth_sockets(radius);
|
|
return -1;
|
|
}
|
|
#endif /* CONFIG_IPV6 */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
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)
|
|
wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s",
|
|
strerror(errno));
|
|
else {
|
|
radius_client_disable_pmtu_discovery(radius->acct_serv_sock);
|
|
ok++;
|
|
}
|
|
|
|
#ifdef CONFIG_IPV6
|
|
radius->acct_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
|
|
if (radius->acct_serv_sock6 < 0)
|
|
wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s",
|
|
strerror(errno));
|
|
else
|
|
ok++;
|
|
#endif /* CONFIG_IPV6 */
|
|
|
|
if (ok == 0)
|
|
return -1;
|
|
|
|
radius_change_server(radius, conf->acct_server, NULL,
|
|
radius->acct_serv_sock, radius->acct_serv_sock6,
|
|
0);
|
|
|
|
if (radius->acct_serv_sock >= 0 &&
|
|
eloop_register_read_sock(radius->acct_serv_sock,
|
|
radius_client_receive, radius,
|
|
(void *) RADIUS_ACCT)) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server");
|
|
radius_close_acct_sockets(radius);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef CONFIG_IPV6
|
|
if (radius->acct_serv_sock6 >= 0 &&
|
|
eloop_register_read_sock(radius->acct_serv_sock6,
|
|
radius_client_receive, radius,
|
|
(void *) RADIUS_ACCT)) {
|
|
wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server");
|
|
radius_close_acct_sockets(radius);
|
|
return -1;
|
|
}
|
|
#endif /* CONFIG_IPV6 */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_init - Initialize RADIUS client
|
|
* @ctx: Callback context to be used in hostapd_logger() calls
|
|
* @conf: RADIUS client configuration (RADIUS servers)
|
|
* Returns: Pointer to private RADIUS client context or %NULL on failure
|
|
*
|
|
* The caller is responsible for keeping the configuration data available for
|
|
* the lifetime of the RADIUS client, i.e., until radius_client_deinit() is
|
|
* called for the returned context pointer.
|
|
*/
|
|
struct radius_client_data *
|
|
radius_client_init(void *ctx, struct hostapd_radius_servers *conf)
|
|
{
|
|
struct radius_client_data *radius;
|
|
|
|
radius = os_zalloc(sizeof(struct radius_client_data));
|
|
if (radius == NULL)
|
|
return NULL;
|
|
|
|
radius->ctx = ctx;
|
|
radius->conf = conf;
|
|
radius->auth_serv_sock = radius->acct_serv_sock =
|
|
radius->auth_serv_sock6 = radius->acct_serv_sock6 =
|
|
radius->auth_sock = radius->acct_sock = -1;
|
|
|
|
if (conf->auth_server && radius_client_init_auth(radius)) {
|
|
radius_client_deinit(radius);
|
|
return NULL;
|
|
}
|
|
|
|
if (conf->acct_server && radius_client_init_acct(radius)) {
|
|
radius_client_deinit(radius);
|
|
return NULL;
|
|
}
|
|
|
|
if (conf->retry_primary_interval)
|
|
eloop_register_timeout(conf->retry_primary_interval, 0,
|
|
radius_retry_primary_timer, radius,
|
|
NULL);
|
|
|
|
return radius;
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_deinit - Deinitialize RADIUS client
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
*/
|
|
void radius_client_deinit(struct radius_client_data *radius)
|
|
{
|
|
if (!radius)
|
|
return;
|
|
|
|
radius_close_auth_sockets(radius);
|
|
radius_close_acct_sockets(radius);
|
|
|
|
eloop_cancel_timeout(radius_retry_primary_timer, radius, NULL);
|
|
|
|
radius_client_flush(radius, 0);
|
|
os_free(radius->auth_handlers);
|
|
os_free(radius->acct_handlers);
|
|
os_free(radius);
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_flush_auth - Flush pending RADIUS messages for an address
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
* @addr: MAC address of the related device
|
|
*
|
|
* This function can be used to remove pending RADIUS authentication messages
|
|
* that are related to a specific device. The addr parameter is matched with
|
|
* the one used in radius_client_send() call that was used to transmit the
|
|
* authentication request.
|
|
*/
|
|
void radius_client_flush_auth(struct radius_client_data *radius,
|
|
const u8 *addr)
|
|
{
|
|
struct radius_msg_list *entry, *prev, *tmp;
|
|
|
|
prev = NULL;
|
|
entry = radius->msgs;
|
|
while (entry) {
|
|
if (entry->msg_type == RADIUS_AUTH &&
|
|
os_memcmp(entry->addr, addr, ETH_ALEN) == 0) {
|
|
hostapd_logger(radius->ctx, addr,
|
|
HOSTAPD_MODULE_RADIUS,
|
|
HOSTAPD_LEVEL_DEBUG,
|
|
"Removing pending RADIUS authentication"
|
|
" message for removed client");
|
|
|
|
if (prev)
|
|
prev->next = entry->next;
|
|
else
|
|
radius->msgs = entry->next;
|
|
|
|
tmp = entry;
|
|
entry = entry->next;
|
|
radius_client_msg_free(tmp);
|
|
radius->num_msgs--;
|
|
continue;
|
|
}
|
|
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
}
|
|
|
|
|
|
static int radius_client_dump_auth_server(char *buf, size_t buflen,
|
|
struct hostapd_radius_server *serv,
|
|
struct radius_client_data *cli)
|
|
{
|
|
int pending = 0;
|
|
struct radius_msg_list *msg;
|
|
char abuf[50];
|
|
|
|
if (cli) {
|
|
for (msg = cli->msgs; msg; msg = msg->next) {
|
|
if (msg->msg_type == RADIUS_AUTH)
|
|
pending++;
|
|
}
|
|
}
|
|
|
|
return os_snprintf(buf, buflen,
|
|
"radiusAuthServerIndex=%d\n"
|
|
"radiusAuthServerAddress=%s\n"
|
|
"radiusAuthClientServerPortNumber=%d\n"
|
|
"radiusAuthClientRoundTripTime=%d\n"
|
|
"radiusAuthClientAccessRequests=%u\n"
|
|
"radiusAuthClientAccessRetransmissions=%u\n"
|
|
"radiusAuthClientAccessAccepts=%u\n"
|
|
"radiusAuthClientAccessRejects=%u\n"
|
|
"radiusAuthClientAccessChallenges=%u\n"
|
|
"radiusAuthClientMalformedAccessResponses=%u\n"
|
|
"radiusAuthClientBadAuthenticators=%u\n"
|
|
"radiusAuthClientPendingRequests=%u\n"
|
|
"radiusAuthClientTimeouts=%u\n"
|
|
"radiusAuthClientUnknownTypes=%u\n"
|
|
"radiusAuthClientPacketsDropped=%u\n",
|
|
serv->index,
|
|
hostapd_ip_txt(&serv->addr, abuf, sizeof(abuf)),
|
|
serv->port,
|
|
serv->round_trip_time,
|
|
serv->requests,
|
|
serv->retransmissions,
|
|
serv->access_accepts,
|
|
serv->access_rejects,
|
|
serv->access_challenges,
|
|
serv->malformed_responses,
|
|
serv->bad_authenticators,
|
|
pending,
|
|
serv->timeouts,
|
|
serv->unknown_types,
|
|
serv->packets_dropped);
|
|
}
|
|
|
|
|
|
static int radius_client_dump_acct_server(char *buf, size_t buflen,
|
|
struct hostapd_radius_server *serv,
|
|
struct radius_client_data *cli)
|
|
{
|
|
int pending = 0;
|
|
struct radius_msg_list *msg;
|
|
char abuf[50];
|
|
|
|
if (cli) {
|
|
for (msg = cli->msgs; msg; msg = msg->next) {
|
|
if (msg->msg_type == RADIUS_ACCT ||
|
|
msg->msg_type == RADIUS_ACCT_INTERIM)
|
|
pending++;
|
|
}
|
|
}
|
|
|
|
return os_snprintf(buf, buflen,
|
|
"radiusAccServerIndex=%d\n"
|
|
"radiusAccServerAddress=%s\n"
|
|
"radiusAccClientServerPortNumber=%d\n"
|
|
"radiusAccClientRoundTripTime=%d\n"
|
|
"radiusAccClientRequests=%u\n"
|
|
"radiusAccClientRetransmissions=%u\n"
|
|
"radiusAccClientResponses=%u\n"
|
|
"radiusAccClientMalformedResponses=%u\n"
|
|
"radiusAccClientBadAuthenticators=%u\n"
|
|
"radiusAccClientPendingRequests=%u\n"
|
|
"radiusAccClientTimeouts=%u\n"
|
|
"radiusAccClientUnknownTypes=%u\n"
|
|
"radiusAccClientPacketsDropped=%u\n",
|
|
serv->index,
|
|
hostapd_ip_txt(&serv->addr, abuf, sizeof(abuf)),
|
|
serv->port,
|
|
serv->round_trip_time,
|
|
serv->requests,
|
|
serv->retransmissions,
|
|
serv->responses,
|
|
serv->malformed_responses,
|
|
serv->bad_authenticators,
|
|
pending,
|
|
serv->timeouts,
|
|
serv->unknown_types,
|
|
serv->packets_dropped);
|
|
}
|
|
|
|
|
|
/**
|
|
* radius_client_get_mib - Get RADIUS client MIB information
|
|
* @radius: RADIUS client context from radius_client_init()
|
|
* @buf: Buffer for returning MIB data in text format
|
|
* @buflen: Maximum buf length in octets
|
|
* Returns: Number of octets written into the buffer
|
|
*/
|
|
int radius_client_get_mib(struct radius_client_data *radius, char *buf,
|
|
size_t buflen)
|
|
{
|
|
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];
|
|
count += radius_client_dump_auth_server(
|
|
buf + count, buflen - count, serv,
|
|
serv == conf->auth_server ?
|
|
radius : NULL);
|
|
}
|
|
}
|
|
|
|
if (conf->acct_servers) {
|
|
for (i = 0; i < conf->num_acct_servers; i++) {
|
|
serv = &conf->acct_servers[i];
|
|
count += radius_client_dump_acct_server(
|
|
buf + count, buflen - count, serv,
|
|
serv == conf->acct_server ?
|
|
radius : NULL);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
void radius_client_reconfig(struct radius_client_data *radius,
|
|
struct hostapd_radius_servers *conf)
|
|
{
|
|
if (radius)
|
|
radius->conf = conf;
|
|
}
|