Fix corner case when not threaded.
[freeradius.git] / src / main / event.c
index 7846f8f..65fcb6b 100644 (file)
@@ -83,11 +83,6 @@ 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
 
 /*
@@ -156,6 +151,10 @@ 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
@@ -163,10 +162,45 @@ static void remove_from_request_hash(REQUEST *request)
 #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)
 {
-       int done = FALSE;
        RADIUS_PACKET **proxy_p;
        REQUEST *request;
 
@@ -181,16 +215,8 @@ static REQUEST *lookup_in_proxy_hash(RADIUS_PACKET *reply)
        request = fr_packet2myptr(REQUEST, proxy, proxy_p);
        request->num_proxied_responses++; /* needs to be protected by lock */
 
-       done = (request->num_proxied_requests == request->num_proxied_responses);
        PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
 
-
-       /*
-        *      Catch the most common case of everything working
-        *      correctly.
-        */
-       if (done) remove_from_proxy_hash(request);
-
        return request;
 }
 
@@ -246,42 +272,6 @@ static void remove_from_proxy_hash(REQUEST *request)
 }
 #endif /* WITH_PROXY */
 
-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_TCP
 static int remove_all_requests(void *ctx, void *data)
 {
@@ -376,6 +366,8 @@ retry:
        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) {
@@ -407,7 +399,7 @@ retry:
        if (!fr_packet_list_insert(proxy_list, &request->proxy)) {
                fr_packet_list_id_free(proxy_list, request->proxy);
                PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-               radlog(L_PROXY, "Failed to insert entry into proxy list");
+               radlog(L_PROXY, "Failed to insert entry into proxy list.");
                return 0;
        }
 
@@ -466,12 +458,11 @@ static void wait_for_proxy_id_to_expire(void *ctx)
 #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));
                }
 
@@ -489,6 +480,7 @@ static void wait_for_child_to_die(void *ctx)
        REQUEST *request = ctx;
 
        rad_assert(request->magic == REQUEST_MAGIC);
+       remove_from_request_hash(request);
 
        /*
         *      If it's still queued (waiting for a thread to pick it
@@ -500,14 +492,15 @@ static void wait_for_child_to_die(void *ctx)
             (pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) == 0))) {
 
                /*
-                *      Cap delay at five minutes.
+                *      Cap delay at max_request_time
                 */
-               if (request->delay < (USEC * 60 * 5)) {
+               if (request->delay < (USEC * request->root->max_request_time)) {
                        request->delay += (request->delay >> 1);
-                       radlog(L_INFO, "WARNING: Child is hung for request %d in component %s module %s.",
-                              request->number, request->component, request->module);
+                       radlog_request(L_INFO, 0, request, "WARNING: Child is hung in component %s module %s.",
+                              request->component, request->module);
                } else {
-                       RDEBUG2("Child is still stuck for request %d",
+                       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);
@@ -516,8 +509,7 @@ static void wait_for_child_to_die(void *ctx)
                return;
        }
 
-       RDEBUG2("Child is responsive for request %d", request->number);
-       remove_from_request_hash(request);
+       RDEBUG2("Child is finally responsive");
 
 #ifdef WITH_PROXY
        if (request->proxy) {
@@ -547,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);
@@ -617,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);
 
@@ -685,6 +677,12 @@ static void no_response_to_ping(void *ctx)
 }
 
 
+/*
+ *     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;
@@ -818,7 +816,7 @@ static void ping_home_server(void *ctx)
        rad_assert(request->proxy_listener == NULL);
 
        if (!insert_into_proxy_hash(request)) {
-               radlog(L_PROXY, "Failed inserting status check %d into proxy hash.  Discarding it.",
+               radlog(L_PROXY, "Failed to insert status check %d into proxy list.  Discarding it.",
                       request->number);
                ev_request_free(&request);
                return;
@@ -929,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
        /*
@@ -956,10 +952,10 @@ static int setup_post_proxy_fail(REQUEST *request)
        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
                /*
@@ -969,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;
                }
@@ -982,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);
@@ -1097,8 +1093,8 @@ static void no_response_to_proxied_request(void *ctx)
         *      well.
         */
        if (home->no_response_fail) {
-               radlog(L_ERR, "Rejecting request %d (proxy Id %d) due to lack of any response from home server %s port %d",
-                      request->number, request->proxy->id,
+               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)),
@@ -1146,33 +1142,53 @@ static void no_response_to_proxied_request(void *ctx)
        }
 #endif
 
-       if (home->state == HOME_STATE_IS_DEAD) {
-               rad_assert(home->ev != NULL); /* or it will never wake up */
+       /*
+        *      If it's not alive, don't try to make it a zombie.
+        */
+       if (home->state != HOME_STATE_ALIVE) {
+               /*
+                *      Don't check home->ev due to race conditions.
+                */
                return;
        }
 
        /*
-        *      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.
+        *      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->state == HOME_STATE_ALIVE) {
-               home->state = HOME_STATE_ZOMBIE;
-               home->zombie_period_start = now;        
-               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);
+       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
 
@@ -1184,6 +1200,7 @@ 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.
@@ -1191,6 +1208,7 @@ static void wait_a_bit(void *ctx)
        if (request->listener->status != RAD_LISTEN_STATUS_KNOWN) {
                goto stop_processing;
        }
+#endif
 
 #ifdef WITH_COA
        /*
@@ -1212,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;
 
@@ -1228,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);
@@ -1254,7 +1285,8 @@ static void wait_a_bit(void *ctx)
                }
 
        stop_processing:
-#if defined(HAVE_PTHREAD_H) || defined(WITH_PROXY)
+               request->master_state = REQUEST_STOP_PROCESSING;
+
                /*
                 *      A child thread MAY still be running on the
                 *      request.  Ask the thread to stop working on
@@ -1262,41 +1294,30 @@ static void wait_a_bit(void *ctx)
                 */
                if (have_children &&
                    (pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) == 0)) {
-                       request->master_state = REQUEST_STOP_PROCESSING;
-
-                       radlog(L_ERR, "WARNING: Unresponsive child for request %d, in module %s component %s",
+                       radlog(L_ERR, "WARNING: Unresponsive child for request %u, in component %s module %s",
                               request->number,
-                              request->module ? request->module : "<server core>",
-                              request->component ? request->component : "<server core>");
-                       
-                       request->delay = USEC / 4;
-                       tv_add(&request->when, request->delay);
-                       callback = wait_for_child_to_die;
-                       break;
+                              request->component ? request->component : "<server core>",
+                              request->module ? request->module : "<server core>");
+
                }
+                       
+               request->delay = USEC;
+               tv_add(&request->when, request->delay);
+               callback = wait_for_child_to_die;
+               break;
 #endif
 
                /*
-                *      Else no child thread is processing the
-                *      request.  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.
@@ -1313,7 +1334,6 @@ static void wait_a_bit(void *ctx)
                        return;
                }
 #endif
-               request_stats_final(request);
                cleanup_delay(request);
                return;
 
@@ -1322,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);
@@ -1349,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;
        }
@@ -1385,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;
@@ -1483,28 +1502,34 @@ static void retransmit_coa_request(void *ctx)
                 *      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 re-inserting CoA request into proxy hash.");
+                       radlog(L_PROXY,"Failed to insert retransmission of CoA request into proxy list.");
                        return;
                }
 
                /*
-                *      Now that we have a new Id, free the old one.
+                *      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);
 
-               request->num_proxied_requests = 0;
-               request->num_proxied_responses = 0;
+       } 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,
@@ -1530,9 +1555,9 @@ static int originated_coa_request(REQUEST *request)
        /*
         *      Check whether we want to originate one, or cancel one.
         */
-       vp = pairfind(request->config_items, PW_SEND_COA_REQUEST);
+       vp = pairfind(request->config_items, PW_SEND_COA_REQUEST, 0);
        if (!vp && request->coa) {
-               vp = pairfind(request->coa->proxy->vps, PW_SEND_COA_REQUEST);
+               vp = pairfind(request->coa->proxy->vps, PW_SEND_COA_REQUEST, 0);
        }
 
        if (vp) {
@@ -1551,18 +1576,18 @@ static int originated_coa_request(REQUEST *request)
         *      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) {
@@ -1603,7 +1628,7 @@ 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, IPPROTO_UDP);
@@ -1615,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:
@@ -1652,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;
@@ -1690,7 +1715,7 @@ static int originated_coa_request(REQUEST *request)
        coa->proxy->dst_port = coa->home_server->port;
 
        if (!insert_into_proxy_hash(coa)) {
-               radlog(L_PROXY, "Failed inserting CoA request into proxy hash.");
+               radlog(L_PROXY, "Failed to insert CoA request into proxy list.");
                goto fail;
        }
 
@@ -1739,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);
@@ -1771,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;
@@ -1808,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
@@ -1857,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;
        }
 
@@ -1868,15 +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);
+               DEBUG_PACKET(request, request->proxy_reply, 0);
+
                /*
-                *      FIXME: For now, we can only proxy RADIUS packets.
-                *
-                *      In order to proxy other packets, we need to
-                *      somehow cache the "decode" function.
+                *      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.
                 */
-               rcode = rad_decode(request->proxy_reply, request->proxy,
-                                  request->home_server->secret);
-               DEBUG_PACKET(request, request->proxy_reply, 0);
+               if ((rcode == 0) &&
+                   (request->num_proxied_requests <= request->num_proxied_responses)) {
+                       remove_from_proxy_hash(request);
+               }
+
        } else
 #endif
        if (request->packet->vps == NULL) {
@@ -1912,14 +1940,14 @@ static int request_pre_handler(REQUEST *request)
 
        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;
 }
@@ -1934,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)) {
-               radlog(L_PROXY, "Failed inserting request into proxy hash.");
+               radlog(L_PROXY, "Failed to insert request into proxy list.");
                return 0;
        }
 
@@ -1961,8 +1996,7 @@ 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)),
@@ -1975,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
@@ -2091,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) {
@@ -2196,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
@@ -2210,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;
@@ -2235,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);
        }
@@ -2248,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);
@@ -2262,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;
@@ -2318,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;
        }
        
@@ -2335,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
@@ -2367,7 +2399,7 @@ static void request_post_handler(REQUEST *request)
         */
        if ((request->packet->code == PW_AUTHENTICATION_REQUEST) &&
            (request->reply->code == 0) &&
-           ((vp = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL) &&
+           ((vp = pairfind(request->config_items, PW_AUTH_TYPE, 0)) != NULL) &&
            (vp->vp_integer == PW_AUTHTYPE_REJECT)) {
                request->reply->code = PW_AUTHENTICATION_REJECT;
        }
@@ -2402,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
 
        /*
@@ -2422,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
 
@@ -2438,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
@@ -2469,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);
@@ -2485,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;
@@ -2537,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);
        }
@@ -2546,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");
                }
@@ -2592,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;
 
@@ -2603,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);
        
@@ -2619,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);
@@ -2653,90 +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, OR the socket has been closed, look for
-                *      another connection to a home server.
+                *      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)) ||
-                   (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("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 */
-
-#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);
+               if ((request->home_server->state != HOME_STATE_IS_DEAD) &&
+                   (request->proxy_listener->status == RAD_LISTEN_STATUS_KNOWN)) {
+                       rad_retransmit_packet(request);
                        break;
                }
-#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++;
+               /*
+                *      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
 
@@ -2779,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);
@@ -2905,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);
@@ -2928,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);
@@ -2952,7 +3019,10 @@ int received_request(rad_listen_t *listener,
        /*
         *      We may want to quench the new request.
         */
-       if ((listener->type != RAD_LISTEN_DETAIL) &&
+       if (
+#ifdef WITH_DETAIL
+           (listener->type != RAD_LISTEN_DETAIL) &&
+#endif
            !can_handle_new_request(packet, client, root)) {
                return 0;
        }
@@ -2997,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;
        }
@@ -3059,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);
 
@@ -3073,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
@@ -3129,10 +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.
         */
-       if (request->proxy->code != PW_STATUS_SERVER) {
-               request->home_server->state = HOME_STATE_ALIVE;
-       }
+       request->home_server->state = HOME_STATE_ALIVE;
+       request->home_server->last_packet = now.tv_sec;
        
 #ifdef WITH_COA
        /*
@@ -3181,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
@@ -3206,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)),
@@ -3257,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;
@@ -3479,7 +3569,7 @@ int event_new_fd(rad_listen_t *this)
                }               
 #ifdef WITH_PROXY
                else {
-                       int count = this->count;
+                       int count;
 
                        /*
                         *      Duplicate code
@@ -3561,7 +3651,10 @@ int event_new_fd(rad_listen_t *this)
         */
        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
                /*
@@ -3837,45 +3930,44 @@ 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);
 
+       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);
 
        fr_event_now(el, &now);
        when = now;
@@ -3893,7 +3985,7 @@ static void event_poll_detail(void *ctx)
                exit(1);
        }
 }
-
+#endif
 
 static void event_status(struct timeval *wake)
 {
@@ -4002,6 +4094,10 @@ int radius_event_init(CONF_SECTION *cs, int 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;
        }