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