RADIUS: Add minimal accounting server support
authorJouni Malinen <j@w1.fi>
Sat, 15 Feb 2014 13:37:53 +0000 (15:37 +0200)
committerJouni Malinen <j@w1.fi>
Sat, 15 Feb 2014 14:26:48 +0000 (16:26 +0200)
This can be used to test RADIUS Accounting in hostapd.

Signed-off-by: Jouni Malinen <j@w1.fi>
hostapd/config_file.c
hostapd/hostapd.conf
src/ap/ap_config.h
src/ap/authsrv.c
src/radius/radius.c
src/radius/radius.h
src/radius/radius_server.c
src/radius/radius_server.h

index 54e4af9..19d6ad3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration file parser
- * Copyright (c) 2003-2013, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -2188,6 +2188,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                        bss->radius_server_clients = os_strdup(pos);
                } else if (os_strcmp(buf, "radius_server_auth_port") == 0) {
                        bss->radius_server_auth_port = atoi(pos);
+               } else if (os_strcmp(buf, "radius_server_acct_port") == 0) {
+                       bss->radius_server_acct_port = atoi(pos);
                } else if (os_strcmp(buf, "radius_server_ipv6") == 0) {
                        bss->radius_server_ipv6 = atoi(pos);
 #endif /* RADIUS_SERVER */
index da7817f..c503ce2 100644 (file)
@@ -971,6 +971,11 @@ own_ip_addr=127.0.0.1
 # The UDP port number for the RADIUS authentication server
 #radius_server_auth_port=1812
 
+# The UDP port number for the RADIUS accounting server
+# Commenting this out or setting this to 0 can be used to disable RADIUS
+# accounting while still enabling RADIUS authentication.
+#radius_server_acct_port=1813
+
 # Use IPv6 with RADIUS server (IPv4 will also be supported using IPv6 API)
 #radius_server_ipv6=1
 
index b4860a0..4631ca9 100644 (file)
@@ -311,6 +311,7 @@ struct hostapd_bss_config {
 
        char *radius_server_clients;
        int radius_server_auth_port;
+       int radius_server_acct_port;
        int radius_server_ipv6;
 
        char *test_socket; /* UNIX domain socket path for driver_test */
index 8bb58a6..7183aba 100644 (file)
@@ -92,6 +92,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd)
        os_memset(&srv, 0, sizeof(srv));
        srv.client_file = conf->radius_server_clients;
        srv.auth_port = conf->radius_server_auth_port;
+       srv.acct_port = conf->radius_server_acct_port;
        srv.conf_ctx = hapd;
        srv.eap_sim_db_priv = hapd->eap_sim_db_priv;
        srv.ssl_ctx = hapd->ssl_ctx;
index 494f92d..dbad848 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * RADIUS message processing
- * Copyright (c) 2002-2009, 2011-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2009, 2011-2014, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -473,6 +473,27 @@ void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
 }
 
 
+void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret,
+                                size_t secret_len, const u8 *req_authenticator)
+{
+       const u8 *addr[2];
+       size_t len[2];
+
+       msg->hdr->length = host_to_be16(wpabuf_len(msg->buf));
+       os_memcpy(msg->hdr->authenticator, req_authenticator, MD5_MAC_LEN);
+       addr[0] = wpabuf_head(msg->buf);
+       len[0] = wpabuf_len(msg->buf);
+       addr[1] = secret;
+       len[1] = secret_len;
+       md5_vector(2, addr, len, msg->hdr->authenticator);
+
+       if (wpabuf_len(msg->buf) > 0xffff) {
+               wpa_printf(MSG_WARNING, "RADIUS: Too long messages (%lu)",
+                          (unsigned long) wpabuf_len(msg->buf));
+       }
+}
+
+
 int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret,
                               size_t secret_len)
 {
index 2031054..ad65b04 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * RADIUS message processing
- * Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2009, 2012, 2014, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -204,6 +204,9 @@ int radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret,
                               const struct radius_hdr *req_hdr);
 void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
                            size_t secret_len);
+void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret,
+                                size_t secret_len,
+                                const u8 *req_authenticator);
 int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret,
                               size_t secret_len);
 int radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret,
index 1063d65..2904b2f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * RADIUS authentication server
- * Copyright (c) 2005-2009, 2011, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2005-2009, 2011-2014, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -49,6 +49,13 @@ struct radius_server_counters {
        u32 bad_authenticators;
        u32 packets_dropped;
        u32 unknown_types;
+
+       u32 acct_requests;
+       u32 invalid_acct_requests;
+       u32 acct_responses;
+       u32 malformed_acct_requests;
+       u32 acct_bad_authenticators;
+       u32 unknown_acct_types;
 };
 
 /**
@@ -99,6 +106,11 @@ struct radius_server_data {
        int auth_sock;
 
        /**
+        * acct_sock - Socket for RADIUS accounting messages
+        */
+       int acct_sock;
+
+       /**
         * clients - List of authorized RADIUS clients
         */
        struct radius_client *clients;
@@ -979,6 +991,140 @@ fail:
 }
 
 
+static void radius_server_receive_acct(int sock, void *eloop_ctx,
+                                      void *sock_ctx)
+{
+       struct radius_server_data *data = eloop_ctx;
+       u8 *buf = NULL;
+       union {
+               struct sockaddr_storage ss;
+               struct sockaddr_in sin;
+#ifdef CONFIG_IPV6
+               struct sockaddr_in6 sin6;
+#endif /* CONFIG_IPV6 */
+       } from;
+       socklen_t fromlen;
+       int len, res;
+       struct radius_client *client = NULL;
+       struct radius_msg *msg = NULL, *resp = NULL;
+       char abuf[50];
+       int from_port = 0;
+       struct radius_hdr *hdr;
+       struct wpabuf *rbuf;
+
+       buf = os_malloc(RADIUS_MAX_MSG_LEN);
+       if (buf == NULL) {
+               goto fail;
+       }
+
+       fromlen = sizeof(from);
+       len = recvfrom(sock, buf, RADIUS_MAX_MSG_LEN, 0,
+                      (struct sockaddr *) &from.ss, &fromlen);
+       if (len < 0) {
+               wpa_printf(MSG_INFO, "recvfrom[radius_server]: %s",
+                          strerror(errno));
+               goto fail;
+       }
+
+#ifdef CONFIG_IPV6
+       if (data->ipv6) {
+               if (inet_ntop(AF_INET6, &from.sin6.sin6_addr, abuf,
+                             sizeof(abuf)) == NULL)
+                       abuf[0] = '\0';
+               from_port = ntohs(from.sin6.sin6_port);
+               RADIUS_DEBUG("Received %d bytes from %s:%d",
+                            len, abuf, from_port);
+
+               client = radius_server_get_client(data,
+                                                 (struct in_addr *)
+                                                 &from.sin6.sin6_addr, 1);
+       }
+#endif /* CONFIG_IPV6 */
+
+       if (!data->ipv6) {
+               os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
+               from_port = ntohs(from.sin.sin_port);
+               RADIUS_DEBUG("Received %d bytes from %s:%d",
+                            len, abuf, from_port);
+
+               client = radius_server_get_client(data, &from.sin.sin_addr, 0);
+       }
+
+       RADIUS_DUMP("Received data", buf, len);
+
+       if (client == NULL) {
+               RADIUS_DEBUG("Unknown client %s - packet ignored", abuf);
+               data->counters.invalid_acct_requests++;
+               goto fail;
+       }
+
+       msg = radius_msg_parse(buf, len);
+       if (msg == NULL) {
+               RADIUS_DEBUG("Parsing incoming RADIUS frame failed");
+               data->counters.malformed_acct_requests++;
+               client->counters.malformed_acct_requests++;
+               goto fail;
+       }
+
+       os_free(buf);
+       buf = NULL;
+
+       if (wpa_debug_level <= MSG_MSGDUMP) {
+               radius_msg_dump(msg);
+       }
+
+       if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_REQUEST) {
+               RADIUS_DEBUG("Unexpected RADIUS code %d",
+                            radius_msg_get_hdr(msg)->code);
+               data->counters.unknown_acct_types++;
+               client->counters.unknown_acct_types++;
+               goto fail;
+       }
+
+       data->counters.acct_requests++;
+       client->counters.acct_requests++;
+
+       if (radius_msg_verify_acct_req(msg, (u8 *) client->shared_secret,
+                                      client->shared_secret_len)) {
+               RADIUS_DEBUG("Invalid Authenticator from %s", abuf);
+               data->counters.acct_bad_authenticators++;
+               client->counters.acct_bad_authenticators++;
+               goto fail;
+       }
+
+       /* TODO: Write accounting information to a file or database */
+
+       hdr = radius_msg_get_hdr(msg);
+
+       resp = radius_msg_new(RADIUS_CODE_ACCOUNTING_RESPONSE, hdr->identifier);
+       if (resp == NULL)
+               goto fail;
+
+       radius_msg_finish_acct_resp(resp, (u8 *) client->shared_secret,
+                                   client->shared_secret_len,
+                                   hdr->authenticator);
+
+       RADIUS_DEBUG("Reply to %s:%d", abuf, from_port);
+       if (wpa_debug_level <= MSG_MSGDUMP) {
+               radius_msg_dump(resp);
+       }
+       rbuf = radius_msg_get_buf(resp);
+       data->counters.acct_responses++;
+       client->counters.acct_responses++;
+       res = sendto(data->acct_sock, wpabuf_head(rbuf), wpabuf_len(rbuf), 0,
+                    (struct sockaddr *) &from.ss, fromlen);
+       if (res < 0) {
+               wpa_printf(MSG_INFO, "sendto[RADIUS SRV]: %s",
+                          strerror(errno));
+       }
+
+fail:
+       radius_msg_free(resp);
+       radius_msg_free(msg);
+       os_free(buf);
+}
+
+
 static int radius_server_disable_pmtu_discovery(int s)
 {
        int r = -1;
@@ -1329,6 +1475,29 @@ radius_server_init(struct radius_server_conf *conf)
                return NULL;
        }
 
+       if (conf->acct_port) {
+#ifdef CONFIG_IPV6
+               if (conf->ipv6)
+                       data->acct_sock = radius_server_open_socket6(
+                               conf->acct_port);
+               else
+#endif /* CONFIG_IPV6 */
+               data->acct_sock = radius_server_open_socket(conf->acct_port);
+               if (data->acct_sock < 0) {
+                       wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS accounting server");
+                       radius_server_deinit(data);
+                       return NULL;
+               }
+               if (eloop_register_read_sock(data->acct_sock,
+                                            radius_server_receive_acct,
+                                            data, NULL)) {
+                       radius_server_deinit(data);
+                       return NULL;
+               }
+       } else {
+               data->acct_sock = -1;
+       }
+
        return data;
 }
 
@@ -1347,6 +1516,11 @@ void radius_server_deinit(struct radius_server_data *data)
                close(data->auth_sock);
        }
 
+       if (data->acct_sock >= 0) {
+               eloop_unregister_read_sock(data->acct_sock);
+               close(data->acct_sock);
+       }
+
        radius_server_free_clients(data, data->clients);
 
        os_free(data->pac_opaque_encr_key);
@@ -1410,7 +1584,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf,
                          "radiusAuthServTotalMalformedAccessRequests=%u\n"
                          "radiusAuthServTotalBadAuthenticators=%u\n"
                          "radiusAuthServTotalPacketsDropped=%u\n"
-                         "radiusAuthServTotalUnknownTypes=%u\n",
+                         "radiusAuthServTotalUnknownTypes=%u\n"
+                         "radiusAccServTotalRequests=%u\n"
+                         "radiusAccServTotalInvalidRequests=%u\n"
+                         "radiusAccServTotalResponses=%u\n"
+                         "radiusAccServTotalMalformedRequests=%u\n"
+                         "radiusAccServTotalBadAuthenticators=%u\n"
+                         "radiusAccServTotalUnknownTypes=%u\n",
                          data->counters.access_requests,
                          data->counters.invalid_requests,
                          data->counters.dup_access_requests,
@@ -1420,7 +1600,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf,
                          data->counters.malformed_access_requests,
                          data->counters.bad_authenticators,
                          data->counters.packets_dropped,
-                         data->counters.unknown_types);
+                         data->counters.unknown_types,
+                         data->counters.acct_requests,
+                         data->counters.invalid_acct_requests,
+                         data->counters.acct_responses,
+                         data->counters.malformed_acct_requests,
+                         data->counters.acct_bad_authenticators,
+                         data->counters.unknown_acct_types);
        if (ret < 0 || ret >= end - pos) {
                *pos = '\0';
                return pos - buf;
@@ -1455,7 +1641,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf,
                                  "radiusAuthServMalformedAccessRequests=%u\n"
                                  "radiusAuthServBadAuthenticators=%u\n"
                                  "radiusAuthServPacketsDropped=%u\n"
-                                 "radiusAuthServUnknownTypes=%u\n",
+                                 "radiusAuthServUnknownTypes=%u\n"
+                                 "radiusAccServTotalRequests=%u\n"
+                                 "radiusAccServTotalInvalidRequests=%u\n"
+                                 "radiusAccServTotalResponses=%u\n"
+                                 "radiusAccServTotalMalformedRequests=%u\n"
+                                 "radiusAccServTotalBadAuthenticators=%u\n"
+                                 "radiusAccServTotalUnknownTypes=%u\n",
                                  idx,
                                  abuf, mbuf,
                                  cli->counters.access_requests,
@@ -1466,7 +1658,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf,
                                  cli->counters.malformed_access_requests,
                                  cli->counters.bad_authenticators,
                                  cli->counters.packets_dropped,
-                                 cli->counters.unknown_types);
+                                 cli->counters.unknown_types,
+                                 cli->counters.acct_requests,
+                                 cli->counters.invalid_acct_requests,
+                                 cli->counters.acct_responses,
+                                 cli->counters.malformed_acct_requests,
+                                 cli->counters.acct_bad_authenticators,
+                                 cli->counters.unknown_acct_types);
                if (ret < 0 || ret >= end - pos) {
                        *pos = '\0';
                        return pos - buf;
index 284bd59..78f5fc2 100644 (file)
@@ -22,6 +22,11 @@ struct radius_server_conf {
        int auth_port;
 
        /**
+        * acct_port - UDP port to listen to as an accounting server
+        */
+       int acct_port;
+
+       /**
         * client_file - RADIUS client configuration file
         *
         * This file contains the RADIUS clients and the shared secret to be