newvector should be a bool
[freeradius.git] / src / main / process.c
index b0e39f8..0150a1c 100644 (file)
@@ -71,7 +71,11 @@ static char const *action_codes[] = {
 };
 
 #ifdef DEBUG_STATE_MACHINE
-#define TRACE_STATE_MACHINE if (debug_flag) printf("(%u) ********\tSTATE %s action %s live M-%s C-%s\t********\n", request->number, __FUNCTION__, action_codes[action], master_state_names[request->master_state], child_state_names[request->child_state])
+#define TRACE_STATE_MACHINE if (debug_flag) do { struct timeval debug_tv; \
+                                                gettimeofday(&debug_tv, NULL);\
+                                                debug_tv.tv_sec -= fr_start_time;\
+                                                printf("(%u) %d.%06d ********\tSTATE %s action %s live M-%s C-%s\t********\n",\
+                                                       request->number, (int) debug_tv.tv_sec, (int) debug_tv.tv_usec,  __FUNCTION__, action_codes[action], master_state_names[request->master_state], child_state_names[request->child_state]); } while (0)
 
 static char const *master_state_names[REQUEST_MASTER_NUM_STATES] = {
        "?",
@@ -98,13 +102,22 @@ static char const *child_state_names[REQUEST_CHILD_NUM_STATES] = {
  *     Declare a state in the state machine.
  *
  */
-#define STATE_MACHINE_DECL(_x) static void _x(REQUEST *request, int action)
+#define STATE_MACHINE_DECL(_x) static void CC_HINT(nonnull) _x(REQUEST *request, int action)
 
 #define STATE_MACHINE_TIMER(_x) request->timer_action = _x; \
                fr_event_insert(el, request_timer, request, \
                                &when, &request->ev);
 
-
+/*
+ *     We need a different VERIFY_REQUEST macro in process.c
+ *     To avoid the race conditions with the master thread
+ *     checking the REQUEST whilst it's being worked on by
+ *     the child.
+ */
+#if defined(WITH_VERIFY_PTR) && defined(HAVE_PTHREAD_H)
+#  undef VERIFY_REQUEST
+#  define VERIFY_REQUEST(_x) if (pthread_equal(pthread_self(), _x->child_pid) != 0) verify_request(__FILE__, __LINE__, _x)
+#endif
 
 /**
  * @section request_timeline
@@ -128,7 +141,7 @@ static char const *child_state_names[REQUEST_CHILD_NUM_STATES] = {
  * -   Y: Reply is ready.  Rejects MAY be delayed here.  All other
  *        replies are sent immediately.
  *
- * -   J: Reject is sent "reject_delay" after the reply is ready.
+ * -   J: Reject is sent "response_delay" after the reply is ready.
  *
  * -   C: For Access-Requests, After "cleanup_delay", the request is
  *        deleted.  Accounting-Request packets go directly from Y to C.
@@ -190,8 +203,7 @@ static fr_packet_list_t *proxy_list = NULL;
 
 #ifdef HAVE_PTHREAD_H
 #ifdef WITH_PROXY
-static pthread_mutex_t proxy_mutex;
-static rad_listen_t *proxy_listener_list = NULL;
+static pthread_mutex_t proxy_mutex;
 static bool proxy_no_new_sockets = false;
 #endif
 
@@ -210,15 +222,72 @@ static pthread_t NO_SUCH_CHILD_PID;
 #define NO_CHILD_THREAD
 #endif
 
+#if  defined(HAVE_PTHREAD_H) && !defined (NDEBUG)
+static bool we_are_master(void)
+{
+       if (spawn_flag &&
+           (pthread_equal(pthread_self(), NO_SUCH_CHILD_PID) == 0)) {
+               return false;
+       }
+
+       return true;
+}
+#define ASSERT_MASTER  if (!we_are_master()) rad_panic("We are not master")
+
+#else
+#define we_are_master(_x) (1)
+#define ASSERT_MASTER
+#endif
+
+static int event_new_fd(rad_listen_t *this);
+
 /*
  *     We need mutexes around the event FD list *only* in certain
  *     cases.
  */
 #if defined (HAVE_PTHREAD_H) && (defined(WITH_PROXY) || defined(WITH_TCP))
+static rad_listen_t *new_listeners = NULL;
+
 static pthread_mutex_t fd_mutex;
 #define FD_MUTEX_LOCK if (spawn_flag) pthread_mutex_lock
 #define FD_MUTEX_UNLOCK if (spawn_flag) pthread_mutex_unlock
+
+void radius_update_listener(rad_listen_t *this)
+{
+       /*
+        *      Just do it ourselves.
+        */
+       if (we_are_master()) {
+               event_new_fd(this);
+               return;
+       }
+
+       FD_MUTEX_LOCK(&fd_mutex);
+
+       /*
+        *      If it's already in the list, don't add it again.
+        */
+       if (this->next) {
+               FD_MUTEX_UNLOCK(&fd_mutex);
+               return;
+       }
+
+       /*
+        *      Otherwise, add it to the list
+        */
+       this->next = new_listeners;
+       new_listeners = this;
+       FD_MUTEX_UNLOCK(&fd_mutex);
+       radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD);
+}
 #else
+void radius_update_listener(rad_listen_t *this)
+{
+       /*
+        *      No threads.  Just insert it.
+        */
+       event_new_fd(this);
+}
 /*
  *     This is easier than ifdef's throughout the code.
  */
@@ -226,13 +295,14 @@ static pthread_mutex_t    fd_mutex;
 #define FD_MUTEX_UNLOCK(_x)
 #endif
 
-static int request_num_counter = 0;
+static int request_num_counter = 1;
 #ifdef WITH_PROXY
 static int request_will_proxy(REQUEST *request);
 static int request_proxy(REQUEST *request, int retransmit);
 STATE_MACHINE_DECL(proxy_wait_for_reply);
+STATE_MACHINE_DECL(proxy_no_reply);
 STATE_MACHINE_DECL(proxy_running);
-static int process_proxy_reply(REQUEST *request);
+static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply);
 static void remove_from_proxy_hash(REQUEST *request);
 static void remove_from_proxy_hash_nl(REQUEST *request, bool yank);
 static int insert_into_proxy_hash(REQUEST *request);
@@ -242,31 +312,14 @@ static REQUEST *request_setup(rad_listen_t *listener, RADIUS_PACKET *packet,
                              RADCLIENT *client, RAD_REQUEST_FUNP fun);
 
 STATE_MACHINE_DECL(request_common);
-
-#if  defined(HAVE_PTHREAD_H) && !defined (NDEBUG)
-static bool we_are_master(void)
-{
-       if (spawn_flag &&
-           (pthread_equal(pthread_self(), NO_SUCH_CHILD_PID) == 0)) {
-               return false;
-       }
-
-       return true;
-}
-#define ASSERT_MASTER  if (!we_are_master()) rad_panic("We are not master")
-
-#else
-#define we_are_master(_x) (1)
-#define ASSERT_MASTER
-#endif
-
-STATE_MACHINE_DECL(request_reject_delay);
+STATE_MACHINE_DECL(request_response_delay);
 STATE_MACHINE_DECL(request_cleanup_delay);
 STATE_MACHINE_DECL(request_running);
 #ifdef WITH_COA
 static void request_coa_originate(REQUEST *request);
 STATE_MACHINE_DECL(coa_running);
 STATE_MACHINE_DECL(coa_wait_for_reply);
+STATE_MACHINE_DECL(coa_no_reply);
 static void request_coa_separate(REQUEST *coa);
 #endif
 
@@ -301,9 +354,9 @@ static void tv_add(struct timeval *tv, int usec_delay)
 }
 
 /*
- *     In daemon mode, AND this request has debug flags set.
+ *     Debug the packet if requested.
  */
-#define DEBUG_PACKET if (!debug_flag && request->options && request->radlog) debug_packet
+#define DEBUG_PACKET if (request->log.lvl && request->log.func) debug_packet
 
 static void debug_packet(REQUEST *request, RADIUS_PACKET *packet, int direction)
 {
@@ -312,11 +365,11 @@ static void debug_packet(REQUEST *request, RADIUS_PACKET *packet, int direction)
        char buffer[1024];
        char const *received, *from;
        fr_ipaddr_t const *ip;
-       int port;
+       uint16_t port;
 
        if (!packet) return;
 
-       rad_assert(request->radlog != NULL);
+       rad_assert(request->log.func != NULL);
 
        if (direction == 0) {
                received = "Received";
@@ -365,21 +418,68 @@ static void debug_packet(REQUEST *request, RADIUS_PACKET *packet, int direction)
  *
  ***********************************************************************/
 
+static struct timeval *request_response_window(REQUEST *request)
+{
+       VERIFY_REQUEST(request);
+
+       if (request->client) {
+               /*
+                *      The client hasn't set the response window.  Return
+                *      either the home server one, if set, or the global one.
+                */
+               if (!timerisset(&request->client->response_window)) {
+                       return &request->home_server->response_window;
+               }
+
+               if (timercmp(&request->client->response_window,
+                            &request->home_server->response_window, <)) {
+                       return &request->client->response_window;
+               }
+       }
+
+       rad_assert(request->home_server != NULL);
+       return &request->home_server->response_window;
+}
+
+/*
+ * Determine initial request processing delay.
+ */
+static int request_init_delay(REQUEST *request)
+{
+       struct timeval half_response_window;
+
+       VERIFY_REQUEST(request);
+
+       /* Allow client response window to lower initial delay */
+       if (timerisset(&request->client->response_window)) {
+               half_response_window.tv_sec = request->client->response_window.tv_sec >> 1;
+               half_response_window.tv_usec =
+                       ((request->client->response_window.tv_sec & 1) * USEC +
+                               request->client->response_window.tv_usec) >> 1;
+               if (timercmp(&half_response_window, &request->root->init_delay, <))
+                       return (int)half_response_window.tv_sec * USEC +
+                               (int)half_response_window.tv_usec;
+       }
+
+       return (int)request->root->init_delay.tv_sec * USEC +
+               (int)request->root->init_delay.tv_usec;
+}
+
 /*
  *     Callback for ALL timer events related to the request.
  */
 static void request_timer(void *ctx)
 {
-       REQUEST *request = ctx;
-       int action = request->timer_action;
+       REQUEST *request = talloc_get_type_abort(ctx, REQUEST);
+       int action;
+
+       action = request->timer_action;
 
        TRACE_STATE_MACHINE;
 
        request->process(request, action);
 }
 
-#define USEC (1000000)
-
 /*
  *     Only ever called from the master thread.
  */
@@ -390,6 +490,8 @@ STATE_MACHINE_DECL(request_done)
        char buffer[128];
 #endif
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
 #ifdef WITH_COA
@@ -439,6 +541,8 @@ STATE_MACHINE_DECL(request_done)
                if (request->reply->code != 0) {
                        request->listener->send(request->listener, request);
                        return;
+               } else {
+                       RDEBUG("No reply.  Ignoring retransmit");
                }
                break;
 
@@ -511,12 +615,11 @@ STATE_MACHINE_DECL(request_done)
                 *      packets from the home server.
                 */
        case FR_ACTION_PROXY_REPLY:
-               DEBUG2("Reply from home server %s port %d  - ID: %d arrived too late for request %u. Try increasing 'retry_delay' or 'max_request_time'",
+               RDEBUG2("Reply from home server %s port %d  - ID: %d arrived too late.  Try increasing 'retry_delay' or 'max_request_time'",
                       inet_ntop(request->proxy->src_ipaddr.af,
                                 &request->proxy->src_ipaddr.ipaddr,
                                 buffer, sizeof(buffer)),
-                      request->proxy->dst_port, request->proxy->id,
-                      request->number);
+                       request->proxy->dst_port, request->proxy->id);
                return;
 #endif
 
@@ -554,7 +657,7 @@ STATE_MACHINE_DECL(request_done)
                        when.tv_sec += request->home_server->coa_mrd;
                } else
 #endif
-               when.tv_sec += request->home_server->response_window;
+                       timeradd(&when, request_response_window(request), &when);
 
                /*
                 *      We haven't received all responses, AND there's still
@@ -628,7 +731,7 @@ STATE_MACHINE_DECL(request_done)
 
        if (request->ev) fr_event_delete(el, &request->ev);
 
-       request_free(&request);
+       talloc_free(request);
 }
 
 
@@ -636,6 +739,8 @@ static void request_cleanup_delay_init(REQUEST *request, struct timeval const *p
 {
        struct timeval now, when;
 
+       VERIFY_REQUEST(request);
+
        if (request->packet->code == PW_CODE_ACCOUNTING_REQUEST) goto done;
 
        if (!request->root->cleanup_delay) goto done;
@@ -684,6 +789,8 @@ static void request_process_timer(REQUEST *request)
        int action = FR_ACTION_TIMER;
 #endif
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
        ASSERT_MASTER;
 
@@ -710,8 +817,8 @@ static void request_process_timer(REQUEST *request)
                 */
                if (request->listener->status != RAD_LISTEN_STATUS_KNOWN) {
                        if ((request->master_state == REQUEST_ACTIVE) &&
-                           (request->child_state < REQUEST_REJECT_DELAY)) {
-                               WDEBUG("Socket was closed while processing request %u: Stopping it.", request->number);
+                           (request->child_state < REQUEST_RESPONSE_DELAY)) {
+                               WARN("Socket was closed while processing request %u: Stopping it.", request->number);
                                request->master_state = REQUEST_STOP_PROCESSING;
                        }
                }
@@ -748,7 +855,7 @@ static void request_process_timer(REQUEST *request)
 #ifdef WITH_PROXY
                case REQUEST_PROXIED:
 #endif
-               case REQUEST_REJECT_DELAY:
+               case REQUEST_RESPONSE_DELAY:
                case REQUEST_CLEANUP_DELAY:
                case REQUEST_DONE:
                done:
@@ -765,9 +872,6 @@ static void request_process_timer(REQUEST *request)
        switch (request->child_state) {
        case REQUEST_QUEUED:
        case REQUEST_RUNNING:
-#ifdef WITH_PROXY
-       case REQUEST_PROXIED:
-#endif
                when = request->packet->timestamp;
                when.tv_sec += request->root->max_request_time;
 
@@ -791,47 +895,76 @@ static void request_process_timer(REQUEST *request)
 #endif
                        request->master_state = REQUEST_STOP_PROCESSING;
                }
+               goto delay;     /* sleep some more */
 
 #ifdef WITH_PROXY
+       case REQUEST_PROXIED:
+               when = request->packet->timestamp;
+               when.tv_sec += request->root->max_request_time;
+
+               if (timercmp(&now, &when, >=)) {
+                       RWDEBUG("No response to proxied request in 'max_request_time'.  Stopping it.");
+                       request->master_state = REQUEST_STOP_PROCESSING;
+                       request_done(request, FR_ACTION_DONE);
+                       break;
+               }
+
+               rad_assert(request->proxy != NULL);
+#ifdef WITH_COA
                /*
-                *      We should wait for the proxy reply.
+                *      Ugh.
                 */
-               if (request->child_state == REQUEST_PROXIED) {
+               if (request->packet->code != request->proxy->code) {
                        if (request->proxy_reply) {
-                               request->process = proxy_running;
+                               request->process = coa_running;
                        } else {
-                               request->process = proxy_wait_for_reply;
+                               request->process = coa_wait_for_reply;
                        }
-               }
+               } else
 #endif
 
+               if (request->proxy_reply) {
+                       request->process = proxy_running;
+               } else {
+                       request->process = proxy_wait_for_reply;
+               }
+
+               when = request->proxy->timestamp;
+               tv_add(&when, request->delay);
+
+               if (timercmp(&now, &when, >=)) {
+                       request->process(request, FR_ACTION_TIMER);
+                       return;
+               }
+
                /*
-                *      If the request has been told to die, we wait.
-                *      Otherwise, we wait for the child thread to
-                *      finish it's work.
+                *      Leave the initial delay alone.
                 */
-               goto delay;
+               STATE_MACHINE_TIMER(FR_ACTION_TIMER);
+               return;
+#endif /* WITH_PROXY */
 
-       case REQUEST_REJECT_DELAY:
-               rad_assert(request->root->reject_delay > 0);
+       case REQUEST_RESPONSE_DELAY:
+               rad_assert(request->response_delay > 0);
 #ifdef WITH_COA
                rad_assert(!request->proxy || (request->packet->code == request->proxy->code));
 #endif
 
-               request->process = request_reject_delay;
+               request->process = request_response_delay;
 
                when = request->reply->timestamp;
-               when.tv_sec += request->root->reject_delay;
+
+               tv_add(&when, request->response_delay * USEC);
 
                if (timercmp(&when, &now, >)) {
 #ifdef DEBUG_STATE_MACHINE
-                       if (debug_flag) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_reject_delay");
+                       if (debug_flag) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_response_delay");
 #endif
                        STATE_MACHINE_TIMER(FR_ACTION_TIMER);
                        return;
                } /* else it's time to send the reject */
 
-               RDEBUG2("Sending delayed reject");
+               RDEBUG2("Sending delayed response");
                DEBUG_PACKET(request, request->reply, 1);
                request->listener->send(request->listener, request);
                request->child_state = REQUEST_CLEANUP_DELAY;
@@ -871,6 +1004,8 @@ static void request_queue_or_run(UNUSED REQUEST *request,
        int action = FR_ACTION_TIMER;
 #endif
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        /*
@@ -897,7 +1032,8 @@ static void request_queue_or_run(UNUSED REQUEST *request,
                /*
                 *      (re) set the initial delay.
                 */
-               request->delay = USEC / 3;
+               request->delay = request_init_delay(request);
+               if (request->delay > USEC) request->delay = USEC;
                gettimeofday(&when, NULL);
                tv_add(&when, request->delay);
                request->delay += request->delay >> 1;
@@ -940,6 +1076,8 @@ STATE_MACHINE_DECL(request_common)
        char buffer[128];
 #endif
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
        ASSERT_MASTER;
 
@@ -958,15 +1096,18 @@ STATE_MACHINE_DECL(request_common)
                 *      We're still waiting for a proxy reply.
                 */
                if (request->child_state == REQUEST_PROXIED) {
+                       request->process = proxy_wait_for_reply;
                        proxy_wait_for_reply(request, action);
                        return;
                }
 #endif
 
-               ERROR("(%u) Discarding duplicate request from "
-                      "client %s port %d - ID: %u due to unfinished request",
-                      request->number, request->client->shortname,
-                      request->packet->src_port,request->packet->id);
+               ERROR("(%u) Ignoring duplicate packet from "
+                     "client %s port %d - ID: %u due to unfinished request "
+                     "in component %s module %s",
+                     request->number, request->client->shortname,
+                     request->packet->src_port,request->packet->id,
+                     request->component, request->module);
                break;
 
        case FR_ACTION_CONFLICTING:
@@ -983,12 +1124,11 @@ STATE_MACHINE_DECL(request_common)
 
 #ifdef WITH_PROXY
        case FR_ACTION_PROXY_REPLY:
-               DEBUG2("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(request->proxy->src_ipaddr.af,
-                                &request->proxy->src_ipaddr.ipaddr,
+               RDEBUG2("Reply from home server %s port %d  - ID: %d arrived too late.  Try increasing 'retry_delay' or 'max_request_time'",
+                      inet_ntop(request->proxy->dst_ipaddr.af,
+                                &request->proxy->dst_ipaddr.ipaddr,
                                 buffer, sizeof(buffer)),
-                      request->proxy->dst_port, request->proxy->id,
-                      request->number);
+                       request->proxy->dst_port, request->proxy->id);
                return;
 #endif
 
@@ -1002,6 +1142,8 @@ STATE_MACHINE_DECL(request_cleanup_delay)
 {
        struct timeval when;
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
        ASSERT_MASTER;
 
@@ -1010,7 +1152,7 @@ STATE_MACHINE_DECL(request_cleanup_delay)
                if (request->reply->code != 0) {
                        request->listener->send(request->listener, request);
                } else {
-                       RDEBUG("No reply.  Ignoring retransmit.");
+                       RDEBUG("No reply.  Ignoring retransmit");
                }
 
                /*
@@ -1023,10 +1165,13 @@ STATE_MACHINE_DECL(request_cleanup_delay)
                STATE_MACHINE_TIMER(FR_ACTION_TIMER);
                return;
 
+       case FR_ACTION_CONFLICTING:
+               request_done(request, FR_ACTION_DONE);
+               break;
+
 #ifdef WITH_PROXY
        case FR_ACTION_PROXY_REPLY:
 #endif
-       case FR_ACTION_CONFLICTING:
        case FR_ACTION_TIMER:
                request_common(request, action);
                return;
@@ -1037,15 +1182,17 @@ STATE_MACHINE_DECL(request_cleanup_delay)
        }
 }
 
-STATE_MACHINE_DECL(request_reject_delay)
+STATE_MACHINE_DECL(request_response_delay)
 {
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
        ASSERT_MASTER;
 
        switch (action) {
        case FR_ACTION_DUP:
                ERROR("(%u) Discarding duplicate request from "
-                      "client %s port %d - ID: %u due to delayed reject",
+                      "client %s port %d - ID: %u due to delayed response",
                       request->number, request->client->shortname,
                       request->packet->src_port,request->packet->id);
                return;
@@ -1065,12 +1212,14 @@ STATE_MACHINE_DECL(request_reject_delay)
 }
 
 
-static int request_pre_handler(REQUEST *request, UNUSED int action)
+static int CC_HINT(nonnull) request_pre_handler(REQUEST *request, UNUSED int action)
 {
-       TRACE_STATE_MACHINE;
-
        int rcode;
 
+       VERIFY_REQUEST(request);
+
+       TRACE_STATE_MACHINE;
+
        if (request->master_state == REQUEST_STOP_PROCESSING) return 0;
 
        /*
@@ -1084,33 +1233,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action)
                return 1;
        }
 
-#ifdef WITH_PROXY
-       /*
-        *      Put the decoded packet into it's proper place.
-        */
-       if (request->proxy_reply != NULL) {
-               /*
-                *      There may be a proxy reply, but it may be too late.
-                */
-               if (!request->proxy_listener) return 0;
-
-               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) {
+       if (!request->packet->vps) { /* FIXME: check for correct state */
                rcode = request->listener->decode(request->listener, request);
 
 #ifdef WITH_UNLANG
@@ -1119,8 +1242,8 @@ static int request_pre_handler(REQUEST *request, UNUSED int action)
                         *      Ignore parse errors.
                         */
                        if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition)) {
-                               request->options = 2;
-                               request->radlog = vradlog_request;
+                               request->log.lvl = L_DBG_LVL_2;
+                               request->log.func = vradlog_request;
                        }
                }
 #endif
@@ -1140,12 +1263,6 @@ static int request_pre_handler(REQUEST *request, UNUSED int action)
                request->username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
        }
 
-#ifdef WITH_PROXY
-       if (action == FR_ACTION_PROXY_REPLY) {
-               return process_proxy_reply(request);
-       }
-#endif
-
        return 1;
 }
 
@@ -1153,11 +1270,16 @@ STATE_MACHINE_DECL(request_finish)
 {
        VALUE_PAIR *vp;
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        (void) action;  /* -Wunused */
 
-       if (request->master_state == REQUEST_STOP_PROCESSING) return;
+       if (request->master_state == REQUEST_STOP_PROCESSING) {
+               NO_CHILD_THREAD;
+               return;
+       }
 
        /*
         *      Don't send replies if there are none to send.
@@ -1179,6 +1301,7 @@ STATE_MACHINE_DECL(request_finish)
                         */
                }
 #else
+               NO_CHILD_THREAD;
                return;
 #endif
        }
@@ -1198,16 +1321,16 @@ STATE_MACHINE_DECL(request_finish)
        /*
         *      Catch Auth-Type := Reject BEFORE proxying the packet.
         */
-       else if (request->packet->code == PW_CODE_AUTHENTICATION_REQUEST) {
+       else if (request->packet->code == PW_CODE_ACCESS_REQUEST) {
                if (request->reply->code == 0) {
                        vp = pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY);
 
-                       if (!vp || (vp->vp_integer != PW_CODE_AUTHENTICATION_REJECT)) {
+                       if (!vp || (vp->vp_integer != PW_CODE_ACCESS_REJECT)) {
                                RDEBUG2("There was no response configured: "
                                        "rejecting request");
                        }
 
-                       request->reply->code = PW_CODE_AUTHENTICATION_REJECT;
+                       request->reply->code = PW_CODE_ACCESS_REJECT;
                }
        }
 
@@ -1219,7 +1342,7 @@ STATE_MACHINE_DECL(request_finish)
        if (vp) pairadd(&request->reply->vps, vp);
 
        switch (request->reply->code) {
-       case PW_CODE_AUTHENTICATION_ACK:
+       case PW_CODE_ACCESS_ACCEPT:
                rad_postauth(request);
                break;
        case PW_CODE_ACCESS_CHALLENGE:
@@ -1240,7 +1363,7 @@ STATE_MACHINE_DECL(request_finish)
         *      We do this separately so ACK and challenge can change the code
         *      to reject if a module returns reject.
         */
-       if (request->reply->code == PW_CODE_AUTHENTICATION_REJECT) {
+       if (request->reply->code == PW_CODE_ACCESS_REJECT) {
                pairdelete(&request->config_items, PW_POST_AUTH_TYPE, 0, TAG_ANY);
                vp = pairmake_config("Post-Auth-Type", "Reject", T_OP_SET);
                if (vp) rad_postauth(request);
@@ -1267,16 +1390,44 @@ STATE_MACHINE_DECL(request_finish)
        gettimeofday(&request->reply->timestamp, NULL);
 
        /*
+        *      Ignore all "do not respond" packets.
+        */
+       if (!request->reply->code) {
+               RDEBUG("Not sending reply");
+               goto done;
+       }
+
+       /*
+        *      See if we need to delay an Access-Reject packet.
+        */
+       if ((request->reply->code == PW_CODE_ACCESS_REJECT) &&
+           (request->root->reject_delay > 0)) {
+               request->response_delay = request->root->reject_delay;
+
+#ifdef WITH_PROXY
+               /*
+                *      If we timed out a proxy packet, don't delay
+                *      the reject any more.
+                */
+               if (request->proxy && !request->proxy_reply) {
+                       request->response_delay = 0;
+               }
+#endif
+
+       }
+
+       /*
         *      Send the reply.
         */
-       if ((request->reply->code != PW_CODE_AUTHENTICATION_REJECT) ||
-           (request->root->reject_delay == 0)) {
+       if (!request->response_delay) {
                DEBUG_PACKET(request, request->reply, 1);
                request->listener->send(request->listener,
                                        request);
+
+       done:
                pairfree(&request->reply->vps);
 
-               RDEBUG2("Finished request %u.", request->number);
+               RDEBUG2("Finished request");
 #ifdef WITH_ACCOUNTING
                if (request->packet->code == PW_CODE_ACCOUNTING_REQUEST) {
                        NO_CHILD_THREAD;
@@ -1292,16 +1443,17 @@ STATE_MACHINE_DECL(request_finish)
                        request->child_state = REQUEST_CLEANUP_DELAY;
                }
        } else {
-               RDEBUG2("Delaying reject of request %u for %d seconds",
-                       request->number,
-                       request->root->reject_delay);
+               RDEBUG2("Delaying response for %d seconds",
+                       request->response_delay);
                NO_CHILD_THREAD;
-               request->child_state = REQUEST_REJECT_DELAY;
+               request->child_state = REQUEST_RESPONSE_DELAY;
        }
 }
 
 STATE_MACHINE_DECL(request_running)
 {
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        switch (action) {
@@ -1324,7 +1476,8 @@ STATE_MACHINE_DECL(request_running)
        case FR_ACTION_PROXY_REPLY:
                request->child_state = REQUEST_RUNNING;
                request->process = proxy_running;
-               /* FALL-THROUGH */
+               request->process(request, FR_ACTION_RUN);
+               break;
 #endif
 
        case FR_ACTION_RUN:
@@ -1392,12 +1545,14 @@ STATE_MACHINE_DECL(request_running)
 int request_receive(rad_listen_t *listener, RADIUS_PACKET *packet,
                    RADCLIENT *client, RAD_REQUEST_FUNP fun)
 {
-       int count;
+       uint32_t count;
        RADIUS_PACKET **packet_p;
        REQUEST *request = NULL;
        struct timeval now;
        listen_socket_t *sock = NULL;
 
+       VERIFY_PACKET(packet);
+
        /*
         *      Set the last packet received.
         */
@@ -1430,60 +1585,74 @@ int request_receive(rad_listen_t *listener, RADIUS_PACKET *packet,
                    (memcmp(request->packet->vector, packet->vector,
                            sizeof(packet->vector)) == 0)) {
 
+                       /*
+                        *      If the request is running, it'
+                        */
+                       if (request->child_state != REQUEST_DONE) {
+                               request->process(request, FR_ACTION_DUP);
+
 #ifdef WITH_STATS
-                       switch (packet->code) {
-                       case PW_CODE_AUTHENTICATION_REQUEST:
-                               FR_STATS_INC(auth, total_dup_requests);
-                               break;
+                               switch (packet->code) {
+                               case PW_CODE_ACCESS_REQUEST:
+                                       FR_STATS_INC(auth, total_dup_requests);
+                                       break;
 
 #ifdef WITH_ACCOUNTING
-                       case PW_CODE_ACCOUNTING_REQUEST:
-                               FR_STATS_INC(acct, total_dup_requests);
-                               break;
+                               case PW_CODE_ACCOUNTING_REQUEST:
+                                       FR_STATS_INC(acct, total_dup_requests);
+                                       break;
 #endif
 #ifdef WITH_COA
-                       case PW_CODE_COA_REQUEST:
-                               FR_STATS_INC(coa, total_dup_requests);
-                               break;
+                               case PW_CODE_COA_REQUEST:
+                                       FR_STATS_INC(coa, total_dup_requests);
+                                       break;
 
-                       case PW_CODE_DISCONNECT_REQUEST:
-                               FR_STATS_INC(dsc, total_dup_requests);
-                               break;
+                               case PW_CODE_DISCONNECT_REQUEST:
+                                       FR_STATS_INC(dsc, total_dup_requests);
+                                       break;
 #endif
 
-                       default:
-                         break;
-                       }
+                               default:
+                                       break;
+                               }
 #endif /* WITH_STATS */
+                               return 0; /* duplicate of live request */
+                       }
+#ifdef HAVE_PTHREAD_H
+                       /*
+                        *      There should no longer be a child
+                        *      thread associated with this request.
+                        */
+                       rad_assert(pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) != 0);
+#endif
 
-                       request->process(request, FR_ACTION_DUP);
-                       return 0;
-               }
+                       /*
+                        *      Clean up the old request, and allow
+                        *      the new one to continue.
+                        */
+                       request_done(request, FR_ACTION_DONE);
+                       request = NULL;
 
-               /*
-                *      Say we're ignoring the old one, and continue
-                *      to process the new one.
-                */
-               request->process(request, FR_ACTION_CONFLICTING);
-               request = NULL;
+               } else {
+                       /*
+                        *      Say we're ignoring the old one, and continue
+                        *      to process the new one.
+                        */
+                       request->process(request, FR_ACTION_CONFLICTING);
+                       request = NULL;
+               }
        }
 
        /*
         *      Quench maximum number of outstanding requests.
         */
-       if (mainconfig.max_requests &&
-           ((count = fr_packet_list_num_elements(pl)) > mainconfig.max_requests)) {
-               static time_t last_complained = 0;
-
-               if (last_complained == now.tv_sec) return 0;
-
-               last_complained = now.tv_sec;
-
-               ERROR("Dropping request (%d is too many): from client %s port %d - ID: %d", count,
-                      client->shortname,
-                      packet->src_port, packet->id);
-               WARN("Please check the configuration file.\n"
-                    "\tThe value for 'max_requests' is probably set too low.\n");
+       if (main_config.max_requests &&
+           ((count = fr_packet_list_num_elements(pl)) > main_config.max_requests)) {
+               RATE_LIMIT(ERROR("Dropping request (%d is too many): from client %s port %d - ID: %d", count,
+                                client->shortname,
+                                packet->src_port, packet->id);
+                          WARN("Please check the configuration file.\n"
+                               "\tThe value for 'max_requests' is probably set too low.\n"));
 
                exec_trigger(NULL, NULL, "server.max_requests", true);
                return 0;
@@ -1494,11 +1663,9 @@ skip_dup:
         *      Rate-limit the incoming packets
         */
        if (sock && sock->max_rate) {
-               int pps;
-
-               pps = rad_pps(&sock->rate_pps_old, &sock->rate_pps_now,
-                             &sock->rate_time, &now);
+               uint32_t pps;
 
+               pps = rad_pps(&sock->rate_pps_old, &sock->rate_pps_now, &sock->rate_time, &now);
                if (pps > sock->max_rate) {
                        DEBUG("Dropping request due to rate limiting");
                        return 0;
@@ -1531,8 +1698,13 @@ skip_dup:
                request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
 
                fun(request);
-               request->listener->send(request->listener, request);
-               request_free(&request);
+
+               if (request->reply->code != 0) {
+                       request->listener->send(request->listener, request);
+               } else {
+                       RDEBUG("Not sending reply");
+               }
+               talloc_free(request);
                return 1;
        }
 
@@ -1555,10 +1727,10 @@ static REQUEST *request_setup(rad_listen_t *listener, RADIUS_PACKET *packet,
         *      Create and initialize the new request.
         */
        request = request_alloc(NULL);
-       request->reply = rad_alloc(request, 0);
+       request->reply = rad_alloc(request, false);
        if (!request->reply) {
                ERROR("No memory");
-               request_free(&request);
+               talloc_free(request);
                return NULL;
        }
 
@@ -1580,7 +1752,7 @@ static REQUEST *request_setup(rad_listen_t *listener, RADIUS_PACKET *packet,
 
 #ifdef WITH_STATS
        request->listener->stats.last_packet = request->packet->timestamp.tv_sec;
-       if (packet->code == PW_CODE_AUTHENTICATION_REQUEST) {
+       if (packet->code == PW_CODE_ACCESS_REQUEST) {
                request->client->auth.last_packet = request->packet->timestamp.tv_sec;
                radius_auth_stats.last_packet = request->packet->timestamp.tv_sec;
 #ifdef WITH_ACCOUNTING
@@ -1607,7 +1779,7 @@ static REQUEST *request_setup(rad_listen_t *listener, RADIUS_PACKET *packet,
                request->server = NULL;
        }
 
-       request->root = &mainconfig;
+       request->root = &main_config;
 #ifdef WITH_TCP
        request->listener->count++;
 #endif
@@ -1651,12 +1823,14 @@ static REQUEST *request_setup(rad_listen_t *listener, RADIUS_PACKET *packet,
  */
 static void tcp_socket_timer(void *ctx)
 {
-       rad_listen_t *listener = ctx;
+       rad_listen_t *listener = talloc_get_type_abort(ctx, rad_listen_t);
        listen_socket_t *sock = listener->data;
        struct timeval end, now;
        char buffer[256];
        fr_socket_limit_t *limit;
 
+       ASSERT_MASTER;
+
        fr_event_now(el, &now);
 
        if (listener->status != RAD_LISTEN_STATUS_KNOWN) return;
@@ -1763,7 +1937,7 @@ static void add_jitter(struct timeval *when)
  */
 static int eol_proxy_listener(void *ctx, void *data)
 {
-       rad_listen_t *this = ctx;
+       rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t);
        RADIUS_PACKET **proxy_p = data;
        REQUEST *request;
 
@@ -1792,7 +1966,7 @@ static int eol_proxy_listener(void *ctx, void *data)
 
 static int eol_listener(void *ctx, void *data)
 {
-       rad_listen_t *this = ctx;
+       rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t);
        RADIUS_PACKET **packet_p = data;
        REQUEST *request;
 
@@ -1817,6 +1991,8 @@ static int eol_listener(void *ctx, void *data)
  */
 static void remove_from_proxy_hash_nl(REQUEST *request, bool yank)
 {
+       VERIFY_REQUEST(request);
+
        if (!request->in_proxy_hash) return;
 
        fr_packet_list_id_free(proxy_list, request->proxy, yank);
@@ -1860,6 +2036,8 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank)
 
 static void remove_from_proxy_hash(REQUEST *request)
 {
+       VERIFY_REQUEST(request);
+
        /*
         *      Check this without grabbing the mutex because it's a
         *      lot faster that way.
@@ -1880,7 +2058,7 @@ static void remove_from_proxy_hash(REQUEST *request)
 
        remove_from_proxy_hash_nl(request, true);
 
-       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
 }
 
 static int insert_into_proxy_hash(REQUEST *request)
@@ -1889,6 +2067,8 @@ static int insert_into_proxy_hash(REQUEST *request)
        int rcode, tries;
        void *proxy_listener;
 
+       VERIFY_REQUEST(request);
+
        rad_assert(request->proxy != NULL);
        rad_assert(request->home_server != NULL);
        rad_assert(proxy_list != NULL);
@@ -1901,6 +2081,7 @@ static int insert_into_proxy_hash(REQUEST *request)
 
        for (tries = 0; tries < 2; tries++) {
                rad_listen_t *this;
+               listen_socket_t *sock;
 
                RDEBUG3("proxy: Trying to allocate ID (%d/2)", tries);
                rcode = fr_packet_list_id_alloc(proxy_list,
@@ -1926,16 +2107,34 @@ static int insert_into_proxy_hash(REQUEST *request)
                request->proxy->src_port = 0; /* Use any new socket */
                proxy_listener = this;
 
+               sock = this->data;
+               if (!fr_packet_list_socket_add(proxy_list, this->fd,
+                                              sock->proto,
+                                              &sock->other_ipaddr, sock->other_port,
+                                              this)) {
+
+#ifdef HAVE_PTHREAD_H
+                       proxy_no_new_sockets = true;
+#endif
+                       PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+
+                       /*
+                        *      This is bad.  However, the
+                        *      packet list now supports 256
+                        *      open sockets, which should
+                        *      minimize this problem.
+                        */
+                       ERROR("Failed adding proxy socket: %s",
+                             fr_strerror());
+                       goto fail;
+               }
+
                /*
-                *      Add it to the event loop (and to the packet list)
-                *      before we try to grab another Id.
+                *      Add it to the event loop.  Ensure that we have
+                *      only one mutex locked at a time.
                 */
                PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-               if (!event_new_fd(this)) {
-                       RDEBUG3("proxy: Failed inserting new socket into event loop");
-                       listen_free(&this);
-                       goto fail;
-               }
+               radius_update_listener(this);
                PTHREAD_MUTEX_LOCK(&proxy_mutex);
        }
 
@@ -1967,7 +2166,7 @@ static int insert_into_proxy_hash(REQUEST *request)
 
        PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
 
-       RDEBUG3(" proxy: allocating destination %s port %d - Id %d",
+       RDEBUG3("proxy: allocating destination %s port %d - Id %d",
               inet_ntop(request->proxy->dst_ipaddr.af,
                         &request->proxy->dst_ipaddr.ipaddr, buf, sizeof(buf)),
               request->proxy->dst_port,
@@ -1976,12 +2175,19 @@ static int insert_into_proxy_hash(REQUEST *request)
        return 1;
 }
 
-static int process_proxy_reply(REQUEST *request)
+static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply)
 {
        int rcode;
        int post_proxy_type = 0;
        VALUE_PAIR *vp;
 
+       VERIFY_REQUEST(request);
+
+       /*
+        *      There may be a proxy reply, but it may be too late.
+        */
+       if (!request->proxy_listener) return 0;
+
        /*
         *      Delete any reply we had accumulated until now.
         */
@@ -1997,9 +2203,9 @@ static int process_proxy_reply(REQUEST *request)
         *      If we have a proxy_reply, and it was a reject, setup
         *      post-proxy-type Reject
         */
-       if (!vp && request->proxy_reply &&
-           request->proxy_reply->code == PW_CODE_AUTHENTICATION_REJECT) {
-               DICT_VALUE      *dval;
+       if (!vp && reply &&
+           reply->code == PW_CODE_ACCESS_REJECT) {
+               DICT_VALUE      *dval;
 
                dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Reject");
                if (dval) {
@@ -2013,18 +2219,40 @@ static int process_proxy_reply(REQUEST *request)
        if (vp) {
                post_proxy_type = vp->vp_integer;
 
-               RDEBUG2("  Found Post-Proxy-Type %s",
-                       dict_valnamebyattr(PW_POST_PROXY_TYPE, 0,
-                                          post_proxy_type));
+               RDEBUG2("Found Post-Proxy-Type %s", dict_valnamebyattr(PW_POST_PROXY_TYPE, 0, post_proxy_type));
+       }
+
+       if (reply) {
+               VERIFY_PACKET(reply);
+               /*
+                *      Decode the packet.
+                */
+               rcode = request->proxy_listener->decode(request->proxy_listener, request);
+               DEBUG_PACKET(request, 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 {
+               remove_from_proxy_hash(request);
        }
 
        if (request->home_pool && request->home_pool->virtual_server) {
                char const *old_server = request->server;
 
                request->server = request->home_pool->virtual_server;
-               RDEBUG2(" server %s {", request->server);
+               RDEBUG2("server %s {", request->server);
+               RINDENT();
                rcode = process_post_proxy(post_proxy_type, request);
-               RDEBUG2(" }");
+               REXDENT();
+               RDEBUG2("}");
                request->server = old_server;
        } else {
                rcode = process_post_proxy(post_proxy_type, request);
@@ -2043,25 +2271,15 @@ static int process_proxy_reply(REQUEST *request)
         *      There may NOT be a proxy reply, as we may be
         *      running Post-Proxy-Type = Fail.
         */
-       if (request->proxy_reply) {
+       if (reply) {
+               pairadd(&request->reply->vps, paircopy(request->reply, reply->vps));
+
                /*
                 *      Delete the Proxy-State Attributes from
                 *      the reply.  These include Proxy-State
                 *      attributes from us and remote server.
                 */
-               pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE, 0, TAG_ANY);
-
-               /*
-                *      Add the attributes left in the proxy
-                *      reply to the reply list.
-                */
-               pairfilter(request->reply, &request->reply->vps,
-                         &request->proxy_reply->vps, 0, 0, TAG_ANY);
-
-               /*
-                *      Free proxy request pairs.
-                */
-               pairfree(&request->proxy->vps);
+               pairdelete(&request->reply->vps, PW_PROXY_STATE, 0, TAG_ANY);
        }
 
        switch (rcode) {
@@ -2084,6 +2302,8 @@ int request_proxy_reply(RADIUS_PACKET *packet)
        struct timeval now;
        char buffer[128];
 
+       VERIFY_PACKET(packet);
+
        PTHREAD_MUTEX_LOCK(&proxy_mutex);
        proxy_p = fr_packet_list_find_byreply(proxy_list, packet);
 
@@ -2156,7 +2376,7 @@ int request_proxy_reply(RADIUS_PACKET *packet)
         *      Call the state machine to do something useful with the
         *      request.
         */
-       request->proxy_reply = packet;
+       request->proxy_reply = talloc_steal(request, packet);
        packet->timestamp = now;
        request->priority = RAD_LISTEN_PROXY;
 
@@ -2166,13 +2386,14 @@ int request_proxy_reply(RADIUS_PACKET *packet)
         */
        if (request->home_server->state == HOME_STATE_UNKNOWN) {
                request->home_server->state = HOME_STATE_ALIVE;
+               request->home_server->response_timeouts = 0;
        }
 
 #ifdef WITH_STATS
        request->home_server->stats.last_packet = packet->timestamp.tv_sec;
        request->proxy_listener->stats.last_packet = packet->timestamp.tv_sec;
 
-       if (request->proxy->code == PW_CODE_AUTHENTICATION_REQUEST) {
+       if (request->proxy->code == PW_CODE_ACCESS_REQUEST) {
                proxy_auth_stats.last_packet = packet->timestamp.tv_sec;
 #ifdef WITH_ACCOUNTING
        } else if (request->proxy->code == PW_CODE_ACCOUNTING_REQUEST) {
@@ -2207,7 +2428,9 @@ static int setup_post_proxy_fail(REQUEST *request)
        DICT_VALUE const *dval = NULL;
        VALUE_PAIR *vp;
 
-       if (request->proxy->code == PW_CODE_AUTHENTICATION_REQUEST) {
+       VERIFY_REQUEST(request);
+
+       if (request->proxy->code == PW_CODE_ACCESS_REQUEST) {
                dval = dict_valbyname(PW_POST_PROXY_TYPE, 0,
                                      "Fail-Authentication");
 
@@ -2222,7 +2445,7 @@ static int setup_post_proxy_fail(REQUEST *request)
                dval = dict_valbyname(PW_POST_PROXY_TYPE, 0, "Fail-Disconnect");
 #endif
        } else {
-               WDEBUG("Unknown packet type in Post-Proxy-Type Fail: ignoring");
+               WARN("Unknown packet type in Post-Proxy-Type Fail: ignoring");
                return 0;
        }
 
@@ -2241,8 +2464,10 @@ static int setup_post_proxy_fail(REQUEST *request)
        return 1;
 }
 
-STATE_MACHINE_DECL(proxy_running)
+STATE_MACHINE_DECL(proxy_no_reply)
 {
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        switch (action) {
@@ -2254,7 +2479,10 @@ STATE_MACHINE_DECL(proxy_running)
                break;
 
        case FR_ACTION_RUN:
-               request_running(request, action);
+               if (process_proxy_reply(request, NULL)) {
+                       request_finish(request, action);
+               }
+               request_done(request, FR_ACTION_DONE);
                break;
 
        default:
@@ -2263,9 +2491,9 @@ STATE_MACHINE_DECL(proxy_running)
        }
 }
 
-STATE_MACHINE_DECL(request_virtual_server)
+STATE_MACHINE_DECL(proxy_running)
 {
-       char const *old;
+       VERIFY_REQUEST(request);
 
        TRACE_STATE_MACHINE;
 
@@ -2278,10 +2506,12 @@ STATE_MACHINE_DECL(request_virtual_server)
                break;
 
        case FR_ACTION_RUN:
-               old = request->server;
-               request->server = request->home_server->server;
-               request_running(request, action);
-               request->server = old;
+               if (process_proxy_reply(request, request->proxy_reply)) {
+                       request->handle(request);
+                       request_finish(request, action);
+               } else {
+                       request_done(request, FR_ACTION_DONE);
+               }
                break;
 
        default:
@@ -2290,7 +2520,6 @@ STATE_MACHINE_DECL(request_virtual_server)
        }
 }
 
-
 static int request_will_proxy(REQUEST *request)
 {
        int rcode, pre_proxy_type = 0;
@@ -2300,6 +2529,8 @@ static int request_will_proxy(REQUEST *request)
        REALM *realm = NULL;
        home_pool_t *pool = NULL;
 
+       VERIFY_REQUEST(request);
+
        if (!request->root->proxy_requests) return 0;
        if (request->packet->dst_port == 0) return 0;
        if (request->packet->code == PW_CODE_STATUS_SERVER) return 0;
@@ -2324,7 +2555,7 @@ static int request_will_proxy(REQUEST *request)
                /*
                 *      Figure out which pool to use.
                 */
-               if (request->packet->code == PW_CODE_AUTHENTICATION_REQUEST) {
+               if (request->packet->code == PW_CODE_ACCESS_REQUEST) {
                        pool = realm->auth_pool;
 
 #ifdef WITH_ACCOUNTING
@@ -2349,7 +2580,7 @@ static int request_will_proxy(REQUEST *request)
                if (!vp) return 0;
 
                switch (request->packet->code) {
-               case PW_CODE_AUTHENTICATION_REQUEST:
+               case PW_CODE_ACCESS_REQUEST:
                        pool_type = HOME_TYPE_AUTH;
                        break;
 
@@ -2429,7 +2660,7 @@ static int request_will_proxy(REQUEST *request)
                vp = pairfind(request->proxy->vps, PW_USER_NAME, 0, TAG_ANY);
                if (!vp) {
                        vp_cursor_t cursor;
-                       vp = radius_paircreate(request, NULL,
+                       vp = radius_paircreate(NULL, NULL,
                                               PW_USER_NAME, 0);
                        rad_assert(vp != NULL); /* handled by above function */
                        /* Insert at the START of the list */
@@ -2451,10 +2682,10 @@ static int request_will_proxy(REQUEST *request)
         *      since we can't use the request authenticator
         *      anymore - we changed it.
         */
-       if ((request->packet->code == PW_CODE_AUTHENTICATION_REQUEST) &&
+       if ((request->packet->code == PW_CODE_ACCESS_REQUEST) &&
            pairfind(request->proxy->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) &&
            pairfind(request->proxy->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL) {
-               vp = radius_paircreate(request, &request->proxy->vps, PW_CHAP_CHALLENGE, 0);
+               vp = radius_paircreate(request->proxy, &request->proxy->vps, PW_CHAP_CHALLENGE, 0);
                pairmemcpy(vp, request->packet->vector, sizeof(request->packet->vector));
        }
 
@@ -2462,7 +2693,7 @@ static int request_will_proxy(REQUEST *request)
         *      The RFC's say we have to do this, but FreeRADIUS
         *      doesn't need it.
         */
-       vp = radius_paircreate(request, &request->proxy->vps, PW_PROXY_STATE, 0);
+       vp = radius_paircreate(request->proxy, &request->proxy->vps, PW_PROXY_STATE, 0);
        pairsprintf(vp, "%u", request->packet->id);
 
        /*
@@ -2479,7 +2710,7 @@ static int request_will_proxy(REQUEST *request)
                DICT_VALUE const *dval = dict_valbyattr(vp->da->attr, vp->da->vendor, vp->vp_integer);
                /* Must be a validation issue */
                rad_assert(dval);
-               RDEBUG2("  Found Pre-Proxy-Type %s", dval->name);
+               RDEBUG2("Found Pre-Proxy-Type %s", dval->name);
                pre_proxy_type = vp->vp_integer;
        }
 
@@ -2489,10 +2720,14 @@ static int request_will_proxy(REQUEST *request)
                char const *old_server = request->server;
 
                request->server = request->home_pool->virtual_server;
-               RDEBUG2(" server %s {", request->server);
+
+               RDEBUG2("server %s {", request->server);
+               RINDENT();
                rcode = process_pre_proxy(pre_proxy_type, request);
-               RDEBUG2(" }");
-                       request->server = old_server;
+               REXDENT();
+               RDEBUG2("}");
+
+               request->server = old_server;
        } else {
                rcode = process_pre_proxy(pre_proxy_type, request);
        }
@@ -2525,6 +2760,8 @@ static int request_proxy(REQUEST *request, int retransmit)
 {
        char buffer[128];
 
+       VERIFY_REQUEST(request);
+
        rad_assert(request->parent == NULL);
        rad_assert(request->home_server != NULL);
 
@@ -2538,51 +2775,102 @@ static int request_proxy(REQUEST *request, int retransmit)
 #endif
 
        /*
-        *      The request may be sent to a virtual server.  If we're
-        *      in a child thread, just process it here. If we're the
-        *      master, push it back onto the queue for later
-        *      processing.
+        *      The request may need sending to a virtual server.
+        *      This code is more than a little screwed up.  The rest
+        *      of the state machine doesn't handle parent / child
+        *      relationships well.  i.e. if the child request takes
+        *      too long, the core will mark the *parent* as "stop
+        *      processing".  And the child will continue without
+        *      knowing anything...
+        *
+        *      So, we have some horrible hacks to get around that.
         */
        if (request->home_server->server) {
+               REQUEST *fake;
+
+               if (request->packet->dst_port == 0) {
+                       WARN("Cannot proxy an internal request");
+                       return 0;
+               }
+
                DEBUG("Proxying to virtual server %s",
                      request->home_server->server);
 
-               if (!we_are_master()) {
-                       request_virtual_server(request, FR_ACTION_RUN);
-                       NO_CHILD_THREAD;
-                       return 1;
-               }
+               /*
+                *      Packets to virtual serrers don't get
+                *      retransmissions sent to them.  And the virtual
+                *      server is run ONLY if we have no child
+                *      threads, or we're running in a child thread.
+                */
+               rad_assert(retransmit == 0);
+               rad_assert(!spawn_flag || !we_are_master());
 
-               request_queue_or_run(request, request_virtual_server);
-               return 1;
+               fake = request_alloc_fake(request);
+
+               fake->packet->vps = paircopy(fake->packet, request->packet->vps);
+               talloc_free(request->proxy);
+
+               fake->server = request->home_server->server;
+               fake->handle = request->handle;
+               fake->process = NULL; /* should never be run for anything */
+
+               /*
+                *      Run the virtual server.
+                */
+               request_running(fake, FR_ACTION_RUN);
+
+               request->proxy = talloc_steal(request, fake->packet);
+               fake->packet = NULL;
+               request->proxy_reply = talloc_steal(request, fake->reply);
+               fake->reply = NULL;
+
+               talloc_free(fake);
+
+               /*
+                *      Just do the work here, rather than trying to
+                *      run the "decode proxy reply" stuff...
+                */
+               process_proxy_reply(request, request->proxy_reply);
+
+               request->handle(request); /* to do more post-proxy stuff */
+
+               return -1;      /* so we call request_finish */
        }
 
        /*
         *      We're actually sending a proxied packet.  Do that now.
         */
        if (!request->in_proxy_hash && !insert_into_proxy_hash(request)) {
-               EDEBUG("Failed to insert request into the proxy list.");
+               ERROR("Failed to insert request into the proxy list");
                return -1;
        }
 
        rad_assert(request->proxy->id >= 0);
 
+       if (debug_flag) {
+               struct timeval *response_window;
+
+               response_window = request_response_window(request);
+
 #ifdef WITH_TLS
-       if (request->home_server->tls) {
-               RDEBUG2("Proxying request to home server %s port %d (TLS)",
-                       inet_ntop(request->proxy->dst_ipaddr.af,
-                                 &request->proxy->dst_ipaddr.ipaddr,
-                                 buffer, sizeof(buffer)),
-                       request->proxy->dst_port);
-       } else
+               if (request->home_server->tls) {
+                       RDEBUG2("Proxying request to home server %s port %d (TLS) timeout %d.%06d",
+                               inet_ntop(request->proxy->dst_ipaddr.af,
+                                         &request->proxy->dst_ipaddr.ipaddr,
+                                         buffer, sizeof(buffer)),
+                               request->proxy->dst_port,
+                               (int) response_window->tv_sec, (int) response_window->tv_usec);
+               } else
 #endif
-       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);
+                       RDEBUG2("Proxying request to home server %s port %d timeout %d.%06d",
+                               inet_ntop(request->proxy->dst_ipaddr.af,
+                                         &request->proxy->dst_ipaddr.ipaddr,
+                                         buffer, sizeof(buffer)),
+                               request->proxy->dst_port,
+                               (int) response_window->tv_sec, (int) response_window->tv_usec);
 
-       DEBUG_PACKET(request, request->proxy, 1);
+               DEBUG_PACKET(request, request->proxy, 1);
+       }
 
        gettimeofday(&request->proxy_retransmit, NULL);
        if (!retransmit) {
@@ -2605,6 +2893,8 @@ static int request_proxy_anew(REQUEST *request)
 {
        home_server_t *home;
 
+       VERIFY_REQUEST(request);
+
        /*
         *      Delete the request from the proxy list.
         *
@@ -2634,7 +2924,7 @@ static int request_proxy_anew(REQUEST *request)
        home_server_update_request(home, request);
 
        if (!insert_into_proxy_hash(request)) {
-               RPROXY("Failed to insert retransmission into the proxy list.");
+               RPROXY("Failed to insert retransmission into the proxy list");
                goto post_proxy_fail;
        }
 
@@ -2653,7 +2943,7 @@ static int request_proxy_anew(REQUEST *request)
                VALUE_PAIR *vp;
 
                vp = pairfind(request->proxy->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY);
-               if (!vp) vp = radius_paircreate(request,
+               if (!vp) vp = radius_paircreate(request->proxy,
                                                &request->proxy->vps,
                                                PW_ACCT_DELAY_TIME, 0);
                if (vp) {
@@ -2675,6 +2965,8 @@ STATE_MACHINE_DECL(request_ping)
        home_server_t *home = request->home_server;
        char buffer[128];
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
        ASSERT_MASTER;
 
@@ -2725,6 +3017,7 @@ STATE_MACHINE_DECL(request_ping)
                 *      pings.
                 */
                home->state = HOME_STATE_ALIVE;
+               home->response_timeouts = 0;
                exec_trigger(request, home->cs, "home_server.alive", false);
                home->currently_outstanding = 0;
                home->num_sent_pings = 0;
@@ -2756,7 +3049,7 @@ STATE_MACHINE_DECL(request_ping)
  */
 static void ping_home_server(void *ctx)
 {
-       home_server_t *home = ctx;
+       home_server_t *home = talloc_get_type_abort(ctx, home_server_t);
        REQUEST *request;
        VALUE_PAIR *vp;
        struct timeval when, now;
@@ -2787,7 +3080,7 @@ static void ping_home_server(void *ctx)
        request->number = request_num_counter++;
        NO_CHILD_THREAD;
 
-       request->proxy = rad_alloc(request, 1);
+       request->proxy = rad_alloc(request, true);
        rad_assert(request->proxy != NULL);
 
        if (home->ping_check == HOME_PING_CHECK_STATUS_SERVER) {
@@ -2797,7 +3090,7 @@ static void ping_home_server(void *ctx)
                         "Message-Authenticator", "0x00", T_OP_SET);
 
        } else if (home->type == HOME_TYPE_AUTH) {
-               request->proxy->code = PW_CODE_AUTHENTICATION_REQUEST;
+               request->proxy->code = PW_CODE_ACCESS_REQUEST;
 
                pairmake(request->proxy, &request->proxy->vps,
                         "User-Name", home->ping_user_name, T_OP_SET);
@@ -2858,7 +3151,7 @@ static void ping_home_server(void *ctx)
                rad_assert(!request->in_request_hash);
                rad_assert(!request->in_proxy_hash);
                rad_assert(request->ev == NULL);
-               request_free(&request);
+               talloc_free(request);
                return;
        }
 
@@ -2905,7 +3198,7 @@ static void home_trigger(home_server_t *home, char const *trigger)
        exec_trigger(&my_request, home->cs, trigger, false);
 }
 
-static void mark_home_server_zombie(home_server_t *home, struct timeval *now)
+static void mark_home_server_zombie(home_server_t *home, struct timeval *now, struct timeval *response_window)
 {
        time_t start;
        char buffer[128];
@@ -2917,7 +3210,7 @@ static void mark_home_server_zombie(home_server_t *home, struct timeval *now)
 
 #ifdef WITH_TCP
        if (home->proto == IPPROTO_TCP) {
-               WDEBUG("Not marking TCP server %s zombie", home->name);
+               WARN("Not marking TCP server %s zombie", home->name);
                return;
        }
 #endif
@@ -2952,10 +3245,10 @@ static void mark_home_server_zombie(home_server_t *home, struct timeval *now)
        home->num_sent_pings = 0;
        home->num_received_pings = 0;
 
-       PROXY( "Marking home server %s port %d as zombie (it has not responded in %d seconds).",
+       PROXY( "Marking home server %s port %d as zombie (it has not responded in %d.%06d seconds).",
               inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr,
                         buffer, sizeof(buffer)),
-              home->port, home->response_window);
+              home->port, (int) response_window->tv_sec, (int) response_window->tv_usec);
 
        ping_home_server(home);
 }
@@ -2963,7 +3256,7 @@ static void mark_home_server_zombie(home_server_t *home, struct timeval *now)
 
 void revive_home_server(void *ctx)
 {
-       home_server_t *home = ctx;
+       home_server_t *home = talloc_get_type_abort(ctx, home_server_t);
        char buffer[128];
 
 #ifdef WITH_TCP
@@ -2971,6 +3264,7 @@ void revive_home_server(void *ctx)
 #endif
 
        home->state = HOME_STATE_ALIVE;
+       home->response_timeouts = 0;
        home_trigger(home, "home_server.alive");
        home->currently_outstanding = 0;
        gettimeofday(&home->revive_time, NULL);
@@ -2993,7 +3287,7 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when)
 
 #ifdef WITH_TCP
        if (home->proto == IPPROTO_TCP) {
-               WDEBUG("Not marking TCP server dead");
+               WARN("Not marking TCP server dead");
                return;
        }
 #endif
@@ -3036,9 +3330,12 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when)
 STATE_MACHINE_DECL(proxy_wait_for_reply)
 {
        struct timeval now, when;
+       struct timeval *response_window = NULL;
        home_server_t *home = request->home_server;
        char buffer[128];
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        rad_assert(request->packet->code != PW_CODE_STATUS_SERVER);
@@ -3053,8 +3350,17 @@ STATE_MACHINE_DECL(proxy_wait_for_reply)
 
        switch (action) {
        case FR_ACTION_DUP:
+               /*
+                *      We have a reply, ignore the retransmit.
+                */
                if (request->proxy_reply) return;
 
+               /*
+                *      The request was proxied to a virtual server.
+                *      Ignore the retransmit.
+                */
+               if (request->home_server->server) return;
+
                if ((home->state == HOME_STATE_IS_DEAD) ||
                    !request->proxy_listener ||
                    (request->proxy_listener->status != RAD_LISTEN_STATUS_KNOWN)) {
@@ -3121,6 +3427,8 @@ STATE_MACHINE_DECL(proxy_wait_for_reply)
                break;
 
        case FR_ACTION_TIMER:
+               response_window = request_response_window(request);
+
 #ifdef WITH_TCP
                if (!request->proxy_listener ||
                    (request->proxy_listener->status != RAD_LISTEN_STATUS_KNOWN)) {
@@ -3146,14 +3454,18 @@ STATE_MACHINE_DECL(proxy_wait_for_reply)
                         *      responding to other (better looking) packets.
                         */
                        when = request->proxy->timestamp;
-                       when.tv_sec += home->response_window;
+                       timeradd(&when, response_window, &when);
 
                        /*
                         *      Not at the response window.  Set the timer for
                         *      that.
                         */
                        if (timercmp(&when, &now, >)) {
-                               RDEBUG("Expecting proxy response no later than %d seconds from now", home->response_window);
+                               struct timeval diff;
+                               timersub(&when, &now, &diff);
+
+                               RDEBUG("Expecting proxy response no later than %d.%06d seconds from now",
+                                      (int) diff.tv_sec, (int) diff.tv_usec);
                                STATE_MACHINE_TIMER(FR_ACTION_TIMER);
                                return;
                        }
@@ -3179,7 +3491,9 @@ STATE_MACHINE_DECL(proxy_wait_for_reply)
                    && (home->proto != IPPROTO_TCP)
 #endif
                        ) {
-                       mark_home_server_zombie(home, &now);
+                       home->response_timeouts++;
+                       if (home->response_timeouts >= home->max_response_timeouts)
+                               mark_home_server_zombie(home, &now, response_window);
                }
 
                FR_STATS_TYPE_INC(home->stats.total_timeouts);
@@ -3200,19 +3514,19 @@ STATE_MACHINE_DECL(proxy_wait_for_reply)
                 *      may have failed over to another home server.
                 *      But that one may be dead, too.
                 */
-               RERROR("Failing request - proxy ID %u, due to lack of any response from home server %s port %d",
-                      request->proxy->id,
+               RERROR("Failing proxied request, due to lack of any response from home server %s port %d",
                               inet_ntop(request->proxy->dst_ipaddr.af,
                                         &request->proxy->dst_ipaddr.ipaddr,
                                         buffer, sizeof(buffer)),
                               request->proxy->dst_port);
 
-               if (!setup_post_proxy_fail(request)) {
+               if (setup_post_proxy_fail(request)) {
+                       request_queue_or_run(request, proxy_no_reply);
+               } else {
                        gettimeofday(&request->reply->timestamp, NULL);
                        request_cleanup_delay_init(request, NULL);
-                       return;
                }
-               /* FALL-THROUGH */
+               break;
 
                /*
                 *      Duplicate proxy replies have been quenched by
@@ -3256,7 +3570,8 @@ static void request_coa_originate(REQUEST *request)
        fr_ipaddr_t ipaddr;
        char buffer[256];
 
-       rad_assert(request != NULL);
+       VERIFY_REQUEST(request);
+
        rad_assert(request->coa != NULL);
        rad_assert(request->proxy == NULL);
        rad_assert(!request->in_proxy_hash);
@@ -3273,7 +3588,7 @@ static void request_coa_originate(REQUEST *request)
        if (vp) {
                if (vp->vp_integer == 0) {
                fail:
-                       request_free(&request->coa);
+                       TALLOC_FREE(request->coa);
                        return;
                }
        }
@@ -3331,7 +3646,7 @@ static void request_coa_originate(REQUEST *request)
                home_server_update_request(coa->home_server, coa);
 
        } else if (!coa->home_server) {
-               int port = PW_COA_UDP_PORT;
+               uint16_t port = PW_COA_UDP_PORT;
 
                vp = pairfind(coa->proxy->vps, PW_PACKET_DST_PORT, 0, TAG_ANY);
                if (vp) port = vp->vp_integer;
@@ -3386,7 +3701,7 @@ static void request_coa_originate(REQUEST *request)
                DICT_VALUE const *dval = dict_valbyattr(vp->da->attr, vp->da->vendor, vp->vp_integer);
                /* Must be a validation issue */
                rad_assert(dval);
-               RDEBUG2("  Found Pre-Proxy-Type %s", dval->name);
+               RDEBUG2("Found Pre-Proxy-Type %s", dval->name);
                pre_proxy_type = vp->vp_integer;
        }
 
@@ -3394,9 +3709,11 @@ static void request_coa_originate(REQUEST *request)
                char const *old_server = coa->server;
 
                coa->server = coa->home_pool->virtual_server;
-               RDEBUG2(" server %s {", coa->server);
+               RDEBUG2("server %s {", coa->server);
+               RINDENT();
                rcode = process_pre_proxy(pre_proxy_type, coa);
-               RDEBUG2(" }");
+               REXDENT();
+               RDEBUG2("}");
                coa->server = old_server;
        } else {
                rcode = process_pre_proxy(pre_proxy_type, coa);
@@ -3422,7 +3739,7 @@ static void request_coa_originate(REQUEST *request)
        coa->proxy->dst_port = coa->home_server->port;
 
        if (!insert_into_proxy_hash(coa)) {
-               radlog_request(L_PROXY, 0, coa, "Failed to insert CoA request into proxy list.");
+               radlog_request(L_PROXY, 0, coa, "Failed to insert CoA request into proxy list");
                goto fail;
        }
 
@@ -3460,9 +3777,11 @@ static void request_coa_originate(REQUEST *request)
 
 static void coa_timer(REQUEST *request)
 {
-       int delay, frac;
+       uint32_t delay, frac;
        struct timeval now, when, mrd;
 
+       VERIFY_REQUEST(request);
+
        rad_assert(request->parent == NULL);
 
        if (request->proxy_reply) return request_process_timer(request);
@@ -3513,12 +3832,12 @@ static void coa_timer(REQUEST *request)
                                         &request->proxy->dst_ipaddr.ipaddr,
                                         buffer, sizeof(buffer)),
                               request->proxy->dst_port);
-               if (!setup_post_proxy_fail(request)) {
+
+               if (setup_post_proxy_fail(request)) {
+                       request_queue_or_run(request, coa_no_reply);
+               } else {
                        request_done(request, FR_ACTION_DONE);
-                       return;
                }
-
-               request_queue_or_run(request, coa_running);
                return;
        }
 
@@ -3584,6 +3903,8 @@ STATE_MACHINE_DECL(coa_wait_for_reply)
 {
        rad_assert(request->parent == NULL);
 
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        switch (action) {
@@ -3596,19 +3917,7 @@ STATE_MACHINE_DECL(coa_wait_for_reply)
 
        case FR_ACTION_PROXY_REPLY:
                rad_assert(request->parent == NULL);
-#ifdef HAVE_PTHREAD_H
-               /*
-                *      Catch the case of a proxy reply when called
-                *      from the main worker thread.
-                */
-               if (we_are_master()) {
-                       request_queue_or_run(request, coa_running);
-                       return;
-               }
-               /* FALL-THROUGH */
-#endif
-       case FR_ACTION_RUN:
-               request_running(request, action);
+               request_queue_or_run(request, coa_running);
                break;
 
        default:
@@ -3622,6 +3931,9 @@ static void request_coa_separate(REQUEST *request)
 #ifdef DEBUG_STATE_MACHINE
        int action = FR_ACTION_TIMER;
 #endif
+
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        rad_assert(request->parent != NULL);
@@ -3642,8 +3954,45 @@ static void request_coa_separate(REQUEST *request)
        request->process(request, FR_ACTION_TIMER);
 }
 
+STATE_MACHINE_DECL(coa_no_reply)
+{
+       char buffer[128];
+
+       VERIFY_REQUEST(request);
+
+       TRACE_STATE_MACHINE;
+
+       switch (action) {
+       case FR_ACTION_TIMER:
+               request_common(request, action);
+               break;
+
+       case FR_ACTION_PROXY_REPLY: /* too late! */
+               RDEBUG2("Reply from CoA server %s port %d  - ID: %d arrived too late.",
+                       inet_ntop(request->proxy->src_ipaddr.af,
+                                 &request->proxy->src_ipaddr.ipaddr,
+                                 buffer, sizeof(buffer)),
+                       request->proxy->dst_port, request->proxy->id);
+               break;
+
+       case FR_ACTION_RUN:
+               /*
+                *      FIXME: do recv_coa Fail
+                */
+               (void) process_proxy_reply(request, NULL);
+               request_done(request, FR_ACTION_DONE);
+               break;
+
+       default:
+               RDEBUG3("%s: Ignoring action %s", __FUNCTION__, action_codes[action]);
+               break;
+       }
+}
+
 STATE_MACHINE_DECL(coa_running)
 {
+       VERIFY_REQUEST(request);
+
        TRACE_STATE_MACHINE;
 
        switch (action) {
@@ -3656,7 +4005,12 @@ STATE_MACHINE_DECL(coa_running)
                break;
 
        case FR_ACTION_RUN:
-               request_running(request, FR_ACTION_PROXY_REPLY);
+               if (process_proxy_reply(request, request->proxy_reply)) {
+                       request->handle(request);
+                       request_finish(request, action);
+               } else {
+                       request_done(request, FR_ACTION_DONE);
+               }
                break;
 
        default:
@@ -3679,7 +4033,7 @@ STATE_MACHINE_DECL(coa_running)
  ***********************************************************************/
 static void event_socket_handler(UNUSED fr_event_list_t *xel, UNUSED int fd, void *ctx)
 {
-       rad_listen_t *listener = ctx;
+       rad_listen_t *listener = talloc_get_type_abort(ctx, rad_listen_t);
 
        rad_assert(xel == el);
 
@@ -3702,6 +4056,8 @@ static void event_socket_handler(UNUSED fr_event_list_t *xel, UNUSED int fd, voi
 }
 
 #ifdef WITH_DETAIL
+#ifdef WITH_DETAIL_THREAD
+#else
 /*
  *     This function is called periodically to see if this detail
  *     file is available for reading.
@@ -3709,7 +4065,7 @@ static void event_socket_handler(UNUSED fr_event_list_t *xel, UNUSED int fd, voi
 static void event_poll_detail(void *ctx)
 {
        int delay;
-       rad_listen_t *this = ctx;
+       rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t);
        struct timeval when, now;
        listen_detail_t *detail = this->data;
 
@@ -3736,7 +4092,8 @@ static void event_poll_detail(void *ctx)
                fr_exit(1);
        }
 }
-#endif
+#endif /* WITH_DETAIL_THREAD */
+#endif /* WITH_DETAIL */
 
 static void event_status(struct timeval *wake)
 {
@@ -3746,14 +4103,14 @@ static void event_status(struct timeval *wake)
 
        if (debug_flag == 0) {
                if (just_started) {
-                       INFO("Ready to process requests.");
+                       INFO("Ready to process requests");
                        just_started = false;
                }
                return;
        }
 
        if (!wake) {
-               INFO("Ready to process requests.");
+               INFO("Ready to process requests");
 
        } else if ((wake->tv_sec != 0) ||
                   (wake->tv_usec >= 100000)) {
@@ -3784,7 +4141,7 @@ static void event_status(struct timeval *wake)
 #ifdef WITH_TCP
 static void listener_free_cb(void *ctx)
 {
-       rad_listen_t *this = ctx;
+       rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t);
        char buffer[1024];
 
        if (this->count > 0) {
@@ -3852,10 +4209,12 @@ static int proxy_eol_cb(void *ctx, void *data)
 }
 #endif
 
-int event_new_fd(rad_listen_t *this)
+static int event_new_fd(rad_listen_t *this)
 {
        char buffer[1024];
 
+       ASSERT_MASTER;
+
        if (this->status == RAD_LISTEN_STATUS_KNOWN) return 1;
 
        this->print(this, buffer, sizeof(buffer));
@@ -3869,109 +4228,86 @@ int event_new_fd(rad_listen_t *this)
                        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) {
-                       PTHREAD_MUTEX_LOCK(&proxy_mutex);
-                       if (!fr_packet_list_socket_add(proxy_list, this->fd,
-                                                      sock->proto,
-                                                      &sock->other_ipaddr, sock->other_port,
-                                                      this)) {
-
-#ifdef HAVE_PTHREAD_H
-                               proxy_no_new_sockets = true;
-#endif
-                               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
-
-                               /*
-                                *      This is bad.  However, the
-                                *      packet list now supports 256
-                                *      open sockets, which should
-                                *      minimize this problem.
-                                */
-                               ERROR("Failed adding proxy socket: %s",
-                                      fr_strerror());
-                               return 0;
-                       }
-
-                       if (sock->home) {
-                               sock->home->limit.num_connections++;
-
-#ifdef HAVE_PTHREAD_H
-                               /*
-                                *      If necessary, add it to the list of
-                                *      new proxy listeners.
-                                */
-                               if (sock->home->limit.lifetime || sock->home->limit.idle_timeout) {
-                                       this->next = proxy_listener_list;
-                                       proxy_listener_list = this;
-                               }
-#endif
-                       }
-                       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->limit.lifetime || sock->home->limit.idle_timeout) {
-                                       radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD);
-                               }
-                       }
-               }
-#endif
-
+               switch (this->type) {
 #ifdef WITH_DETAIL
                /*
                 *      Detail files are always known, and aren't
                 *      put into the socket event loop.
                 */
-               if (this->type == RAD_LISTEN_DETAIL) {
+               case RAD_LISTEN_DETAIL:
                        this->status = RAD_LISTEN_STATUS_KNOWN;
 
+#ifndef WITH_DETAIL_THREAD
                        /*
                         *      Set up the first poll interval.
                         */
                        event_poll_detail(this);
                        return 1;
-               }
+#else
+                       break;  /* add the FD to the list */
 #endif
+#endif /* WITH_DETAIL */
 
-#ifdef WITH_TCP
+#ifdef WITH_PROXY
                /*
-                *      Add timers to child sockets, if necessary.
+                *      Add it to the list of sockets we can use.
+                *      Server sockets (i.e. auth/acct) are never
+                *      added to the packet list.
                 */
-               if (sock->proto == IPPROTO_TCP && sock->opened &&
-                   (sock->limit.lifetime || sock->limit.idle_timeout)) {
-                       struct timeval when;
+               case RAD_LISTEN_PROXY:
+#ifdef WITH_TCP
+                       /*
+                        *      Add timers to outgoing child sockets, if necessary.
+                        */
+                       if (sock->proto == IPPROTO_TCP && sock->opened &&
+                           (sock->home->limit.lifetime || sock->home->limit.idle_timeout)) {
+                               struct timeval when;
 
-                       ASSERT_MASTER;
+                               when.tv_sec = sock->opened + 1;
+                               when.tv_usec = 0;
 
-                       when.tv_sec = sock->opened + 1;
-                       when.tv_usec = 0;
+                               if (!fr_event_insert(el, tcp_socket_timer, this, &when,
+                                                    &(sock->ev))) {
+                                       rad_panic("Failed to insert event");
+                               }
+                       }
+#endif
+                       break;
+#endif /* WITH_PROXY */
 
-                       if (!fr_event_insert(el, tcp_socket_timer, this, &when,
-                                            &(sock->ev))) {
-                               rad_panic("Failed to insert event");
+                       /*
+                        *      FIXME: put idle timers on command sockets.
+                        */
+
+               default:
+#ifdef WITH_TCP
+                       /*
+                        *      Add timers to incoming child sockets, if necessary.
+                        */
+                       if (sock->proto == IPPROTO_TCP && sock->opened &&
+                           (sock->limit.lifetime || sock->limit.idle_timeout)) {
+                               struct timeval when;
+
+                               when.tv_sec = sock->opened + 1;
+                               when.tv_usec = 0;
+
+                               if (!fr_event_insert(el, tcp_socket_timer, this, &when,
+                                                    &(sock->ev))) {
+                                       rad_panic("Failed to insert event");
+                               }
                        }
-               }
 #endif
+                       break;
+               } /* switch over listener types */
 
-               FD_MUTEX_LOCK(&fd_mutex);
+               /*
+                *      All sockets: add the FD to the event handler.
+                */
                if (!fr_event_fd_insert(el, 0, this->fd,
                                        event_socket_handler, this)) {
                        ERROR("Failed adding event handler for socket!");
                        fr_exit(1);
                }
-               FD_MUTEX_UNLOCK(&fd_mutex);
 
                this->status = RAD_LISTEN_STATUS_KNOWN;
                return 1;
@@ -3985,9 +4321,7 @@ int event_new_fd(rad_listen_t *this)
                /*
                 *      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_PROXY
                /*
@@ -4099,9 +4433,6 @@ int event_new_fd(rad_listen_t *this)
                                      buffer, fr_strerror());
                                fr_exit(1);
                        }
-                       if (sock->home &&  sock->home->limit.num_connections) {
-                               sock->home->limit.num_connections--;
-                       }
                        PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
                } else
 #endif
@@ -4145,6 +4476,8 @@ int event_new_fd(rad_listen_t *this)
 
 static void handle_signal_self(int flag)
 {
+       ASSERT_MASTER;
+
        if ((flag & (RADIUS_SIGNAL_SELF_EXIT | RADIUS_SIGNAL_SELF_TERM)) != 0) {
                if ((flag & RADIUS_SIGNAL_SELF_EXIT) != 0) {
                        INFO("Signalled to exit");
@@ -4171,7 +4504,7 @@ static void handle_signal_self(int flag)
                        return;
                }
 
-               INFO("Received HUP signal.");
+               INFO("Received HUP signal");
 
                last_hup = when;
 
@@ -4180,13 +4513,14 @@ static void handle_signal_self(int flag)
        }
 
 #ifdef WITH_DETAIL
+#ifndef WITH_DETAIL_THREAD
        if ((flag & RADIUS_SIGNAL_SELF_DETAIL) != 0) {
                rad_listen_t *this;
 
                /*
                 *      FIXME: O(N) loops suck.
                 */
-               for (this = mainconfig.listen;
+               for (this = main_config.listen;
                     this != NULL;
                     this = this->next) {
                        if (this->type != RAD_LISTEN_DETAIL) continue;
@@ -4204,58 +4538,48 @@ static void handle_signal_self(int flag)
                }
        }
 #endif
+#endif
 
 #ifdef WITH_TCP
 #ifdef WITH_PROXY
 #ifdef HAVE_PTHREAD_H
        /*
-        *      Add event handlers for idle timeouts && maximum lifetime.
+        *      There are new listeners in the list.  Run
+        *      event_new_fd() on them.
         */
        if ((flag & RADIUS_SIGNAL_SELF_NEW_FD) != 0) {
-               struct timeval when, now;
-
-               fr_event_now(el, &now);
+               rad_listen_t *this, *next;
 
-               PTHREAD_MUTEX_LOCK(&proxy_mutex);
-
-               while (proxy_listener_list) {
-                       rad_listen_t *this = proxy_listener_list;
-                       listen_socket_t *sock = this->data;
+               FD_MUTEX_LOCK(&fd_mutex);
 
-                       rad_assert(sock->proto == IPPROTO_TCP);
-                       proxy_listener_list = this->next;
+               /*
+                *      FIXME: unlock the mutex before calling
+                *      event_new_fd()?
+                */
+               for (this = new_listeners; this != NULL; this = next) {
+                       next = this->next;
                        this->next = NULL;
 
-                       if (!sock->home) continue; /* skip UDP sockets */
-
-                       when = now;
-
-                       /*
-                        *      Sockets should only be added to the
-                        *      proxy_listener_list if they have limits.
-                        *
-                        */
-                       rad_assert(sock->home->limit.lifetime || sock->home->limit.idle_timeout);
-
-                       if (!fr_event_insert(el, tcp_socket_timer, this, &when,
-                                            &(sock->ev))) {
-                               rad_panic("Failed to insert event");
-                       }
+                       event_new_fd(this);
                }
 
-               PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
+               new_listeners = NULL;
+               FD_MUTEX_UNLOCK(&fd_mutex);
        }
 #endif /* HAVE_PTHREAD_H */
 #endif /* WITH_PROXY */
 #endif /* WITH_TCP */
 }
 
-#ifndef WITH_SELF_PIPE
+#ifndef HAVE_PTHREAD_H
 void radius_signal_self(int flag)
 {
-       handle_signal_self(flag);
+       return handle_signal_self(flag);
 }
+
 #else
+static int self_pipe[2] = { -1, -1 };
+
 /*
  *     Inform ourselves that we received a signal.
  */
@@ -4280,7 +4604,7 @@ void radius_signal_self(int flag)
 
        buffer[0] |= flag;
 
-       write(self_pipe[1], buffer, 1);
+       if (write(self_pipe[1], buffer, 1) < 0) fr_exit(0);
 }
 
 
@@ -4302,7 +4626,7 @@ static void event_signal_handler(UNUSED fr_event_list_t *xel,
 
        handle_signal_self(buffer[0]);
 }
-#endif
+#endif /* HAVE_PTHREAD_H */
 
 /***********************************************************************
  *
@@ -4328,19 +4652,20 @@ int radius_event_start(CONF_SECTION *cs, bool have_children)
 
        time(&fr_start_time);
 
-       /*
-        *  radius_event_init() must be called first
-        */
-       rad_assert(el);
-       if (fr_start_time == (time_t)-1) return 0;
+       if (!check_config) {
+               /*
+                *  radius_event_init() must be called first
+                */
+               rad_assert(el);
 
-       pl = fr_packet_list_create(0);
-       if (!pl) return 0;      /* leak el */
+               pl = fr_packet_list_create(0);
+               if (!pl) return 0;      /* leak el */
+       }
 
        request_num_counter = 0;
 
 #ifdef WITH_PROXY
-       if (mainconfig.proxy_requests) {
+       if (main_config.proxy_requests) {
                /*
                 *      Create the tree for managing proxied requests and
                 *      responses.
@@ -4355,6 +4680,17 @@ int radius_event_start(CONF_SECTION *cs, bool have_children)
                        fr_exit(1);
                }
 #endif
+
+               /*
+                *      The "init_delay" is set to "response_window".
+                *      Reset it to half of "response_window" in order
+                *      to give the event loop enough time to service
+                *      the event before hitting "response_window".
+                */
+               main_config.init_delay.tv_usec += (main_config.init_delay.tv_sec & 0x01) * USEC;
+               main_config.init_delay.tv_usec >>= 1;
+               main_config.init_delay.tv_sec >>= 1;
+
        }
 #endif
 
@@ -4380,7 +4716,7 @@ int radius_event_start(CONF_SECTION *cs, bool have_children)
 
        if (check_config) {
                DEBUG("%s: #### Skipping IP addresses and Ports ####",
-                      mainconfig.name);
+                      main_config.name);
                if (listen_init(cs, &head, spawn_flag) < 0) {
                        fflush(NULL);
                        fr_exit(1);
@@ -4388,7 +4724,7 @@ int radius_event_start(CONF_SECTION *cs, bool have_children)
                return 1;
        }
 
-#ifdef WITH_SELF_PIPE
+#ifdef HAVE_PTHREAD_H
        /*
         *      Child threads need a pipe to signal us, as do the
         *      signal handlers.
@@ -4416,10 +4752,10 @@ int radius_event_start(CONF_SECTION *cs, bool have_children)
                ERROR("Failed creating handler for signals");
                fr_exit(1);
        }
-#endif /* WITH_SELF_PIPE */
+#endif
 
        DEBUG("%s: #### Opening IP addresses and Ports ####",
-              mainconfig.name);
+              main_config.name);
 
        /*
        *       The server temporarily switches to an unprivileged
@@ -4434,7 +4770,7 @@ int radius_event_start(CONF_SECTION *cs, bool have_children)
                fr_exit_now(1);
        }
 
-       mainconfig.listen = head;
+       main_config.listen = head;
 
        /*
         *      At this point, no one has any business *ever* going
@@ -4451,6 +4787,8 @@ static int proxy_delete_cb(UNUSED void *ctx, void *data)
 {
        REQUEST *request = fr_packet2myptr(REQUEST, proxy, data);
 
+       VERIFY_REQUEST(request);
+
        request->master_state = REQUEST_STOP_PROCESSING;
 
 #ifdef HAVE_PTHREAD_H
@@ -4483,12 +4821,14 @@ static int request_delete_cb(UNUSED void *ctx, void *data)
 {
        REQUEST *request = fr_packet2myptr(REQUEST, packet, data);
 
+       VERIFY_REQUEST(request);
+
        request->master_state = REQUEST_STOP_PROCESSING;
 
        /*
         *      Not done, or the child thread is still processing it.
         */
-       if (request->child_state < REQUEST_REJECT_DELAY) return 0; /* continue */
+       if (request->child_state < REQUEST_RESPONSE_DELAY) return 0; /* continue */
 
 #ifdef HAVE_PTHREAD_H
        if (pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) == 0) return 0;
@@ -4501,7 +4841,7 @@ static int request_delete_cb(UNUSED void *ctx, void *data)
        request->in_request_hash = false;
        if (request->ev) fr_event_delete(el, &request->ev);
 
-       if (mainconfig.memory_report) {
+       if (main_config.memory_report) {
                RDEBUG2("Cleaning up request packet ID %u with timestamp +%d",
                        request->packet->id,
                        (unsigned int) (request->timestamp - fr_start_time));
@@ -4513,7 +4853,7 @@ static int request_delete_cb(UNUSED void *ctx, void *data)
        }
 #endif
 
-       request_free(&request);
+       talloc_free(request);
 
        /*
         *      Delete it from the list, and continue;
@@ -4551,7 +4891,7 @@ void radius_event_free(void)
                 *      Walk the lists again, ensuring that all
                 *      requests are done.
                 */
-               if (mainconfig.memory_report) {
+               if (main_config.memory_report) {
                        int num;
 
 #ifdef WITH_PROXY