Fix corner case when not threaded.
[freeradius.git] / src / main / event.c
index d3db7ea..65fcb6b 100644 (file)
@@ -43,7 +43,6 @@ RCSID("$Id$")
 extern pid_t radius_pid;
 extern int dont_fork;
 extern int check_config;
-extern void force_log_reopen(void);
 extern char *debug_condition;
 
 /*
@@ -70,6 +69,8 @@ static int self_pipe[2];
 #ifdef HAVE_PTHREAD_H
 #ifdef WITH_PROXY
 static pthread_mutex_t proxy_mutex;
+static rad_listen_t *proxy_listener_list = NULL;
+static int proxy_no_new_sockets = FALSE;
 #endif
 
 #define PTHREAD_MUTEX_LOCK if (have_children) pthread_mutex_lock
@@ -82,24 +83,32 @@ static pthread_t NO_SUCH_CHILD_PID;
  */
 #define PTHREAD_MUTEX_LOCK(_x)
 #define PTHREAD_MUTEX_UNLOCK(_x)
-int thread_pool_addrequest(REQUEST *request, RAD_REQUEST_FUNP fun)
-{
-       radius_handle_request(request, fun);
-       return 1;
-}
 #endif
 
+/*
+ *     We need mutexes around the event FD list *only* in certain
+ *     cases.
+ */
+#if defined (HAVE_PTHREAD_H) && (defined(WITH_PROXY) || defined(WITH_TCP))
+static pthread_mutex_t fd_mutex;
+#define FD_MUTEX_LOCK if (have_children) pthread_mutex_lock
+#define FD_MUTEX_UNLOCK if (have_children) pthread_mutex_unlock
+#else
+/*
+ *     This is easier than ifdef's throughout the code.
+ */
+#define FD_MUTEX_LOCK(_x)
+#define FD_MUTEX_UNLOCK(_x)
+#endif
+
+
 #define INSERT_EVENT(_function, _ctx) if (!fr_event_insert(el, _function, _ctx, &((_ctx)->when), &((_ctx)->ev))) { _rad_panic(__FILE__, __LINE__, "Failed to insert event"); }
 
 #ifdef WITH_PROXY
 static fr_packet_list_t *proxy_list = NULL;
+static void remove_from_proxy_hash(REQUEST *request);
 
-/*
- *     We keep the proxy FD's here.  The RADIUS Id's are marked
- *     "allocated" per Id, via a bit per proxy FD.
- */
-static int             proxy_fds[32];
-static rad_listen_t    *proxy_listeners[32];
+static void check_for_zombie_home_server(REQUEST *request);
 #else
 #define remove_from_proxy_hash(foo)
 #endif
@@ -130,8 +139,8 @@ static void tv_add(struct timeval *tv, int usec_delay)
        tv->tv_usec += usec_delay;
 
        if (tv->tv_usec > USEC) {
-               tv->tv_usec -= USEC;
-               tv->tv_sec++;
+               tv->tv_sec += tv->tv_usec / USEC;
+               tv->tv_usec %= USEC;
        }
 }
 
@@ -142,9 +151,52 @@ static void remove_from_request_hash(REQUEST *request)
        fr_packet_list_yank(pl, request->packet);
        request->in_request_hash = FALSE;
 
+       /*
+        *      FIXME: Move this to a "statistics" thread?
+        *      Or (short term) add a mutex lock around it.
+        */
        request_stats_final(request);
+
+#ifdef WITH_TCP
+       request->listener->count--;
+#endif
 }
 
+static void ev_request_free(REQUEST **prequest)
+{
+       REQUEST *request;
+       
+       if (!prequest || !*prequest) return;
+
+       request = *prequest;
+
+#ifdef WITH_COA
+       if (request->coa) {
+               /*
+                *      Divorce the child from the parent first,
+                *      then clean up the child.
+                */
+               request->coa->parent = NULL;
+               ev_request_free(&request->coa);
+       }
+
+       /*
+        *      Divorce the parent from the child, and leave the
+        *      parent still alive.
+        */
+       if (request->parent && (request->parent->coa == request)) {
+               request->parent->coa = NULL;
+       }
+#endif
+
+       if (request->ev) fr_event_delete(el, &request->ev);
+#ifdef WITH_PROXY
+       if (request->in_proxy_hash) remove_from_proxy_hash(request);
+#endif
+       if (request->in_request_hash) remove_from_request_hash(request);
+
+       request_free(prequest);
+}
 
 #ifdef WITH_PROXY
 static REQUEST *lookup_in_proxy_hash(RADIUS_PACKET *reply)
@@ -161,35 +213,7 @@ static REQUEST *lookup_in_proxy_hash(RADIUS_PACKET *reply)
        }
 
        request = fr_packet2myptr(REQUEST, proxy, proxy_p);
-
-       if (!request) {
-               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-               return NULL;
-       }
-
-       request->num_proxied_responses++;
-
-       /*
-        *      Catch the most common case of everything working
-        *      correctly.
-        */
-       if (request->num_proxied_requests == request->num_proxied_responses) {
-               fr_packet_list_yank(proxy_list, request->proxy);
-               fr_packet_list_id_free(proxy_list, request->proxy);
-               request->in_proxy_hash = FALSE;
-       }
-
-       /*
-        *      On the FIRST reply, decrement the count of outstanding
-        *      requests.  Note that this is NOT the count of sent
-        *      packets, but whether or not the home server has
-        *      responded at all.
-        */
-       if (!request->proxy_reply &&
-           request->home_server &&
-           request->home_server->currently_outstanding) {
-               request->home_server->currently_outstanding--;
-       }
+       request->num_proxied_responses++; /* needs to be protected by lock */
 
        PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
 
@@ -221,9 +245,10 @@ static void remove_from_proxy_hash(REQUEST *request)
        fr_packet_list_id_free(proxy_list, request->proxy);
 
        /*
-        *      The home server hasn't replied, but we've given up on
-        *      this request.  Don't count this request against the
-        *      home server.
+        *      On the FIRST reply, decrement the count of outstanding
+        *      requests.  Note that this is NOT the count of sent
+        *      packets, but whether or not the home server has
+        *      responded at all.
         */
        if (!request->proxy_reply &&
            request->home_server &&
@@ -231,183 +256,168 @@ static void remove_from_proxy_hash(REQUEST *request)
                request->home_server->currently_outstanding--;
        }
 
+#ifdef WITH_TCP
+       request->proxy_listener->count--;
+       request->proxy_listener = NULL;
+#endif
+
        /*
         *      Got from YES in hash, to NO, not in hash while we hold
         *      the mutex.  This guarantees that when another thread
-        *      grans the mutex, the "not in hash" flag is correct.
+        *      grabs the mutex, the "not in hash" flag is correct.
         */
        request->in_proxy_hash = FALSE;
 
        PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
 }
+#endif /* WITH_PROXY */
 
-static void ev_request_free(REQUEST **prequest)
+#ifdef WITH_TCP
+static int remove_all_requests(void *ctx, void *data)
 {
+       rad_listen_t *this = ctx;
+       RADIUS_PACKET **packet_p = data;
        REQUEST *request;
        
-       if (!prequest || !*prequest) return;
+       request = fr_packet2myptr(REQUEST, packet, packet_p);
+       if (request->packet->sockfd != this->fd) return 0;
 
-       request = *prequest;
+       switch (request->child_state) {
+       case REQUEST_RUNNING:
+               rad_assert(request->ev != NULL); /* or it's lost forever */
+       case REQUEST_QUEUED:
+               request->master_state = REQUEST_STOP_PROCESSING;
+               return 0;
 
-#ifdef WITH_COA
-       if (request->coa) {
                /*
-                *      Divorce the child from the parent first,
-                *      then clean up the child.
+                *      Waiting for a reply.  There's no point in
+                *      doing anything else.  We remove it from the
+                *      request hash so that we can close the upstream
+                *      socket.
                 */
-               request->coa->parent = NULL;
-               ev_request_free(&request->coa);
-       }
+       case REQUEST_PROXIED:
+               remove_from_request_hash(request);
+               request->child_state = REQUEST_DONE;
+               return 0;
 
-       /*
-        *      Divorce the parent from the child, and leave the
-        *      parent still alive.
-        */
-       if (request->parent && (request->parent->coa == request)) {
-               request->parent->coa = NULL;
+       case REQUEST_REJECT_DELAY:
+       case REQUEST_CLEANUP_DELAY:
+       case REQUEST_DONE:
+               ev_request_free(&request);
+               break;
        }
-#endif
-
-       if (request->ev) fr_event_delete(el, &request->ev);
-       if (request->in_proxy_hash) remove_from_proxy_hash(request);
-       if (request->in_request_hash) remove_from_request_hash(request);
 
-       request_free(prequest);
+       return 0;
 }
 
-static int proxy_id_alloc(REQUEST *request, RADIUS_PACKET *packet)
+#ifdef WITH_PROXY
+static int remove_all_proxied_requests(void *ctx, void *data)
 {
-       int i, proxy, found;
-       rad_listen_t *proxy_listener;
-
-       if (fr_packet_list_id_alloc(proxy_list, packet)) return 1;
+       rad_listen_t *this = ctx;
+       RADIUS_PACKET **proxy_p = data;
+       REQUEST *request;
+       
+       request = fr_packet2myptr(REQUEST, proxy, proxy_p);
+       if (request->proxy->sockfd != this->fd) return 0;
 
-       /*
-        *      Allocate a new proxy fd.  This function adds
-        *      it to the tail of the list of listeners.  With
-        *      some care, this can be thread-safe.
-        */
-       proxy_listener = proxy_new_listener(&packet->src_ipaddr, FALSE);
-       if (!proxy_listener) {
-               RDEBUG2("ERROR: Failed to create a new socket for proxying requests.");
+       switch (request->child_state) {
+       case REQUEST_RUNNING:
+               rad_assert(request->ev != NULL); /* or it's lost forever */
+       case REQUEST_QUEUED:
+               request->master_state = REQUEST_STOP_PROCESSING;
                return 0;
-       }
-       
-       /*
-        *      Cache it locally.
-        */
-       found = -1;
-       proxy = proxy_listener->fd;
-       for (i = 0; i < 32; i++) {
+
                /*
-                *      Found a free entry.  Save the socket,
-                *      and remember where we saved it.
+                *      Eventually we will discover that there is no
+                *      response to the proxied request.
                 */
-               if (proxy_fds[(proxy + i) & 0x1f] == -1) {
-                       found = (proxy + i) & 0x1f;
-                       proxy_fds[found] = proxy;
-                       proxy_listeners[found] = proxy_listener;
-                       break;
-               }
-       }
-       rad_assert(found >= 0);
-       
-       if (!fr_packet_list_socket_add(proxy_list, proxy_listener->fd)) {
-                       RDEBUG2("ERROR: Failed to create a new socket for proxying requests.");
-               return 0;
-               
-       }
-       
-       if (!fr_packet_list_id_alloc(proxy_list, packet)) {
-                       RDEBUG2("ERROR: Failed to create a new socket for proxying requests.");
-               return 0;
+       case REQUEST_PROXIED:
+               break;
+
+               /*
+                *      Keep it in the cache for duplicate detection.
+                */
+       case REQUEST_REJECT_DELAY:
+       case REQUEST_CLEANUP_DELAY:
+       case REQUEST_DONE:
+               break;
        }
-       
-       /*
-        *      Signal the main thread to add the new FD to the list
-        *      of listening FD's.
-        */
-       radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD);
-       return 1;
+
+       remove_from_proxy_hash(request);
+       return 0;
 }
+#endif /* WITH_PROXY */
+#endif /* WITH_TCP */
 
 
-static int insert_into_proxy_hash(REQUEST *request, int retransmit)
+#ifdef WITH_PROXY
+static int insert_into_proxy_hash(REQUEST *request)
 {
-       int i, proxy;
        char buf[128];
+       int rcode, tries;
+       void *proxy_listener;
 
        rad_assert(request->proxy != NULL);
        rad_assert(proxy_list != NULL);
 
+       tries = 1;
+retry:
        PTHREAD_MUTEX_LOCK(&proxy_mutex);
-
-       /*
-        *      Keep track of maximum outstanding requests to a
-        *      particular home server.  'max_outstanding' is
-        *      enforced in home_server_ldb(), in realms.c.
-        */
-       if (request->home_server) {
-               request->home_server->currently_outstanding++;
-               request->home_server->stats.total_requests++;
-       }
-
-       if (retransmit) {
-               RADIUS_PACKET packet;
-
-               packet = *request->proxy;
-
-               if (!proxy_id_alloc(request, &packet)) {
-                       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-                       return 0;
-               }
+       rcode = fr_packet_list_id_alloc(proxy_list,
+                                       request->home_server->proto,
+                                       request->proxy, &proxy_listener);
+       request->num_proxied_requests = 1;
+       request->num_proxied_responses = 0;
+       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+       
+       if (!rcode) {
+               if (proxy_no_new_sockets) return 0;
 
                /*
-                *      Yank the request, free the old Id, and
-                *      remember the new Id.
+                *      Also locks the proxy mutex, so we have to call
+                *      it with the mutex unlocked.  Some systems
+                *      don't support recursive mutexes.
                 */
-               fr_packet_list_yank(proxy_list, request->proxy);
-               fr_packet_list_id_free(proxy_list, request->proxy);
-               *request->proxy = packet;
-
-       } else if (!proxy_id_alloc(request, request->proxy)) {
-               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-               return 0;
-       }
-
-       rad_assert(request->proxy->sockfd >= 0);
-
-       /*
-        *      FIXME: Hack until we get rid of rad_listen_t, and put
-        *      the information into the packet_list.
-        */
-       proxy = -1;
-       for (i = 0; i < 32; i++) {
-               if (proxy_fds[i] == request->proxy->sockfd) {
-                       proxy = i;
-                       break;
+               if (!proxy_new_listener(request->home_server, 0)) {
+                       radlog(L_ERR, "Failed to create a new socket for proxying requests.");
+                       return 0;
                }
-       }
+               request->proxy->src_port = 0; /* Use any new socket */
 
-       if (proxy < 0) {
-               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-               RDEBUG2("ERROR: All sockets are full.");
-               return 0;
+               tries++;
+               if (tries > 2) {
+                       RDEBUG2("ERROR: Failed allocating Id for new socket when proxying requests.");
+                       return 0;
+               }
+               
+               goto retry;
        }
 
-       rad_assert(proxy_fds[proxy] != -1);
-       rad_assert(proxy_listeners[proxy] != NULL);
-       request->proxy_listener = proxy_listeners[proxy];
+       request->proxy_listener = proxy_listener;
 
+       PTHREAD_MUTEX_LOCK(&proxy_mutex);
        if (!fr_packet_list_insert(proxy_list, &request->proxy)) {
                fr_packet_list_id_free(proxy_list, request->proxy);
                PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-               RDEBUG2("ERROR: Failed to insert entry into proxy list");
+               radlog(L_PROXY, "Failed to insert entry into proxy list.");
                return 0;
        }
 
        request->in_proxy_hash = TRUE;
 
+       /*
+        *      Keep track of maximum outstanding requests to a
+        *      particular home server.  'max_outstanding' is
+        *      enforced in home_server_ldb(), in realms.c.
+        */
+       if (request->home_server) {
+               request->home_server->currently_outstanding++;
+       }
+
+#ifdef WITH_TCP
+       request->proxy_listener->count++;
+#endif
+
        PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
 
        RDEBUG3(" proxy: allocating destination %s port %d - Id %d",
@@ -430,7 +440,7 @@ static void wait_for_proxy_id_to_expire(void *ctx)
        rad_assert(request->magic == REQUEST_MAGIC);
        rad_assert(request->proxy != NULL);
 
-       if (!fr_event_now(el, &now)) gettimeofday(&now, NULL);
+       fr_event_now(el, &now);
        request->when = request->proxy_when;
 
 #ifdef WITH_COA
@@ -443,14 +453,16 @@ static void wait_for_proxy_id_to_expire(void *ctx)
        request->when.tv_sec += request->home_server->response_window;
 
        if ((request->num_proxied_requests == request->num_proxied_responses) ||
+#ifdef WITH_TCP
+           (request->home_server->proto == IPPROTO_TCP) ||
+#endif
            timercmp(&now, &request->when, >)) {
                if (request->packet) {
-                       RDEBUG2("Cleaning up request %d ID %d with timestamp +%d",
-                              request->number, request->packet->id,
+                       RDEBUG2("Cleaning up request packet ID %d with timestamp +%d",
+                              request->packet->id,
                               (unsigned int) (request->timestamp - fr_start_time));
                } else {
-                       RDEBUG2("Cleaning up request %d with timestamp +%d",
-                              request->number,
+                       RDEBUG2("Cleaning up request with timestamp +%d",
                               (unsigned int) (request->timestamp - fr_start_time));
                }
 
@@ -468,20 +480,36 @@ static void wait_for_child_to_die(void *ctx)
        REQUEST *request = ctx;
 
        rad_assert(request->magic == REQUEST_MAGIC);
+       remove_from_request_hash(request);
 
-       if ((request->child_state == REQUEST_QUEUED) |
-           (request->child_state == REQUEST_RUNNING)) {
-               request->delay += (request->delay >> 1);
-               tv_add(&request->when, request->delay);
+       /*
+        *      If it's still queued (waiting for a thread to pick it
+        *      up) OR, it's running AND there's still a child thread
+        *      handling it, THEN delay some more.
+        */
+       if ((request->child_state == REQUEST_QUEUED) ||
+           ((request->child_state == REQUEST_RUNNING) &&
+            (pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) == 0))) {
 
-               RDEBUG2("Child is still stuck for request %d", request->number);
+               /*
+                *      Cap delay at max_request_time
+                */
+               if (request->delay < (USEC * request->root->max_request_time)) {
+                       request->delay += (request->delay >> 1);
+                       radlog_request(L_INFO, 0, request, "WARNING: Child is hung in component %s module %s.",
+                              request->component, request->module);
+               } else {
+                       request->delay = USEC * request->root->max_request_time;
+                       RDEBUG2("WARNING: Child is hung after \"max_request_time\" for request %u",
+                               request->number);
+               }
+               tv_add(&request->when, request->delay);
 
                INSERT_EVENT(wait_for_child_to_die, request);
                return;
        }
 
-       RDEBUG2("Child is finally responsive for request %d", request->number);
-       remove_from_request_hash(request);
+       RDEBUG2("Child is finally responsive");
 
 #ifdef WITH_PROXY
        if (request->proxy) {
@@ -511,8 +539,8 @@ static void cleanup_delay(void *ctx)
        }
 #endif
 
-       RDEBUG2("Cleaning up request %d ID %d with timestamp +%d",
-              request->number, request->packet->id,
+       RDEBUG2("Cleaning up request packet ID %d with timestamp +%d",
+               request->packet->id,
               (unsigned int) (request->timestamp - fr_start_time));
 
        ev_request_free(&request);
@@ -520,66 +548,6 @@ static void cleanup_delay(void *ctx)
 
 
 /*
- *     FIXME: Put into a libradius function.
- */
-#define MAX_PACKET_CODE (52)
-static const char *packet_codes[] = {
-  "",
-  "Access-Request",
-  "Access-Accept",
-  "Access-Reject",
-  "Accounting-Request",
-  "Accounting-Response",
-  "Accounting-Status",
-  "Password-Request",
-  "Password-Accept",
-  "Password-Reject",
-  "Accounting-Message",
-  "Access-Challenge",
-  "Status-Server",
-  "Status-Client",
-  "14",
-  "15",
-  "16",
-  "17",
-  "18",
-  "19",
-  "20",
-  "Resource-Free-Request",
-  "Resource-Free-Response",
-  "Resource-Query-Request",
-  "Resource-Query-Response",
-  "Alternate-Resource-Reclaim-Request",
-  "NAS-Reboot-Request",
-  "NAS-Reboot-Response",
-  "28",
-  "Next-Passcode",
-  "New-Pin",
-  "Terminate-Session",
-  "Password-Expired",
-  "Event-Request",
-  "Event-Response",
-  "35",
-  "36",
-  "37",
-  "38",
-  "39",
-  "Disconnect-Request",
-  "Disconnect-ACK",
-  "Disconnect-NAK",
-  "CoA-Request",
-  "CoA-ACK",
-  "CoA-NAK",
-  "46",
-  "47",
-  "48",
-  "49",
-  "IP-Address-Allocate",
-  "IP-Address-Release"
-};
-
-
-/*
  *     In daemon mode, AND this request has debug flags set.
  */
 #define DEBUG_PACKET if (!debug_flag && request->options && request->radlog) debug_packet
@@ -594,7 +562,6 @@ static void debug_packet(REQUEST *request, RADIUS_PACKET *packet, int direction)
 
        if (!packet) return;
 
-       rad_assert(debug_flag != 0);
        rad_assert(request->radlog != NULL);
 
        if (direction == 0) {
@@ -616,9 +583,9 @@ static void debug_packet(REQUEST *request, RADIUS_PACKET *packet, int direction)
         *
         *      This really belongs in a utility library
         */
-       if ((packet->code > 0) && (packet->code < MAX_PACKET_CODE)) {
+       if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
                RDEBUG("%s %s packet %s host %s port %d, id=%d, length=%d",
-                      received, packet_codes[packet->code], from,
+                      received, fr_packet_codes[packet->code], from,
                       inet_ntop(ip->af, &ip->ipaddr, buffer, sizeof(buffer)),
                       port, packet->id, packet->data_len);
        } else {
@@ -642,7 +609,7 @@ static void reject_delay(void *ctx)
        rad_assert(request->magic == REQUEST_MAGIC);
        rad_assert(request->child_state == REQUEST_REJECT_DELAY);
 
-       RDEBUG2("Sending delayed reject for request %d", request->number);
+       RDEBUG2("Sending delayed reject");
 
        DEBUG_PACKET(request, request->reply, 1);
 
@@ -661,6 +628,10 @@ void revive_home_server(void *ctx)
        home_server *home = ctx;
        char buffer[128];
 
+#ifdef WITH_TCP
+       rad_assert(home->proto != IPPROTO_TCP);
+#endif
+
        home->state = HOME_STATE_ALIVE;
        home->currently_outstanding = 0;
        home->revive_time = now;
@@ -670,7 +641,7 @@ void revive_home_server(void *ctx)
         */
        if (home->ev) fr_event_delete(el, &home->ev);
 
-       radlog(L_INFO, "PROXY: Marking home server %s port %d alive again... we have no idea if it really is alive or not.",
+       radlog(L_PROXY, "Marking home server %s port %d alive again... we have no idea if it really is alive or not.",
               inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr,
                         buffer, sizeof(buffer)),
               home->port);
@@ -687,19 +658,31 @@ static void no_response_to_ping(void *ctx)
        rad_assert(request->home_server != NULL);
 
        home = request->home_server;
+#ifdef WITH_TCP
+       rad_assert(home->proto != IPPROTO_TCP);
+#endif
+
        home->num_received_pings = 0;
 
-       RDEBUG2("No response to status check %d from home server %s port %d",
+       radlog(L_ERR, "No response to status check %d for home server %s port %d",
               request->number,
               inet_ntop(request->proxy->dst_ipaddr.af,
                         &request->proxy->dst_ipaddr.ipaddr,
                         buffer, sizeof(buffer)),
               request->proxy->dst_port);
 
+       check_for_zombie_home_server(request);
+
        wait_for_proxy_id_to_expire(request);
 }
 
 
+/*
+ *     Note that we don't care what the value of the code field is.
+ *     If the response has a valid (src ip/port, dst ip/port), id,
+ *     and correctly signed Message-Authenticator, that's good
+ *     enough.
+ */
 static void received_response_to_ping(REQUEST *request)
 {
        home_server *home;
@@ -708,9 +691,13 @@ static void received_response_to_ping(REQUEST *request)
        rad_assert(request->home_server != NULL);
 
        home = request->home_server;
+#ifdef WITH_TCP
+       rad_assert(home->proto != IPPROTO_TCP);
+#endif
+
        home->num_received_pings++;
 
-       RDEBUG2("Received response to status check %d (%d in current sequence)",
+       radlog(L_PROXY, "Received response to status check %d (%d in current sequence)",
               request->number, home->num_received_pings);
 
        /*
@@ -744,7 +731,7 @@ static void received_response_to_ping(REQUEST *request)
                RDEBUG2("Hmm... no event for home server.  Oh well.");
        }
 
-       radlog(L_INFO, "PROXY: Marking home server %s port %d alive",
+       radlog(L_PROXY, "Marking home server %s port %d alive",
               inet_ntop(request->proxy->dst_ipaddr.af,
                         &request->proxy->dst_ipaddr.ipaddr,
                         buffer, sizeof(buffer)),
@@ -763,8 +750,13 @@ static void ping_home_server(void *ctx)
        REQUEST *request;
        VALUE_PAIR *vp;
 
+#ifdef WITH_TCP
+       rad_assert(home->proto != IPPROTO_TCP);
+#endif
+
        if ((home->state == HOME_STATE_ALIVE) ||
-           (home->ping_check == HOME_PING_CHECK_NONE)) {
+           (home->ping_check == HOME_PING_CHECK_NONE) ||
+           (home->ev != NULL)) {
                return;
        }
 
@@ -823,8 +815,8 @@ static void ping_home_server(void *ctx)
 
        rad_assert(request->proxy_listener == NULL);
 
-       if (!insert_into_proxy_hash(request, FALSE)) {
-               RDEBUG2("ERROR: Failed inserting status check %d into proxy hash.  Discarding it.",
+       if (!insert_into_proxy_hash(request)) {
+               radlog(L_PROXY, "Failed to insert status check %d into proxy list.  Discarding it.",
                       request->number);
                ev_request_free(&request);
                return;
@@ -860,7 +852,7 @@ void mark_home_server_dead(home_server *home, struct timeval *when)
        int previous_state = home->state;
        char buffer[128];
 
-       radlog(L_INFO, "PROXY: Marking home server %s port %d as dead.",
+       radlog(L_PROXY, "Marking home server %s port %d as dead.",
               inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr,
                         buffer, sizeof(buffer)),
               home->port);
@@ -935,9 +927,7 @@ static void proxy_fallback_handler(REQUEST *request)
        request->child_state = REQUEST_QUEUED;
        
        rad_assert(request->proxy != NULL);
-       if (!thread_pool_addrequest(request, virtual_server_handler)) {
-               request->child_state = REQUEST_DONE;
-       }
+       thread_pool_addrequest(request, virtual_server_handler);
 
 #ifdef HAVE_PTHREAD_H
        /*
@@ -959,11 +949,13 @@ static int setup_post_proxy_fail(REQUEST *request)
        DICT_VALUE *dval = NULL;
        VALUE_PAIR *vp;
 
+       request->child_state = REQUEST_RUNNING;
+
        if (request->packet->code == PW_AUTHENTICATION_REQUEST) {
-               dval = dict_valbyname(PW_POST_PROXY_TYPE, "Fail-Authentication");
+         dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Fail-Authentication");
 
        } else if (request->packet->code == PW_ACCOUNTING_REQUEST) {
-               dval = dict_valbyname(PW_POST_PROXY_TYPE, "Fail-Accounting");
+               dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Fail-Accounting");
 
 #ifdef WITH_COA
                /*
@@ -973,10 +965,10 @@ static int setup_post_proxy_fail(REQUEST *request)
                request->packet->code &= 0xff; /* restore it */
 
                if (request->proxy->code == PW_COA_REQUEST) {
-                       dval = dict_valbyname(PW_POST_PROXY_TYPE, "Fail-CoA");
+                       dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Fail-CoA");
 
                } else if (request->proxy->code == PW_DISCONNECT_REQUEST) {
-                       dval = dict_valbyname(PW_POST_PROXY_TYPE, "Fail-Disconnect");
+                       dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Fail-Disconnect");
                } else {
                        return 0;
                }
@@ -986,16 +978,16 @@ static int setup_post_proxy_fail(REQUEST *request)
                return 0;
        }
 
-       if (!dval) dval = dict_valbyname(PW_POST_PROXY_TYPE, "Fail");
+       if (!dval) dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Fail");
 
        if (!dval) {
-               pairdelete(&request->config_items, PW_POST_PROXY_TYPE);
+               pairdelete(&request->config_items, PW_POST_PROXY_TYPE, 0);
                return 0;
        }
 
-       vp = pairfind(request->config_items, PW_POST_PROXY_TYPE);
+       vp = pairfind(request->config_items, PW_POST_PROXY_TYPE, 0);
        if (!vp) vp = radius_paircreate(request, &request->config_items,
-                                       PW_POST_PROXY_TYPE, PW_TYPE_INTEGER);
+                                       PW_POST_PROXY_TYPE, 0, PW_TYPE_INTEGER);
        vp->vp_integer = dval->value;
 
        rad_assert(request->proxy_reply == NULL);
@@ -1024,7 +1016,6 @@ static void post_proxy_fail_handler(REQUEST *request)
         *      to do next.
         */
        if (!setup_post_proxy_fail(request)) {
-               request->child_state = REQUEST_RUNNING;
                request_post_handler(request);
 
        } else {
@@ -1062,7 +1053,6 @@ static void post_proxy_fail_handler(REQUEST *request)
        if (have_children) wait_a_bit(request);
 }
 
-
 /* maybe check this against wait_for_proxy_id_to_expire? */
 static void no_response_to_proxied_request(void *ctx)
 {
@@ -1071,6 +1061,12 @@ static void no_response_to_proxied_request(void *ctx)
        char buffer[128];
 
        rad_assert(request->magic == REQUEST_MAGIC);
+
+       if (request->master_state == REQUEST_STOP_PROCESSING) {
+               ev_request_free(&request);
+               return;
+       }
+
        rad_assert(request->child_state == REQUEST_PROXIED);
 
        /*
@@ -1083,45 +1079,116 @@ static void no_response_to_proxied_request(void *ctx)
                return;
        }
 
-       radlog(L_ERR, "Rejecting request %d due to lack of any response from home server %s port %d",
-              request->number,
-              inet_ntop(request->proxy->dst_ipaddr.af,
-                        &request->proxy->dst_ipaddr.ipaddr,
-                        buffer, sizeof(buffer)),
-              request->proxy->dst_port);
-
-       check_for_zombie_home_server(request);
+#ifdef WITH_TCP
+       if (request->home_server->proto != IPPROTO_TCP)
+#endif
+               check_for_zombie_home_server(request);
 
        home = request->home_server;
 
-       post_proxy_fail_handler(request);
+       /*
+        *      The default as of 2.1.7 is to allow requests to
+        *      fail-over to a backup home server when this one does
+        *      not respond.  The old behavior can be configured as
+        *      well.
+        */
+       if (home->no_response_fail) {
+               radlog_request(L_ERR, 0, request, "Rejecting request (proxy Id %d) due to lack of any response from home server %s port %d",
+                      request->proxy->id,
+                      inet_ntop(request->proxy->dst_ipaddr.af,
+                                &request->proxy->dst_ipaddr.ipaddr,
+                                buffer, sizeof(buffer)),
+                      request->proxy->dst_port);
+
+               post_proxy_fail_handler(request);
+       } else {
+               /*
+                *      Enforce max_request_time.
+                *
+                *      We fail over to another backup home server
+                *      when the client re-transmits the request.  If
+                *      the client doesn't re-transmit, no fail-over
+                *      occurs.
+                */
+               rad_assert(request->ev == NULL);
+               request->child_state = REQUEST_RUNNING;
+               wait_a_bit(request);
+       }
 
        /*
         *      Don't touch request due to race conditions
         */
-       if (home->state == HOME_STATE_IS_DEAD) {
-               rad_assert(home->ev != NULL); /* or it will never wake up */
+
+#ifdef WITH_TCP
+       /*
+        *      Do nothing more.  The home server didn't respond,
+        *      but that isn't a catastrophic failure.  Some home
+        *      servers don't respond to packets...
+        */
+       if (home->proto == IPPROTO_TCP) {
+               /*
+                *      FIXME: Set up TCP pinging on this connection.
+                *
+                *      Maybe the CONNECTION is dead, but the home
+                *      server is alive.  In that case, we need to start
+                *      pinging on the connection.
+                *
+                *      This means doing the pinging BEFORE the
+                *      post_proxy_fail_handler above, as it may do
+                *      something with the request, and cause the
+                *      proxy listener to go away!
+                */
                return;
        }
+#endif
 
        /*
-        *      Enable the zombie period when we notice that the home
-        *      server hasn't responded.  We do NOT back-date the start
-        *      of the zombie period.
+        *      If it's not alive, don't try to make it a zombie.
         */
-       if (home->state == HOME_STATE_ALIVE) {
-               radlog(L_ERR, "PROXY: Marking home server %s port %d as zombie (it looks like it is dead).",
-                      inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      home->port);
-               home->state = HOME_STATE_ZOMBIE;
-               home->zombie_period_start = now;
-
+       if (home->state != HOME_STATE_ALIVE) {
                /*
-                *      Start pinging the home server.
+                *      Don't check home->ev due to race conditions.
                 */
-               ping_home_server(home);
+               return;
+       }
+
+       /*
+        *      We've received a real packet recently.  Don't mark the
+        *      server as zombie until we've received NO packets for a
+        *      while.  The "1/4" of zombie period was chosen rather
+        *      arbitrarily.  It's a balance between too short, which
+        *      gives quick fail-over and fail-back, or too long,
+        *      where the proxy still sends packets to an unresponsive
+        *      home server.
+        */
+       if ((home->last_packet + ((home->zombie_period + 3) / 4)) >= now.tv_sec) {
+               return;
        }
+
+       /*
+        *      Enable the zombie period when we notice that the home
+        *      server hasn't responded for a while.  We back-date the
+        *      zombie period to when we last received a response from
+        *      the home server.
+        */
+       home->state = HOME_STATE_ZOMBIE;
+       
+       home->zombie_period_start.tv_sec = home->last_packet;
+       home->zombie_period_start.tv_sec = USEC / 2;
+       
+       fr_event_delete(el, &home->ev);
+       home->currently_outstanding = 0;
+       home->num_received_pings = 0;
+       
+       radlog(L_PROXY, "Marking home server %s port %d as zombie (it looks like it is dead).",
+              inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr,
+                        buffer, sizeof(buffer)),
+              home->port);
+       
+       /*
+        *      Start pinging the home server.
+        */
+       ping_home_server(home);
 }
 #endif
 
@@ -1133,6 +1200,16 @@ static void wait_a_bit(void *ctx)
 
        rad_assert(request->magic == REQUEST_MAGIC);
 
+#ifdef HAVE_PTHREAD_H
+       /*
+        *      The socket was closed.  Tell the request that
+        *      there is no point in continuing.
+        */
+       if (request->listener->status != RAD_LISTEN_STATUS_KNOWN) {
+               goto stop_processing;
+       }
+#endif
+
 #ifdef WITH_COA
        /*
         *      The CoA request is a new (internally generated)
@@ -1153,6 +1230,25 @@ static void wait_a_bit(void *ctx)
        switch (request->child_state) {
        case REQUEST_QUEUED:
        case REQUEST_RUNNING:
+               /*
+                *      If we're not thread-capable, OR we're capable,
+                *      but have been told to run without threads, and
+                *      the request is still running.  This is usually
+                *      because the request was proxied, and the home
+                *      server didn't respond.
+                */
+#ifdef HAVE_PTHREAD_H
+               if (!have_children)
+#endif
+               {
+                       goto done;
+               }
+
+#ifdef HAVE_PTHREAD_H
+               /*
+                *      If we have threads, wait for the child thread
+                *      to stop.
+                */
                when = request->received;
                when.tv_sec += request->root->max_request_time;
 
@@ -1169,24 +1265,18 @@ static void wait_a_bit(void *ctx)
                 *      Request still has more time.  Continue
                 *      waiting.
                 */
-               if (timercmp(&now, &when, <) ||
-                   ((request->listener->type == RAD_LISTEN_DETAIL) &&
-                    (request->child_state == REQUEST_QUEUED))) {
+               if (timercmp(&now, &when, <)) {
                        if (request->delay < (USEC / 10)) {
                                request->delay = USEC / 10;
                        }
                        request->delay += request->delay >> 1;
 
-#ifdef WITH_DETAIL
                        /*
-                        *      Cap wait at some sane value for detail
-                        *      files.
+                        *      Cap delays at something reasonable.
                         */
-                       if ((request->listener->type == RAD_LISTEN_DETAIL) &&
-                           (request->delay > (request->root->max_request_time * USEC))) {
+                       if (request->delay > (request->root->max_request_time * USEC)) {
                                request->delay = request->root->max_request_time * USEC;
                        }
-#endif
 
                        request->when = now;
                        tv_add(&request->when, request->delay);
@@ -1194,60 +1284,40 @@ static void wait_a_bit(void *ctx)
                        break;
                }
 
-#if defined(HAVE_PTHREAD_H) || defined(WITH_PROXY)
+       stop_processing:
+               request->master_state = REQUEST_STOP_PROCESSING;
+
                /*
                 *      A child thread MAY still be running on the
                 *      request.  Ask the thread to stop working on
                 *      the request.
                 */
-               if (have_children) {
-                       /* FIXME: kill unresponsive children? */
-
-                       /*
-                        *      Print this error message ONLY if
-                        *      there's a child currently processing
-                        *      the request.  As we don't have thread
-                        *      locks here, there may be race
-                        *      conditions on this check.  But it's
-                        *      just an error message, so that's OK.
-                        */
-                       if (!pthread_equal(request->child_pid, NO_SUCH_CHILD_PID)) {
-                               radlog(L_ERR, "WARNING: Unresponsive child for request %d, in module %s component %s",
-                                      request->number,
-                                      request->module ? request->module : "<server core>",
-                                      request->component ? request->component : "<server core>");
-                       }
+               if (have_children &&
+                   (pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) == 0)) {
+                       radlog(L_ERR, "WARNING: Unresponsive child for request %u, in component %s module %s",
+                              request->number,
+                              request->component ? request->component : "<server core>",
+                              request->module ? request->module : "<server core>");
 
-                       request->master_state = REQUEST_STOP_PROCESSING;
-                       
-                       request->delay = USEC / 4;
-                       tv_add(&request->when, request->delay);
-                       callback = wait_for_child_to_die;
-                       break;
                }
+                       
+               request->delay = USEC;
+               tv_add(&request->when, request->delay);
+               callback = wait_for_child_to_die;
+               break;
 #endif
 
                /*
-                *      Else there are no child threads.  We probably
-                *      should have just marked the request as 'done'
-                *      elsewhere, like in the post-proxy-fail
-                *      handler.  But doing that would involve
-                *      checking for max_request_time in multiple
-                *      places, so this may be simplest.
-                */
-               request->child_state = REQUEST_DONE;
-               /* FALL-THROUGH */
-
-               /*
                 *      Mark the request as no longer running,
                 *      and clean it up.
                 */
        case REQUEST_DONE:
+       done:
 #ifdef HAVE_PTHREAD_H
                request->child_pid = NO_SUCH_CHILD_PID;
 #endif
 
-#ifdef WTH_COA
+#ifdef WITH_COA
                /*
                 *      This is a CoA request.  It's been divorced
                 *      from everything else, so we clean it up now.
@@ -1264,7 +1334,6 @@ static void wait_a_bit(void *ctx)
                        return;
                }
 #endif
-               request_stats_final(request);
                cleanup_delay(request);
                return;
 
@@ -1273,7 +1342,6 @@ static void wait_a_bit(void *ctx)
 #ifdef HAVE_PTHREAD_H
                request->child_pid = NO_SUCH_CHILD_PID;
 #endif
-               request_stats_final(request);
 
        case REQUEST_PROXIED:
                rad_assert(request->next_callback != NULL);
@@ -1300,7 +1368,7 @@ static void wait_a_bit(void *ctx)
         *      mode, with no threads...
         */
        if (!callback) {
-               RDEBUG("WARNING: Internal sanity check failed in event handler for request %d: Discarding the request!", request->number);
+               RDEBUG("WARNING: Internal sanity check failed in event handler: Discarding the request!");
                ev_request_free(&request);
                return;
        }
@@ -1336,7 +1404,7 @@ static int update_event_timestamp(RADIUS_PACKET *packet, time_t when)
 {
        VALUE_PAIR *vp;
 
-       vp = pairfind(packet->vps, PW_EVENT_TIMESTAMP);
+       vp = pairfind(packet->vps, PW_EVENT_TIMESTAMP, 0);
        if (!vp) return 0;
 
        vp->vp_date = when;
@@ -1428,16 +1496,40 @@ static void retransmit_coa_request(void *ctx)
        }
        
        if (update_event_timestamp(request->proxy, now.tv_sec)) {
-               if (!insert_into_proxy_hash(request, TRUE)) {
-                       DEBUG("ERROR: Failed re-inserting CoA request into proxy hash.");
+               /*
+                *      Keep a copy of the old Id so that the
+                *      re-transmitted request doesn't re-use the old
+                *      Id.
+                */
+               RADIUS_PACKET old = *request->proxy;
+               home_server *home = request->home_server;
+               rad_listen_t *listener = request->proxy_listener;
+
+               /*
+                *      Don't free the old Id on error.
+                */
+               if (!insert_into_proxy_hash(request)) {
+                       radlog(L_PROXY,"Failed to insert retransmission of CoA request into proxy list.");
                        return;
                }
 
-               request->num_proxied_requests = 0;
-               request->num_proxied_responses = 0;
+               /*
+                *      Now that we have a new Id, free the old one
+                *      and update the various statistics.
+                */
+               PTHREAD_MUTEX_LOCK(&proxy_mutex);
+               fr_packet_list_yank(proxy_list, &old);
+               fr_packet_list_id_free(proxy_list, &old);
+               if (home) home->currently_outstanding--;
+#ifdef WITH_TCP
+               if (listener) listener->count--;
+#endif
+               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+
+       } else {                /* FIXME: protect by a mutex? */
+               request->num_proxied_requests++;
        }
 
-       request->num_proxied_requests++;
        request->num_coa_requests++; /* is NOT reset by code 3 lines above! */
 
        request->proxy_listener->send(request->proxy_listener,
@@ -1460,36 +1552,42 @@ static int originated_coa_request(REQUEST *request)
        rad_assert(!request->in_proxy_hash);
        rad_assert(request->proxy_reply == NULL);
 
-       vp = pairfind(request->config_items, PW_SEND_COA_REQUEST);
-       if (!vp && request->coa) vp = pairfind(request->coa->proxy->vps, PW_SEND_COA_REQUEST);
+       /*
+        *      Check whether we want to originate one, or cancel one.
+        */
+       vp = pairfind(request->config_items, PW_SEND_COA_REQUEST, 0);
+       if (!vp && request->coa) {
+               vp = pairfind(request->coa->proxy->vps, PW_SEND_COA_REQUEST, 0);
+       }
+
        if (vp) {
                if (vp->vp_integer == 0) {
                        ev_request_free(&request->coa);
                        return 1;       /* success */
                }
-
-               if (!request->coa) request_alloc_coa(request);
-               if (!request->coa) return 0;
        }
 
+       if (!request->coa) request_alloc_coa(request);
+       if (!request->coa) return 0;
+
        coa = request->coa;
 
        /*
         *      src_ipaddr will be set up in proxy_encode.
         */
        memset(&ipaddr, 0, sizeof(ipaddr));
-       vp = pairfind(coa->proxy->vps, PW_PACKET_DST_IP_ADDRESS);
+       vp = pairfind(coa->proxy->vps, PW_PACKET_DST_IP_ADDRESS, 0);
        if (vp) {
                ipaddr.af = AF_INET;
                ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
 
        } else if ((vp = pairfind(coa->proxy->vps,
-                                 PW_PACKET_DST_IPV6_ADDRESS)) != NULL) {
+                                 PW_PACKET_DST_IPV6_ADDRESS, 0)) != NULL) {
                ipaddr.af = AF_INET6;
                ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
                
        } else if ((vp = pairfind(coa->proxy->vps,
-                                 PW_HOME_SERVER_POOL)) != NULL) {
+                                 PW_HOME_SERVER_POOL, 0)) != NULL) {
                coa->home_pool = home_pool_byname(vp->vp_strvalue,
                                                  HOME_TYPE_COA);
                if (!coa->home_pool) {
@@ -1530,10 +1628,10 @@ static int originated_coa_request(REQUEST *request)
        } else if (!coa->home_server) {
                int port = PW_COA_UDP_PORT;
 
-               vp = pairfind(coa->proxy->vps, PW_PACKET_DST_PORT);
+               vp = pairfind(coa->proxy->vps, PW_PACKET_DST_PORT, 0);
                if (vp) port = vp->vp_integer;
 
-               coa->home_server = home_server_find(&ipaddr, port);
+               coa->home_server = home_server_find(&ipaddr, port, IPPROTO_UDP);
                if (!coa->home_server) {
                        RDEBUG2("WARNING: Unknown destination %s:%d for CoA request.",
                               inet_ntop(ipaddr.af, &ipaddr.ipaddr,
@@ -1542,7 +1640,7 @@ static int originated_coa_request(REQUEST *request)
                }
        }
 
-       vp = pairfind(coa->proxy->vps, PW_PACKET_TYPE);
+       vp = pairfind(coa->proxy->vps, PW_PACKET_TYPE, 0);
        if (vp) {
                switch (vp->vp_integer) {
                case PW_COA_REQUEST:
@@ -1579,7 +1677,7 @@ static int originated_coa_request(REQUEST *request)
        /*
         *      Call the pre-proxy routines.
         */
-       vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE);
+       vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE, 0);
        if (vp) {
                RDEBUG2("  Found Pre-Proxy-Type %s", vp->vp_strvalue);
                pre_proxy_type = vp->vp_integer;
@@ -1616,8 +1714,8 @@ static int originated_coa_request(REQUEST *request)
        coa->proxy->dst_ipaddr = coa->home_server->ipaddr;
        coa->proxy->dst_port = coa->home_server->port;
 
-       if (!insert_into_proxy_hash(coa, FALSE)) {
-               DEBUG("ERROR: Failed inserting CoA request into proxy hash.");
+       if (!insert_into_proxy_hash(coa)) {
+               radlog(L_PROXY, "Failed to insert CoA request into proxy list.");
                goto fail;
        }
 
@@ -1666,8 +1764,6 @@ static int originated_coa_request(REQUEST *request)
         *      another thread may have picked it up.  Don't
         *      touch it!
         */
-       request->num_proxied_requests = 1;
-       request->num_proxied_responses = 0;
        request->child_pid = NO_SUCH_CHILD_PID;
 
        update_event_timestamp(request->proxy, request->proxy_when.tv_sec);
@@ -1698,7 +1794,7 @@ static int process_proxy_reply(REQUEST *request)
         *      Run the packet through the post-proxy stage,
         *      BEFORE playing games with the attributes.
         */
-       vp = pairfind(request->config_items, PW_POST_PROXY_TYPE);
+       vp = pairfind(request->config_items, PW_POST_PROXY_TYPE, 0);
        if (vp) {
                RDEBUG2("  Found Post-Proxy-Type %s", vp->vp_strvalue);
                post_proxy_type = vp->vp_integer;
@@ -1735,7 +1831,7 @@ static int process_proxy_reply(REQUEST *request)
                 *      the reply.  These include Proxy-State
                 *      attributes from us and remote server.
                 */
-               pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE);
+               pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE, 0);
                
                /*
                 *      Add the attributes left in the proxy
@@ -1784,9 +1880,9 @@ static int request_pre_handler(REQUEST *request)
         */
        if (request->packet->dst_port == 0) {
                request->username = pairfind(request->packet->vps,
-                                            PW_USER_NAME);
+                                            PW_USER_NAME, 0);
                request->password = pairfind(request->packet->vps,
-                                            PW_USER_PASSWORD);
+                                            PW_USER_PASSWORD, 0);
                return 1;
        }
 
@@ -1795,9 +1891,20 @@ static int request_pre_handler(REQUEST *request)
         *      Put the decoded packet into it's proper place.
         */
        if (request->proxy_reply != NULL) {
-               rcode = request->proxy_listener->decode(request->proxy_listener,
-                                                       request);
+               rcode = request->proxy_listener->decode(request->proxy_listener, request);
                DEBUG_PACKET(request, request->proxy_reply, 0);
+
+               /*
+                *      Pro-actively remove it from the proxy hash.
+                *      This is later than in 2.1.x, but it means that
+                *      the replies are authenticated before being
+                *      removed from the hash.
+                */
+               if ((rcode == 0) &&
+                   (request->num_proxied_requests <= request->num_proxied_responses)) {
+                       remove_from_proxy_hash(request);
+               }
+
        } else
 #endif
        if (request->packet->vps == NULL) {
@@ -1825,21 +1932,22 @@ static int request_pre_handler(REQUEST *request)
        }
 
        if (rcode < 0) {
-               radlog(L_ERR, "%s Dropping packet without response.", fr_strerror());
+               RDEBUG("%s Dropping packet without response.", fr_strerror());
+               request->reply->offset = -2; /* bad authenticator */
                request->child_state = REQUEST_DONE;
                return 0;
        }
 
        if (!request->username) {
                request->username = pairfind(request->packet->vps,
-                                            PW_USER_NAME);
+                                            PW_USER_NAME, 0);
        }
 
 #ifdef WITH_PROXY
        if (request->proxy) {
                return process_proxy_reply(request);
-#endif
        }
+#endif
 
        return 1;
 }
@@ -1854,13 +1962,20 @@ static int proxy_request(REQUEST *request)
        struct timeval when;
        char buffer[128];
 
+#ifdef WITH_COA
+       if (request->coa) {
+               RDEBUG("WARNING: Cannot proxy and originate CoA packets at the same time.  Cancelling CoA request");
+               ev_request_free(&request->coa);
+       }
+#endif
+
        if (request->home_server->server) {
-               RDEBUG("ERROR: Cannot perform real proxying to a virtual server.");
+               RDEBUG("ERROR: Cannot proxy to a virtual server.");
                return 0;
        }
 
-       if (!insert_into_proxy_hash(request, FALSE)) {
-               RDEBUG("ERROR: Failed inserting request into proxy hash.");
+       if (!insert_into_proxy_hash(request)) {
+               radlog(L_PROXY, "Failed to insert request into proxy list.");
                return 0;
        }
 
@@ -1881,12 +1996,11 @@ static int proxy_request(REQUEST *request)
        }
        request->next_callback = no_response_to_proxied_request;
 
-       RDEBUG2("Proxying request %d to home server %s port %d",
-              request->number,
+       RDEBUG2("Proxying request to home server %s port %d",
               inet_ntop(request->proxy->dst_ipaddr.af,
                         &request->proxy->dst_ipaddr.ipaddr,
                         buffer, sizeof(buffer)),
-              request->proxy->dst_port);
+               request->proxy->dst_port);
 
        /*
         *      Note that we set proxied BEFORE sending the packet.
@@ -1895,8 +2009,6 @@ static int proxy_request(REQUEST *request)
         *      another thread may have picked it up.  Don't
         *      touch it!
         */
-       request->num_proxied_requests = 1;
-       request->num_proxied_responses = 0;
 #ifdef HAVE_PTHREAD_H
        request->child_pid = NO_SUCH_CHILD_PID;
 #endif
@@ -1950,23 +2062,36 @@ static int proxy_to_virtual_server(REQUEST *request)
 
        RDEBUG2(">>> Sending proxied request internally to virtual server.");
        radius_handle_request(fake, fun);
-       RDEBUG2("<<< Received proxied response from internal virtual server.");
+       RDEBUG2("<<< Received proxied response code %d from internal virtual server.", fake->reply->code);
 
-       request->proxy_reply = fake->reply;
-       fake->reply = NULL;
+       if (fake->reply->code != 0) {
+               request->proxy_reply = fake->reply;
+               fake->reply = NULL;
+       } else {
+               /*
+                *      There was no response
+                */
+               setup_post_proxy_fail(request);
+       }
 
        ev_request_free(&fake);
 
        process_proxy_reply(request);
 
-       if (request->server) RDEBUG("server %s {",
-                                   request->server != NULL ?
-                                   request->server : ""); 
-       fun(request);
-       
-       if (request->server) RDEBUG("} # server %s",
-                                   request->server != NULL ?
-                                   request->server : "");
+       /*
+        *      Process it through the normal section again, but ONLY
+        *      if we received a proxy reply..
+        */
+       if (request->proxy_reply) {
+               if (request->server) RDEBUG("server %s {",
+                                           request->server != NULL ?
+                                           request->server : ""); 
+               fun(request);
+               
+               if (request->server) RDEBUG("} # server %s",
+                                           request->server != NULL ?
+                                           request->server : "");
+       }
 
        return 2;               /* success, but NOT '1' !*/
 }
@@ -1998,11 +2123,11 @@ static int successfully_proxied_request(REQUEST *request)
                return 0;
        }
 
-       realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM);
+       realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM, 0);
        if (!realmpair || (realmpair->length == 0)) {
                int pool_type;
 
-               vp = pairfind(request->config_items, PW_HOME_SERVER_POOL);
+               vp = pairfind(request->config_items, PW_HOME_SERVER_POOL, 0);
                if (!vp) return 0;
 
                switch (request->packet->code) {
@@ -2103,7 +2228,7 @@ found_pool:
         *      requests.
         */
        if (realm && (realm->striprealm == TRUE) &&
-          (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME)) != NULL) {
+          (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME, 0)) != NULL) {
                /*
                 *      If there's a Stripped-User-Name attribute in
                 *      the request, then use THAT as the User-Name
@@ -2117,10 +2242,10 @@ found_pool:
                 *      from the vps list, and making the new
                 *      User-Name the head of the vps list.
                 */
-               vp = pairfind(request->proxy->vps, PW_USER_NAME);
+               vp = pairfind(request->proxy->vps, PW_USER_NAME, 0);
                if (!vp) {
                        vp = radius_paircreate(request, NULL,
-                                              PW_USER_NAME, PW_TYPE_STRING);
+                                              PW_USER_NAME, 0, PW_TYPE_STRING);
                        rad_assert(vp != NULL); /* handled by above function */
                        /* Insert at the START of the list */
                        vp->next = request->proxy->vps;
@@ -2142,10 +2267,10 @@ found_pool:
         *      anymore - we changed it.
         */
        if ((request->packet->code == PW_AUTHENTICATION_REQUEST) &&
-           pairfind(request->proxy->vps, PW_CHAP_PASSWORD) &&
-           pairfind(request->proxy->vps, PW_CHAP_CHALLENGE) == NULL) {
+           pairfind(request->proxy->vps, PW_CHAP_PASSWORD, 0) &&
+           pairfind(request->proxy->vps, PW_CHAP_CHALLENGE, 0) == NULL) {
                vp = radius_paircreate(request, &request->proxy->vps,
-                                      PW_CHAP_CHALLENGE, PW_TYPE_OCTETS);
+                                      PW_CHAP_CHALLENGE, 0, PW_TYPE_OCTETS);
                vp->length = AUTH_VECTOR_LEN;
                memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
        }
@@ -2155,7 +2280,7 @@ found_pool:
         *      doesn't need it.
         */
        vp = radius_paircreate(request, &request->proxy->vps,
-                              PW_PROXY_STATE, PW_TYPE_OCTETS);
+                              PW_PROXY_STATE, 0, PW_TYPE_OCTETS);
        snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%d",
                 request->packet->id);
        vp->length = strlen(vp->vp_strvalue);
@@ -2169,7 +2294,7 @@ found_pool:
        /*
         *      Call the pre-proxy routines.
         */
-       vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE);
+       vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE, 0);
        if (vp) {
                RDEBUG2("  Found Pre-Proxy-Type %s", vp->vp_strvalue);
                pre_proxy_type = vp->vp_integer;
@@ -2225,7 +2350,7 @@ found_pool:
        }
 
        if (!proxy_request(request)) {
-               RDEBUG("ERROR: Failed to proxy request %d", request->number);
+               RDEBUG("ERROR: Failed to proxy request");
                return -1;
        }
        
@@ -2242,7 +2367,7 @@ static void request_post_handler(REQUEST *request)
        if ((request->master_state == REQUEST_STOP_PROCESSING) ||
            (request->parent &&
             (request->parent->master_state == REQUEST_STOP_PROCESSING))) {
-               RDEBUG2("Request %d was cancelled.", request->number);
+               RDEBUG2("request was cancelled.");
 #ifdef HAVE_PTHREAD_H
                request->child_pid = NO_SUCH_CHILD_PID;
 #endif
@@ -2269,8 +2394,12 @@ static void request_post_handler(REQUEST *request)
        }
 #endif
 
-       if ((request->reply->code == 0) &&
-           ((vp = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL) &&
+       /*
+        *      Catch Auth-Type := Reject BEFORE proxying the packet.
+        */
+       if ((request->packet->code == PW_AUTHENTICATION_REQUEST) &&
+           (request->reply->code == 0) &&
+           ((vp = pairfind(request->config_items, PW_AUTH_TYPE, 0)) != NULL) &&
            (vp->vp_integer == PW_AUTHTYPE_REJECT)) {
                request->reply->code = PW_AUTHENTICATION_REJECT;
        }
@@ -2305,6 +2434,13 @@ static void request_post_handler(REQUEST *request)
                 *      OR we proxied it internally to a virutal server.
                 */
        }
+
+#ifdef WITH_COA
+       else if (request->proxy && request->coa) {
+               RDEBUG("WARNING: Cannot proxy and originate CoA packets at the same time.  Cancelling CoA request");
+               ev_request_free(&request->coa);
+       }
+#endif
 #endif
 
        /*
@@ -2325,7 +2461,7 @@ static void request_post_handler(REQUEST *request)
        /*
         *      Copy Proxy-State from the request to the reply.
         */
-       vp = paircopy2(request->packet->vps, PW_PROXY_STATE);
+       vp = paircopy2(request->packet->vps, PW_PROXY_STATE, 0);
        if (vp) pairadd(&request->reply->vps, vp);
 #endif
 
@@ -2341,15 +2477,13 @@ static void request_post_handler(REQUEST *request)
                         *      Check if the lack of response is intentional.
                         */
                        vp = pairfind(request->config_items,
-                                     PW_RESPONSE_PACKET_TYPE);
+                                     PW_RESPONSE_PACKET_TYPE, 0);
                        if (!vp) {
-                               RDEBUG2("There was no response configured: rejecting request %d",
-                                      request->number);
+                               RDEBUG2("There was no response configured: rejecting request");
                                request->reply->code = PW_AUTHENTICATION_REJECT;
 
                        } else if (vp->vp_integer == 256) {
-                               RDEBUG2("Not responding to request %d",
-                                      request->number);
+                               RDEBUG2("Not responding to request");
 
                                /*
                                 *      Force cleanup after a long
@@ -2372,7 +2506,7 @@ static void request_post_handler(REQUEST *request)
                 *      Post-Auth-Type = Reject
                 */
                if (request->reply->code == PW_AUTHENTICATION_REJECT) {
-                       pairdelete(&request->config_items, PW_POST_AUTH_TYPE);
+                       pairdelete(&request->config_items, PW_POST_AUTH_TYPE, 0);
                        vp = radius_pairmake(request, &request->config_items,
                                             "Post-Auth-Type", "Reject",
                                             T_OP_SET);
@@ -2388,8 +2522,7 @@ static void request_post_handler(REQUEST *request)
                        when.tv_sec += request->root->reject_delay;
 
                        if (timercmp(&when, &request->next_when, >)) {
-                               RDEBUG2("Delaying reject of request %d for %d seconds",
-                                      request->number,
+                               RDEBUG2("Delaying reject  for %d seconds",
                                       request->root->reject_delay);
                                request->next_when = when;
                                request->next_callback = reject_delay;
@@ -2425,14 +2558,9 @@ static void request_post_handler(REQUEST *request)
                break;
 
        default:
-               if ((request->packet->code > 1024) &&
-                   (request->packet->code < (1024 + 254 + 1))) {
-                       request->next_callback = NULL;
-                       child_state = REQUEST_DONE;
-                       break;
-               }
-
-               radlog(L_ERR, "Unknown packet type %d", request->packet->code);
+               /*
+                *      DHCP, VMPS, etc.
+                */
                request->next_callback = NULL;
                child_state = REQUEST_DONE;
                break;
@@ -2445,8 +2573,11 @@ static void request_post_handler(REQUEST *request)
         *      and it should re-send it.
         *      If configured, encode, sign, and send.
         */
-       if ((request->reply->code != 0) ||
-           (request->listener->type == RAD_LISTEN_DETAIL)) {
+       if ((request->reply->code != 0)
+#ifdef WITH_DETAIL
+           || (request->listener->type == RAD_LISTEN_DETAIL)
+#endif
+           ) {
                DEBUG_PACKET(request, request->reply, 1);
                request->listener->send(request->listener, request);
        }
@@ -2454,10 +2585,14 @@ static void request_post_handler(REQUEST *request)
 #ifdef WITH_COA
        /*
         *      Now that we've completely processed the request,
-        *      see if we need to originate a CoA request.
+        *      see if we need to originate a CoA request.  But ONLY
+        *      if it wasn't proxied.
         */
-       if (request->coa ||
-           (pairfind(request->config_items, PW_SEND_COA_REQUEST) != NULL)) {
+       if (!request->proxy &&
+           (request->packet->code != PW_COA_REQUEST) &&
+           (request->packet->code != PW_DISCONNECT_REQUEST) &&
+           (request->coa ||
+            (pairfind(request->config_items, PW_SEND_COA_REQUEST, 0) != NULL))) {
                if (!originated_coa_request(request)) {
                        RDEBUG2("Do CoA Fail handler here");
                }
@@ -2500,7 +2635,7 @@ static void request_post_handler(REQUEST *request)
        }
 #endif
 
-       RDEBUG2("Finished request %d.", request->number);
+       RDEBUG2("Finished request.");
        rad_assert(child_state >= 0);
        request->child_state = child_state;
 
@@ -2511,12 +2646,101 @@ static void request_post_handler(REQUEST *request)
 }
 
 
-static void received_retransmit(REQUEST *request, const RADCLIENT *client)
-{
 #ifdef WITH_PROXY
-       char buffer[128];
+static void rad_retransmit_packet(REQUEST *request)
+{
+       char buffer[256];
+
+#ifdef WITH_TCP
+       if (request->home_server->proto == IPPROTO_TCP) {
+               DEBUG2("Suppressing duplicate proxied request to home server %s port %d proto TCP - ID: %d",
+                      inet_ntop(request->proxy->dst_ipaddr.af,
+                                &request->proxy->dst_ipaddr.ipaddr,
+                                buffer, sizeof(buffer)),
+                      request->proxy->dst_port,
+                      request->proxy->id);
+               return;         /* don't do anything else */
+       }
+#endif
+
+       RDEBUG2("Sending duplicate proxied request to home server %s port %d - ID: %d",
+               inet_ntop(request->proxy->dst_ipaddr.af,
+                         &request->proxy->dst_ipaddr.ipaddr,
+                         buffer, sizeof(buffer)),
+               request->proxy->dst_port,
+               request->proxy->id);
+       request->num_proxied_requests++;
+
+       DEBUG_PACKET(request, request->proxy, 1);
+       request->proxy_listener->send(request->proxy_listener,
+                                     request);
+}
+
+
+static int rad_retransmit(REQUEST *request)
+{
+       /*
+        *      If we've just discovered that the home server
+        *      is dead, OR the socket has been closed, look for
+        *      another connection to a home server.
+        */
+       if ((request->home_server->state == HOME_STATE_IS_DEAD) ||
+           (request->proxy_listener->status != RAD_LISTEN_STATUS_KNOWN)) {
+               home_server *home;
+               
+               remove_from_proxy_hash(request);
+               
+               home = home_server_ldb(NULL, request->home_pool, request);
+               if (!home) {
+                       RDEBUG2("ERROR: Failed to find live home server for request");
+               no_home_servers:
+                       /*
+                        *      Do post-request processing,
+                        *      and any insertion of necessary
+                        *      events.
+                        */
+                       post_proxy_fail_handler(request);
+                       return 1;
+               }
+
+               request->proxy->code = request->packet->code;
+
+               /*
+                *      Free the old packet, to force re-encoding
+                */
+               free(request->proxy->data);
+               request->proxy->data = NULL;
+               request->proxy->data_len = 0;
+
+               /*
+                *      This request failed over to a virtual
+                *      server.  Push it back onto the queue
+                *      to be processed.
+                */
+               if (request->home_server->server) {
+                       proxy_fallback_handler(request);
+                       return 1;
+               }
+
+               /*
+                *      Try to proxy the request.
+                */
+               if (!proxy_request(request)) {
+                       RDEBUG("ERROR: Failed to re-proxy request");
+                       goto no_home_servers;
+               }
+               return 1;
+       } /* else the home server is still alive */
+
+       rad_retransmit_packet(request);
+
+       return 1;
+}
 #endif
 
+static void received_retransmit(REQUEST *request, const RADCLIENT *client)
+{
+
        RAD_STATS_TYPE_INC(request->listener, total_dup_requests);
        RAD_STATS_CLIENT_INC(request->listener, client, total_dup_requests);
        
@@ -2527,7 +2751,7 @@ static void received_retransmit(REQUEST *request, const RADCLIENT *client)
        discard:
 #endif
                radlog(L_ERR, "Discarding duplicate request from "
-                      "client %s port %d - ID: %d due to unfinished request %d",
+                      "client %s port %d - ID: %d due to unfinished request %u",
                       client->shortname,
                       request->packet->src_port,request->packet->id,
                       request->number);
@@ -2561,76 +2785,25 @@ static void received_retransmit(REQUEST *request, const RADCLIENT *client)
                check_for_zombie_home_server(request);
 
                /*
-                *      If we've just discovered that the home server is
-                *      dead, send the packet to another one.
+                *      Home server is still alive, and the proxy
+                *      socket is OK.  Just re-send the packet.
                 */
-               if ((request->packet->dst_port != 0) &&
-                   (request->home_server->state == HOME_STATE_IS_DEAD)) {
-                       home_server *home;
-
-                       remove_from_proxy_hash(request);
-
-                       home = home_server_ldb(NULL, request->home_pool, request);
-                       if (!home) {
-                               RDEBUG2("Failed to find live home server for request %d", request->number);
-                       no_home_servers:
-                               /*
-                                *      Do post-request processing,
-                                *      and any insertion of necessary
-                                *      events.
-                                */
-                               post_proxy_fail_handler(request);
-                               return;
-                       }
-
-                       request->proxy->code = request->packet->code;
-
-                       /*
-                        *      Free the old packet, to force re-encoding
-                        */
-                       free(request->proxy->data);
-                       request->proxy->data = NULL;
-                       request->proxy->data_len = 0;
-
-                       /*
-                        *      This request failed over to a virtual
-                        *      server.  Push it back onto the queue
-                        *      to be processed.
-                        */
-                       if (request->home_server->server) {
-                               proxy_fallback_handler(request);
-                               return;
-                       }
-
-                       /*
-                        *      Try to proxy the request.
-                        */
-                       if (!proxy_request(request)) {
-                               RDEBUG("ERROR: Failed to re-proxy request %d", request->number);
-                               goto no_home_servers;
-                       }
-
-                       /*
-                        *      This code executes in the main server
-                        *      thread, so there's no need for locking.
-                        */
-                       rad_assert(request->next_callback != NULL);
-                       INSERT_EVENT(request->next_callback, request);
-                       request->next_callback = NULL;
-                       return;
-               } /* else the home server is still alive */
+               if ((request->home_server->state != HOME_STATE_IS_DEAD) &&
+                   (request->proxy_listener->status == RAD_LISTEN_STATUS_KNOWN)) {
+                       rad_retransmit_packet(request);
+                       break;
+               }
 
-               RDEBUG2("Sending duplicate proxied request to home server %s port %d - ID: %d",
-                      inet_ntop(request->proxy->dst_ipaddr.af,
-                                &request->proxy->dst_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      request->proxy->dst_port,
-                      request->proxy->id);
-               request->num_proxied_requests++;
+               /*
+                *      Otherwise, we need to fail over to another
+                *      home server, and possibly run "post-proxy-type
+                *      fail".  Add an event waiting for the child to
+                *      have a result.
+                */
+               INSERT_EVENT(wait_a_bit, request);
 
-               DEBUG_PACKET(request, request->proxy, 1);
-               request->proxy_listener->send(request->proxy_listener,
-                                             request);
+               request->priority = RAD_LISTEN_PROXY;
+               thread_pool_addrequest(request, rad_retransmit);
                break;
 #endif
 
@@ -2673,7 +2846,7 @@ static void received_conflicting_request(REQUEST *request,
                                         const RADCLIENT *client)
 {
        radlog(L_ERR, "Received conflicting packet from "
-              "client %s port %d - ID: %d due to unfinished request %d.  Giving up on old request.",
+              "client %s port %d - ID: %d due to unfinished request %u.  Giving up on old request.",
               client->shortname,
               request->packet->src_port, request->packet->id,
               request->number);
@@ -2685,13 +2858,10 @@ static void received_conflicting_request(REQUEST *request,
        remove_from_request_hash(request);
 
        switch (request->child_state) {
-#ifdef HAVE_PTHREAD_H
                /*
-                *      It's queued or running.  Tell it to stop, and
-                *      wait for it to do so.
+                *      Tell it to stop, and wait for it to do so.
                 */
-       case REQUEST_QUEUED:
-       case REQUEST_RUNNING:
+       default:
                request->master_state = REQUEST_STOP_PROCESSING;
                request->delay += request->delay >> 1;
 
@@ -2699,15 +2869,15 @@ static void received_conflicting_request(REQUEST *request,
 
                INSERT_EVENT(wait_for_child_to_die, request);
                return;
-#endif
 
                /*
-                *      It's in some other state, and therefore also
-                *      in the event queue.  At some point, the
-                *      child will notice, and we can then delete it.
+                *      Catch race conditions.  It may have switched
+                *      from running to done while this code is being
+                *      executed.
                 */
-       default:
-               rad_assert(request->ev != NULL);
+       case REQUEST_REJECT_DELAY:
+       case REQUEST_CLEANUP_DELAY:
+       case REQUEST_DONE:
                break;
        }
 }
@@ -2802,7 +2972,7 @@ int received_request(rad_listen_t *listener,
                         */
                        if ((request->reply->code != 0) &&
                            request->reply->data) {
-                               radlog(L_INFO, "WARNING: Allowing fast client %s port %d - ID: %d for recent request %d.",
+                               radlog(L_INFO, "WARNING: Allowing fast client %s port %d - ID: %d for recent request %u.",
                                       client->shortname,
                                       packet->src_port, packet->id,
                                       request->number);
@@ -2825,7 +2995,7 @@ int received_request(rad_listen_t *listener,
                         */
                        if (timercmp(&when, &request->received, <)) {
                                radlog(L_ERR, "Discarding conflicting packet from "
-                                      "client %s port %d - ID: %d due to recent request %d.",
+                                      "client %s port %d - ID: %d due to recent request %u.",
                                       client->shortname,
                                       packet->src_port, packet->id,
                                       request->number);
@@ -2849,8 +3019,11 @@ int received_request(rad_listen_t *listener,
        /*
         *      We may want to quench the new request.
         */
-       if ((listener->type != RAD_LISTEN_DETAIL) &&
-           !can_handle_new_request(packet, client, root)) {
+       if (
+#ifdef WITH_DETAIL
+           (listener->type != RAD_LISTEN_DETAIL) &&
+#endif
+           !can_handle_new_request(packet, client, root)) {
                return 0;
        }
 
@@ -2861,7 +3034,7 @@ int received_request(rad_listen_t *listener,
 
        if ((request->reply = rad_alloc(0)) == NULL) {
                radlog(L_ERR, "No memory");
-               exit(1);
+               return 0;
        }
 
        request->listener = listener;
@@ -2894,7 +3067,7 @@ int received_request(rad_listen_t *listener,
         *      Remember the request in the list.
         */
        if (!fr_packet_list_insert(pl, &request->packet)) {
-               radlog(L_ERR, "Failed to insert request %d in the list of live requests: discarding", request->number);
+               radlog(L_ERR, "Failed to insert request %u in the list of live requests: discarding", request->number);
                ev_request_free(&request);
                return 0;
        }
@@ -2902,6 +3075,9 @@ int received_request(rad_listen_t *listener,
        request->in_request_hash = TRUE;
        request->root = root;
        root->refcount++;
+#ifdef WITH_TCP
+       request->listener->count++;
+#endif
 
        /*
         *      The request passes many of our sanity checks.
@@ -2953,7 +3129,10 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
        REQUEST         *request;
 
        /*
-        *      Also removes from the proxy hash if responses == requests
+        *      Lookup *without* removal.  In versions prior to 2.2.0,
+        *      this did lookup *and* removal.  That method allowed
+        *      attackers to spoof replies that caused entries to be
+        *      removed from the proxy hash prior to validation.
         */
        request = lookup_in_proxy_hash(packet);
 
@@ -2967,53 +3146,80 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
        }
 
        /*
-        *      We haven't replied to the NAS, but we have seen an
-        *      earlier reply from the home server.  Ignore this packet,
-        *      as we're likely still processing the previous reply.
+        *      There's a reply: discard it if it's a conflicting one.
         */
        if (request->proxy_reply) {
+               /*
+                *      ? The home server gave us a new proxy
+                *      reply which doesn't match the old
+                *      one.  Delete it.
+                */
                if (memcmp(request->proxy_reply->vector,
                           packet->vector,
-                          sizeof(request->proxy_reply->vector)) == 0) {
-                       RDEBUG2("Discarding duplicate reply from host %s port %d  - ID: %d for request %d",
-                              inet_ntop(packet->src_ipaddr.af,
-                                        &packet->src_ipaddr.ipaddr,
-                                        buffer, sizeof(buffer)),
-                              packet->src_port, packet->id,
-                              request->number);
-               } else {
-                       /*
-                        *      ? The home server gave us a new proxy
-                        *      reply which doesn't match the old
-                        *      one.  Delete it.
-                        */
+                          sizeof(request->proxy_reply->vector)) != 0) {
                        RDEBUG2("Ignoring conflicting proxy reply");
-               }
+                       
                
-               /* assert that there's an event queued for request? */
+                       /* assert that there's an event queued for request? */
+                       return NULL;
+               } /* else it had previously passed verification */
+
+               /*
+                *      Verify the packet before doing ANYTHING with
+                *      it.  This means we're doing more MD5 checks in
+                *      the server core.  However, we can fix that by
+                *      moving to multiple threads listening on
+                *      sockets.
+                *
+                *      We do this AFTER looking the request up in the
+                *      hash, and AFTER checking if we saw a previous
+                *      request.  This helps minimize the DoS effect
+                *      of people attacking us with spoofed packets.
+                *
+                *      FIXME: move the "read from proxy socket" code
+                *      into one (or more) threads.  Have it read from
+                *      the socket, do the validation, and write a
+                *      pointer to the packet into a pipe? Or queue it
+                *      to the main server?
+                */
+       } else if (rad_verify(packet, request->proxy,
+                             request->home_server->secret) != 0) {
+               DEBUG("Ignoring spoofed proxy reply.  Signature is invalid");
                return NULL;
        }
 
        /*
-        *      Verify the packet before doing ANYTHING with it.  This
-        *      means we're doing more MD5 checks in the server core.
-        *      However, we can fix that by moving to multiple threads
-        *      listening on sockets.
-        *
-        *      We do this AFTER looking the request up in the hash,
-        *      and AFTER vhecking if we saw a previous request.  This
-        *      helps minimize the DoS effect of people attacking us
-        *      with spoofed packets.
+        *      Check (again) if it's a duplicate reply.  We do this
+        *      after deleting the packet from the proxy hash.
         */
-       if (rad_verify(packet, request->proxy,
-                      request->home_server->secret) != 0) {
-               DEBUG("Ignoring spoofed proxy reply.  Signature is invalid");
-               return NULL;
+       if (request->proxy_reply) {
+               RDEBUG2("Discarding duplicate reply from host %s port %d  - ID: %d",
+                       inet_ntop(packet->src_ipaddr.af,
+                                 &packet->src_ipaddr.ipaddr,
+                                 buffer, sizeof(buffer)),
+                       packet->src_port, packet->id);
        }
 
        gettimeofday(&now, NULL);
 
        /*
+        *      "ping" packets have a different algorithm for marking
+        *      a home server alive.  They also skip all of the CoA,
+        *      etc. checks.
+        */
+       if (!request->packet) {
+               request->proxy_reply = packet;
+#ifdef WITH_TCP
+               rad_assert(request->home_server != NULL);
+               if (request->home_server->proto != IPPROTO_TCP)
+#endif
+                       received_response_to_ping(request);
+               request->proxy_reply = NULL; /* caller will free it */
+               ev_request_free(&request);
+               return NULL;
+       }
+
+       /*
         *      Maybe move this earlier in the decision process?
         *      Having it here means that late or duplicate proxy
         *      replies no longer get the home server marked as
@@ -3023,8 +3229,11 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
         *      receive a packet?  Setting this here means that we
         *      mark it alive on *any* packet, even if it's lost all
         *      of the *other* packets in the last 10s.
+        *
+        *      This behavior could be configurable.
         */
        request->home_server->state = HOME_STATE_ALIVE;
+       request->home_server->last_packet = now.tv_sec;
        
 #ifdef WITH_COA
        /*
@@ -3041,6 +3250,18 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
                request->parent->coa = NULL;
                request->parent = NULL;
 
+               /*
+                *      The proxied packet was different from the
+                *      original packet, AND the proxied packet was
+                *      a CoA: allow it.
+                */
+       } else if ((request->packet->code != request->proxy->code) &&
+                  ((request->proxy->code == PW_COA_REQUEST) ||
+                   (request->proxy->code == PW_DISCONNECT_REQUEST))) {
+         /*
+          *    It's already divorced: do nothing.
+          */
+         
        } else
                /*
                 *      Skip the next set of checks, as the original
@@ -3061,7 +3282,7 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
                RDEBUG2("Ignoring proxy reply that arrived after we sent a reply to the NAS");
                return NULL;
        }
-       
+
 #ifdef WITH_STATS
        /*
         *      The average includes our time to receive packets and
@@ -3086,7 +3307,7 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
        case REQUEST_REJECT_DELAY:
        case REQUEST_CLEANUP_DELAY:
        case REQUEST_DONE:
-               radlog(L_ERR, "Reply from home server %s port %d  - ID: %d arrived too late for request %d. Try increasing 'retry_delay' or 'max_request_time'",
+               radlog(L_ERR, "Reply from home server %s port %d  - ID: %d arrived too late for request %u. Try increasing 'retry_delay' or 'max_request_time'",
                       inet_ntop(packet->src_ipaddr.af,
                                 &packet->src_ipaddr.ipaddr,
                                 buffer, sizeof(buffer)),
@@ -3137,17 +3358,6 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
        }
 #endif
 
-       /*
-        *      There's no incoming request, so it's a proxied packet
-        *      we originated.
-        */
-       if (!request->packet) {
-               received_response_to_ping(request);
-               request->proxy_reply = NULL; /* caller will free it */
-               ev_request_free(&request);
-               return NULL;
-       }
-
        request->child_state = REQUEST_QUEUED;
        request->when = now;
        request->delay = USEC;
@@ -3161,53 +3371,397 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet)
 
        return request;
 }
+
+#endif /* WITH_PROXY */
+
+#ifdef WITH_TCP
+static void tcp_socket_lifetime(void *ctx)
+{
+       rad_listen_t *listener = ctx;
+       char buffer[256];
+
+       listener->print(listener, buffer, sizeof(buffer));
+
+       DEBUG("Reached maximum lifetime on socket %s", buffer);
+
+       listener->status = RAD_LISTEN_STATUS_CLOSED;
+       event_new_fd(listener);
+}
+
+static void tcp_socket_idle_timeout(void *ctx)
+{
+       rad_listen_t *listener = ctx;
+       listen_socket_t *sock = listener->data;
+       char buffer[256];
+
+       fr_event_now(el, &now); /* should always succeed... */
+
+       rad_assert(sock->home != NULL);
+
+       /*
+        *      We implement idle timeout by polling, because it's
+        *      cheaper than resetting the idle timeout every time
+        *      we send / receive a packet.
+        */
+       if ((sock->last_packet + sock->home->idle_timeout) > now.tv_sec) {
+               struct timeval when;
+               void *fun = tcp_socket_idle_timeout;
+               
+               when.tv_sec = sock->last_packet;
+               when.tv_sec += sock->home->idle_timeout;
+               when.tv_usec = 0;
+
+               if (sock->home->lifetime &&
+                   (sock->opened + sock->home->lifetime < when.tv_sec)) {
+                       when.tv_sec = sock->opened + sock->home->lifetime;
+                       fun = tcp_socket_lifetime;
+               }
+               
+               if (!fr_event_insert(el, fun, listener, &when, &sock->ev)) {
+                       rad_panic("Failed to insert event");
+               }
+
+               return;
+       }
+
+       listener->print(listener, buffer, sizeof(buffer));
+       
+       DEBUG("Reached idle timeout on socket %s", buffer);
+
+       listener->status = RAD_LISTEN_STATUS_CLOSED;
+       event_new_fd(listener);
+}
 #endif
 
-void event_new_fd(rad_listen_t *this)
+int event_new_fd(rad_listen_t *this)
 {
        char buffer[1024];
 
-       if (this->status == RAD_LISTEN_STATUS_KNOWN) return;
-       
+       if (this->status == RAD_LISTEN_STATUS_KNOWN) return 1;
+
        this->print(this, buffer, sizeof(buffer));
-       
+
        if (this->status == RAD_LISTEN_STATUS_INIT) {
                if (just_started) {
                        DEBUG("Listening on %s", buffer);
                } else {
-                       DEBUG2(" ... adding new socket %s", buffer);
+                       radlog(L_INFO, " ... adding new socket %s", buffer);
+               }
+
+#ifdef WITH_PROXY
+               /*
+                *      Add it to the list of sockets we can use.
+                *      Server sockets (i.e. auth/acct) are never
+                *      added to the packet list.
+                */
+               if (this->type == RAD_LISTEN_PROXY) {
+                       listen_socket_t *sock = this->data;
+
+                       PTHREAD_MUTEX_LOCK(&proxy_mutex);
+                       if (!fr_packet_list_socket_add(proxy_list, this->fd,
+                                                      sock->proto,
+                                                      &sock->other_ipaddr, sock->other_port,
+                                                      this)) {
+
+                               proxy_no_new_sockets = TRUE;
+                               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+
+                               /*
+                                *      This is bad.  However, the
+                                *      packet list now supports 256
+                                *      open sockets, which should
+                                *      minimize this problem.
+                                */
+                               radlog(L_ERR, "Failed adding proxy socket: %s",
+                                      fr_strerror());
+                               return 0;
+                       }
+
+                       if (sock->home) {
+                               sock->home->num_connections++;
+                               
+                               /*
+                                *      If necessary, add it to the list of
+                                *      new proxy listeners.
+                                */
+                               if (sock->home->lifetime || sock->home->idle_timeout) {
+                                       this->next = proxy_listener_list;
+                                       proxy_listener_list = this;
+                               }
+                       }
+                       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+
+                       /*
+                        *      Tell the main thread that we've added
+                        *      a proxy listener, but only if we need
+                        *      to update the event list.  Do this
+                        *      with the mutex unlocked, to reduce
+                        *      contention.
+                        */
+                       if (sock->home) {
+                               if (sock->home->lifetime || sock->home->idle_timeout) {
+                                       radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD);
+                               }
+                       }
+               }
+#endif         
+
+#ifdef WITH_DETAIL
+               /*
+                *      Detail files are always known, and aren't
+                *      put into the socket event loop.
+                */
+               if (this->type == RAD_LISTEN_DETAIL) {
+                       this->status = RAD_LISTEN_STATUS_KNOWN;
+                       
+                       /*
+                        *      Set up the first poll interval.
+                        */
+                       event_poll_detail(this);
+                       return 1;
                }
+#endif
+
+               FD_MUTEX_LOCK(&fd_mutex);
                if (!fr_event_fd_insert(el, 0, this->fd,
                                        event_socket_handler, this)) {
-                       radlog(L_ERR, "Failed remembering handle for proxy socket!");
+                       radlog(L_ERR, "Failed adding event handler for proxy socket!");
                        exit(1);
                }
+               FD_MUTEX_UNLOCK(&fd_mutex);
                
                this->status = RAD_LISTEN_STATUS_KNOWN;
-               return;
+               return 1;
        }
-       
-       if (this->status == RAD_LISTEN_STATUS_CLOSED) {
-               DEBUG2(" ... closing socket %s", buffer);
-               
+
+       /*
+        *      Something went wrong with the socket: make it harmless.
+        */
+       if (this->status == RAD_LISTEN_STATUS_REMOVE_FD) {
+               int devnull;
+
+               /*
+                *      Remove it from the list of live FD's.
+                */
+               FD_MUTEX_LOCK(&fd_mutex);
                fr_event_fd_delete(el, 0, this->fd);
+               FD_MUTEX_UNLOCK(&fd_mutex);
+
+#ifdef WITH_TCP
+               /*
+                *      We track requests using this socket only for
+                *      TCP.  For UDP, we don't currently close
+                *      sockets.
+                */
+#ifdef WITH_PROXY
+               if (this->type != RAD_LISTEN_PROXY)
+#endif
+               {
+                       if (this->count != 0) {
+                               fr_packet_list_walk(pl, this,
+                                                   remove_all_requests);
+                       }
+
+                       if (this->count == 0) {
+                               this->status = RAD_LISTEN_STATUS_FINISH;
+                               goto finish;
+                       }
+               }               
+#ifdef WITH_PROXY
+               else {
+                       int count;
+
+                       /*
+                        *      Duplicate code
+                        */
+                       PTHREAD_MUTEX_LOCK(&proxy_mutex);
+                       if (!fr_packet_list_socket_freeze(proxy_list,
+                                                         this->fd)) {
+                               radlog(L_ERR, "Fatal error freezing socket: %s",
+                                      fr_strerror());
+                               exit(1);
+                       }
+
+                       /*
+                        *      Doing this with the proxy mutex held
+                        *      is a Bad Thing.  We should move to
+                        *      finer-grained mutexes.
+                        */
+                       count = this->count;
+                       if (count > 0) {
+                               fr_packet_list_walk(proxy_list, this,
+                                                   remove_all_proxied_requests);
+                       }
+                       count = this->count; /* protected by mutex */
+                       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+
+                       if (count == 0) {
+                               this->status = RAD_LISTEN_STATUS_FINISH;
+                               goto finish;
+                       }
+               }
+#endif /* WITH_PROXY */
+#endif /* WITH_TCP */
+
+               /*
+                *      Re-open the socket, pointing it to /dev/null.
+                *      This means that all writes proceed without
+                *      blocking, and all reads return "no data".
+                *
+                *      This leaves the socket active, so any child
+                *      threads won't go insane.  But it means that
+                *      they cannot send or receive any packets.
+                *
+                *      This is EXTRA work in the normal case, when
+                *      sockets are closed without error.  But it lets
+                *      us have one simple processing method for all
+                *      sockets.
+                */
+               devnull = open("/dev/null", O_RDWR);
+               if (devnull < 0) {
+                       radlog(L_ERR, "FATAL failure opening /dev/null: %s",
+                              strerror(errno));
+                       exit(1);
+               }
+               if (dup2(devnull, this->fd) < 0) {
+                       radlog(L_ERR, "FATAL failure closing socket: %s",
+                              strerror(errno));
+                       exit(1);
+               }
+               close(devnull);
+
+               this->status = RAD_LISTEN_STATUS_CLOSED;
+
+               /*
+                *      Fall through to the next section.
+                */
+       }
+
+#ifdef WITH_TCP
+       /*
+        *      Called ONLY from the main thread.  On the following
+        *      conditions:
+        *
+        *      idle timeout
+        *      max lifetime
+        *
+        *      (and falling through from "forcibly close FD" above)
+        *      client closed connection on us
+        *      client sent us a bad packet.
+        */
+       if (this->status == RAD_LISTEN_STATUS_CLOSED) {
+               int count = this->count;
+
+#ifdef WITH_DETAIL
+               rad_assert(this->type != RAD_LISTEN_DETAIL);
+#endif
+
+#ifdef WITH_PROXY
+               /*
+                *      Remove it from the list of active sockets, so
+                *      that it isn't used when proxying new packets.
+                */
+               if (this->type == RAD_LISTEN_PROXY) {
+                       PTHREAD_MUTEX_LOCK(&proxy_mutex);
+                       if (!fr_packet_list_socket_freeze(proxy_list,
+                                                         this->fd)) {
+                               radlog(L_ERR, "Fatal error freezing socket: %s",
+                                      fr_strerror());
+                               exit(1);
+                       }
+                       count = this->count; /* protected by mutex */
+                       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+               }
+#endif
+
+               /*
+                *      Requests are still using the socket.  Wait for
+                *      them to finish.
+                */
+               if (count != 0) {
+                       struct timeval when;
+                       listen_socket_t *sock = this->data;
+
+                       /*
+                        *      Try again to clean up the socket in 30
+                        *      seconds.
+                        */
+                       gettimeofday(&when, NULL);
+                       when.tv_sec += 30;
+                       
+                       if (!fr_event_insert(el,
+                                            (fr_event_callback_t) event_new_fd,
+                                            this, &when, &sock->ev)) {
+                               rad_panic("Failed to insert event");
+                       }
+                      
+                       return 1;
+               }
+
+               /*
+                *      No one is using this socket: we can delete it
+                *      immediately.
+                */
                this->status = RAD_LISTEN_STATUS_FINISH;
+       }
+       
+finish:
+       if (this->status == RAD_LISTEN_STATUS_FINISH) {
+               listen_socket_t *sock = this->data;
+
+               rad_assert(this->count == 0);
+               radlog(L_INFO, " ... closing socket %s", buffer);
+
+               /*
+                *      Remove it from the list of live FD's.  Note
+                *      that it MAY also have been removed above.  We
+                *      do it again here, to catch the case of sockets
+                *      closing on idle timeout, or max
+                *      lifetime... AFTER all requests have finished
+                *      using it.
+                */
+               FD_MUTEX_LOCK(&fd_mutex);
+               fr_event_fd_delete(el, 0, this->fd);
+               FD_MUTEX_UNLOCK(&fd_mutex);
                
+#ifdef WITH_PROXY
                /*
-                *      Close the fd AFTER fixing up the requests and
-                *      listeners, so that they don't send/recv on the
-                *      wrong socket (if someone manages to open
-                *      another one).
+                *      Remove it from the list of sockets to be used
+                *      when proxying.
                 */
-               close(this->fd);
-               this->fd = -1;
+               if (this->type == RAD_LISTEN_PROXY) {
+                       PTHREAD_MUTEX_LOCK(&proxy_mutex);
+                       if (!fr_packet_list_socket_remove(proxy_list,
+                                                         this->fd, NULL)) {
+                               radlog(L_ERR, "Fatal error removing socket: %s",
+                                      fr_strerror());
+                               exit(1);
+                       }
+                       if (sock->home) sock->home->num_connections--;
+                       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+               }
+#endif
+
+               /*
+                *      Remove any pending cleanups.
+                */
+               if (sock->ev) fr_event_delete(el, &sock->ev);
+
+               /*
+                *      And finally, close the socket.
+                */
+               listen_free(&this);
        }
+#endif /* WITH_TCP */
+
+       return 1;
 }
 
 static void handle_signal_self(int flag)
 {
        if ((flag & (RADIUS_SIGNAL_SELF_EXIT | RADIUS_SIGNAL_SELF_TERM)) != 0) {
                if ((flag & RADIUS_SIGNAL_SELF_EXIT) != 0) {
+                       radlog(L_INFO, "Received TERM signal");
                        fr_event_loop_exit(el, 1);
                } else {
                        fr_event_loop_exit(el, 2);
@@ -3223,13 +3777,14 @@ static void handle_signal_self(int flag)
                time_t when;
                static time_t last_hup = 0;
 
-               DEBUG("Received HUP signal.");
-
                when = time(NULL);
                if ((int) (when - last_hup) < 5) {
                        radlog(L_INFO, "Ignoring HUP (less than 5s since last one)");
                        return;
                }
+
+               radlog(L_INFO, "Received HUP signal.");
+
                last_hup = when;
 
                fr_event_loop_exit(el, 0x80);
@@ -3261,15 +3816,52 @@ static void handle_signal_self(int flag)
        }
 #endif
 
+#ifdef WITH_TCP
+#ifdef WITH_PROXY
+       /*
+        *      Add event handlers for idle timeouts && maximum lifetime.
+        */
        if ((flag & RADIUS_SIGNAL_SELF_NEW_FD) != 0) {
-               rad_listen_t *this;
-               
-               for (this = mainconfig.listen;
-                    this != NULL;
-                    this = this->next) {
-                       event_new_fd(this);
+               struct timeval when;
+               void *fun = NULL;
+
+               fr_event_now(el, &now);
+
+               PTHREAD_MUTEX_LOCK(&proxy_mutex);
+
+               while (proxy_listener_list) {
+                       rad_listen_t *this = proxy_listener_list;
+                       listen_socket_t *sock = this->data;
+
+                       proxy_listener_list = this->next;
+                       this->next = NULL;
+
+                       if (!sock->home) continue; /* skip UDP sockets */
+
+                       when = now;
+
+                       if (!sock->home->idle_timeout) {
+                               rad_assert(sock->home->lifetime != 0);
+
+                               when.tv_sec += sock->home->lifetime;
+                               fun = tcp_socket_lifetime;
+                       } else {
+                               rad_assert(sock->home->idle_timeout != 0);
+
+                               when.tv_sec += sock->home->idle_timeout;
+                               fun = tcp_socket_idle_timeout;
+                       }
+
+                       if (!fr_event_insert(el, fun, this, &when,
+                                            &(sock->ev))) {
+                               rad_panic("Failed to insert event");
+                       }
                }
+
+               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
        }
+#endif /* WITH_PROXY */
+#endif /* WITH_TCP */
 }
 
 #ifndef WITH_SELF_PIPE
@@ -3338,54 +3930,51 @@ static void event_socket_handler(fr_event_list_t *xel, UNUSED int fd,
 
        xel = xel;
 
-       if (listener->fd < 0) rad_panic("Socket was closed on us!");
+       if (
+#ifdef WITH_DETAIL
+           (listener->type != RAD_LISTEN_DETAIL) &&
+#endif
+           (listener->fd < 0)) {
+               char buffer[256];
+
+               listener->print(listener, buffer, sizeof(buffer));
+               radlog(L_ERR, "FATAL: Asked to read from closed socket: %s",
+                      buffer);
+       
+               rad_panic("Socket was closed on us!");
+               _exit(1);
+       }
        
        if (!listener->recv(listener, &fun, &request)) return;
 
-       if (!thread_pool_addrequest(request, fun)) {
-               request->child_state = REQUEST_DONE;
-       }
-}
+       rad_assert(fun != NULL);
+       rad_assert(request != NULL);
 
-typedef struct listen_detail_t {
-       fr_event_t      *ev;
-} listen_detail_t;
+       thread_pool_addrequest(request, fun);
+}
 
+#ifdef WITH_DETAIL
 /*
  *     This function is called periodically to see if this detail
  *     file is available for reading.
  */
 static void event_poll_detail(void *ctx)
 {
-       int rcode, delay;
-       RAD_REQUEST_FUNP fun;
-       REQUEST *request;
+       int delay;
        rad_listen_t *this = ctx;
        struct timeval when;
        listen_detail_t *detail = this->data;
 
        rad_assert(this->type == RAD_LISTEN_DETAIL);
 
-       /*
-        *      Try to read something.
-        *
-        *      FIXME: This does poll AND receive.
-        */
-       rcode = this->recv(this, &fun, &request);
-       if (rcode != 0) {
-               rad_assert(fun != NULL);
-               rad_assert(request != NULL);
-               
-               if (!thread_pool_addrequest(request, fun)) {
-                       request->child_state = REQUEST_DONE;
-               }
-       }
+       event_socket_handler(el, this->fd, this);
 
-       if (!fr_event_now(el, &now)) gettimeofday(&now, NULL);
+       fr_event_now(el, &now);
        when = now;
 
        /*
-        *      Backdoor API to get the delay until the next poll time.
+        *      Backdoor API to get the delay until the next poll
+        *      time.
         */
        delay = this->encode(this, NULL);
        tv_add(&when, delay);
@@ -3396,7 +3985,7 @@ static void event_poll_detail(void *ctx)
                exit(1);
        }
 }
-
+#endif
 
 static void event_status(struct timeval *wake)
 {
@@ -3413,7 +4002,7 @@ static void event_status(struct timeval *wake)
        }
 
        if (!wake) {
-               DEBUG("Ready to process requests.");
+               radlog(L_INFO, "Ready to process requests.");
 
        } else if ((wake->tv_sec != 0) ||
                   (wake->tv_usec >= 100000)) {
@@ -3446,7 +4035,7 @@ static void event_status(struct timeval *wake)
  */
 int radius_event_init(CONF_SECTION *cs, int spawn_flag)
 {
-       rad_listen_t *this, *head = NULL;
+       rad_listen_t *head = NULL;
 
        if (el) return 0;
 
@@ -3460,13 +4049,6 @@ int radius_event_init(CONF_SECTION *cs, int spawn_flag)
 
        request_num_counter = 0;
 
-       /*
-        *      Move all of the thread calls to this file?
-        *
-        *      It may be best for the mutexes to be in this file...
-        */
-       have_children = spawn_flag;
-
 #ifdef WITH_PROXY
        if (mainconfig.proxy_requests) {
                /*
@@ -3486,26 +4068,36 @@ int radius_event_init(CONF_SECTION *cs, int spawn_flag)
        }
 #endif
 
-       /*
-        *      Just before we spawn the child threads, force the log
-        *      subsystem to re-open the log file for every write.
-        */
-       if (spawn_flag) force_log_reopen();
-
 #ifdef HAVE_PTHREAD_H
 #ifndef __MINGW32__
        NO_SUCH_CHILD_PID = (pthread_t ) (0);
 #else
        NO_SUCH_CHILD_PID = pthread_self(); /* not a child thread */
 #endif
-       if (thread_pool_init(cs, spawn_flag) < 0) {
+       /*
+        *      Initialize the threads ONLY if we're spawning, AND
+        *      we're running normally.
+        */
+       if (spawn_flag && !check_config &&
+           (thread_pool_init(cs, &spawn_flag) < 0)) {
                exit(1);
        }
 #endif
 
+       /*
+        *      Move all of the thread calls to this file?
+        *
+        *      It may be best for the mutexes to be in this file...
+        */
+       have_children = spawn_flag;
+
        if (check_config) {
                DEBUG("%s: #### Skipping IP addresses and Ports ####",
                       mainconfig.name);
+               if (listen_init(cs, &head) < 0) {
+                       fflush(NULL);
+                       exit(1);
+               }
                return 1;
        }
 
@@ -3537,17 +4129,6 @@ int radius_event_init(CONF_SECTION *cs, int spawn_flag)
        }
 #endif /* WITH_SELF_PIPE */
 
-#ifdef WITH_PROXY
-       /*
-        *      Mark the proxy Fd's as unused.
-        */
-       {
-               int i;
-
-               for (i = 0; i < 32; i++) proxy_fds[i] = -1;
-       }
-#endif
-
        DEBUG("%s: #### Opening IP addresses and Ports ####",
               mainconfig.name);
 
@@ -3564,63 +4145,14 @@ int radius_event_init(CONF_SECTION *cs, int spawn_flag)
                _exit(1);
        }
        
+       mainconfig.listen = head;
+
        /*
         *      At this point, no one has any business *ever* going
         *      back to root uid.
         */
        fr_suid_down_permanent();
 
-       /*
-        *      Add all of the sockets to the event loop.
-        */
-       for (this = head;
-            this != NULL;
-            this = this->next) {
-               char buffer[256];
-
-               this->print(this, buffer, sizeof(buffer));
-
-               switch (this->type) {
-#ifdef WITH_DETAIL
-               case RAD_LISTEN_DETAIL:
-                       DEBUG("Listening on %s", buffer);
-
-                       /*
-                        *      Detail files are always known, and aren't
-                        *      put into the socket event loop.
-                        */
-                       this->status = RAD_LISTEN_STATUS_KNOWN;
-
-                       /*
-                        *      Set up the first poll interval.
-                        */
-                       event_poll_detail(this);
-                       break;
-#endif
-
-#ifdef WITH_PROXY
-               case RAD_LISTEN_PROXY:
-                       rad_assert(proxy_fds[this->fd & 0x1f] == -1);
-                       rad_assert(proxy_listeners[this->fd & 0x1f] == NULL);
-                       
-                       proxy_fds[this->fd & 0x1f] = this->fd;
-                       proxy_listeners[this->fd & 0x1f] = this;
-                       if (!fr_packet_list_socket_add(proxy_list,
-                                                        this->fd)) {
-                               rad_assert(0 == 1);
-                       }
-                       /* FALL-THROUGH */
-#endif
-
-               default:
-                       break;
-               }
-
-               event_new_fd(this);
-       }
-
-       mainconfig.listen = head;
-
        return 1;
 }
 
@@ -3664,9 +4196,7 @@ void radius_event_free(void)
         *      referenced from anywhere else.  Remove them first.
         */
        if (proxy_list) {
-               PTHREAD_MUTEX_LOCK(&proxy_mutex);
                fr_packet_list_walk(proxy_list, NULL, proxy_hash_cb);
-               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
                fr_packet_list_free(proxy_list);
                proxy_list = NULL;
        }