Detect RADIUS packet retransmissions
authorAndreas Steffen <andreas.steffen@strongswan.org>
Sat, 31 May 2014 18:19:12 +0000 (20:19 +0200)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Sat, 31 May 2014 18:37:57 +0000 (20:37 +0200)
src/libcharon/plugins/tnc_pdp/tnc_pdp.c

index 89237f5..109c216 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <errno.h>
 #include <unistd.h>
+#include <time.h>
 
 #include <radius_message.h>
 #include <radius_mppe.h>
@@ -37,7 +38,7 @@
 #include <sa/eap/eap_method.h>
 
 typedef struct private_tnc_pdp_t private_tnc_pdp_t;
-
+typedef struct client_entry_t client_entry_t;
 /**
  * Default RADIUS port, when not configured
  */
@@ -48,6 +49,8 @@ typedef struct private_tnc_pdp_t private_tnc_pdp_t;
  */
 #define MAX_PACKET 4096
 
+#define RADIUS_RETRANSMIT_TIMEOUT      30 /* seconds */
+
 /**
  * private data of tnc_pdp_t
  */
@@ -99,6 +102,11 @@ struct private_tnc_pdp_t {
        chunk_t secret;
 
        /**
+        * RADIUS clients
+        */
+       linked_list_t *clients;
+
+       /**
         * MD5 hasher
         */
        hasher_t *hasher;
@@ -121,6 +129,33 @@ struct private_tnc_pdp_t {
 };
 
 /**
+ * Client entry helping to detect RADIUS packet retransmissions
+ */
+struct client_entry_t {
+
+       /**
+        * IP host address and port of client
+        */
+       host_t *host;
+
+       /**
+        * Time of last RADIUS Access-Request received from client
+        */
+       time_t last_time;
+
+       /**
+        * Identifier of last RADIUS Access-Request received from client
+        */
+       uint8_t last_id;
+};
+
+static void free_client_entry(client_entry_t *this)
+{
+       this->host->destroy(this->host);
+       free(this);
+}
+
+/**
  * Open IPv4 or IPv6 UDP socket
  */
 static int open_udp_socket(int family, u_int16_t port)
@@ -663,16 +698,24 @@ static bool radius_receive(private_tnc_pdp_t *this, int fd, watcher_event_t even
 {
        radius_message_t *request;
        char buffer[MAX_PACKET];
+       client_entry_t *client;
+       bool retransmission = FALSE, found = FALSE, stale;
+       enumerator_t *enumerator;
        int bytes_read = 0;
        host_t *source;
+       uint8_t id;
+       time_t now;
+
        union {
                struct sockaddr_in in4;
                struct sockaddr_in6 in6;
        } src;
+
        struct iovec iov = {
                .iov_base = buffer,
                .iov_len = MAX_PACKET,
        };
+
        struct msghdr msg = {
                .msg_name = &src,
                .msg_namelen = sizeof(src),
@@ -704,7 +747,46 @@ static bool radius_receive(private_tnc_pdp_t *this, int fd, watcher_event_t even
                if (request->verify(request, NULL, this->secret, this->hasher,
                                                                                   this->signer))
                {
-                       process_eap(this, request, source);
+                       id = request->get_identifier(request);
+                       now = time(NULL);
+
+                       enumerator = this->clients->create_enumerator(this->clients);
+                       while (enumerator->enumerate(enumerator, &client))
+                       {
+                               stale = client->last_time < now - RADIUS_RETRANSMIT_TIMEOUT;
+
+                               if (source->equals(source, client->host))
+                               {
+                                       retransmission = !stale && client->last_id == id;
+                                       client->last_id = id;
+                                       client->last_time = now;
+                                       found = TRUE;
+                               }
+                               else if (stale)
+                               {
+                                       this->clients->remove_at(this->clients, enumerator);
+                                       free_client_entry(client);
+                               }
+                       }
+                       enumerator->destroy(enumerator);
+
+                       if (!found)
+                       {
+                               client = malloc_thing(client_entry_t);
+                               client->host = source->clone(source);
+                               client->last_id = id;
+                               client->last_time = now;
+                               this->clients->insert_last(this->clients, client);
+                       }
+                       if (retransmission)
+                       {
+                               DBG1(DBG_CFG, "ignoring RADIUS Access-Request 0x%02x, "
+                                                         "already processing", id);
+                       }
+                       else
+                       {
+                               process_eap(this, request, source);
+                       }
                }
                request->destroy(request);
        }
@@ -739,6 +821,10 @@ METHOD(tnc_pdp_t, destroy, void,
                lib->watcher->remove(lib->watcher, this->radius_ipv6);
                close(this->radius_ipv6);
        }
+       if (this->clients)
+       {
+               this->clients->destroy_function(this->clients, (void*)free_client_entry);
+       }
        DESTROY_IF(this->server);
        DESTROY_IF(this->signer);
        DESTROY_IF(this->hasher);
@@ -843,6 +929,7 @@ tnc_pdp_t *tnc_pdp_create(void)
                this->radius_ipv4 = open_udp_socket(AF_INET,  radius_port);
                this->radius_ipv6 = open_udp_socket(AF_INET6, radius_port);
                this->secret = chunk_from_str(secret);
+               this->clients = linked_list_create();
                this->type = eap_type_from_string(eap_type_str);
                this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
                this->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128);