extern pid_t radius_pid;
extern int dont_fork;
extern int check_config;
-extern void force_log_reopen(void);
extern char *debug_condition;
/*
#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
*/
#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
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;
}
}
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);
+ }
/*
- * The TCP socket was closed, but we've got to
- * hang around until done.
+ * Divorce the parent from the child, and leave the
+ * parent still alive.
*/
- if ((request->listener->status == RAD_LISTEN_STATUS_FINISH) &&
- (request->listener->count == 0)) {
- listen_free(&request->listener);
+ 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)
}
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);
}
fr_packet_list_yank(proxy_list, request->proxy);
-
-#ifdef WITH_TCP
- /*
- * This should be hit when accounting packets don't have
- * responses, or when the home server is down.
- */
- if (request->home_server->proto == IPPROTO_TCP) {
- fr_tcp_radius_t *tcp;
-
- tcp = fr_listen2tcp(request->proxy_listener);
- if (tcp && tcp->ids[request->proxy->id]) {
- tcp->ids[request->proxy->id] = NULL;
- tcp->used--;
- }
- } else
-#endif
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 &&
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;
}
#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);
-#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);
+ return 0;
}
#ifdef WITH_PROXY
-static int proxy_id_alloc(REQUEST *request, RADIUS_PACKET *packet)
+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++;
- }
-
-#ifdef WITH_TCP
- if (request->home_server->proto == IPPROTO_TCP) {
- rad_listen_t *this = NULL;
- fr_tcp_radius_t *tcp = NULL;
+ 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;
/*
- * FIXME: Order the TCP sockets by most recently
- * used, so that we keep the TCP pipe full.
- *
- * FIXME: Try to use the same TCP connection for
- * all packets of an EAP conversation. This will
- * be hard...
+ * Also locks the proxy mutex, so we have to call
+ * it with the mutex unlocked. Some systems
+ * don't support recursive mutexes.
*/
- request->proxy->id = -1;
- for (i = 0; i < request->home_server->max_connections; i++) {
- this = request->home_server->listeners[i];
-
- if (!this) continue;
- tcp = fr_listen2tcp(this);
- if (!tcp) continue;
-
- /*
- * If max requests per connection are
- * set, and we've reached that limit,
- * then skip the connection. It will be
- * closed later, when all of the
- * responses come back.
- */
- if (request->home_server->max_requests &&
- (tcp->num_packets >= request->home_server->max_requests)) {
- continue;
- }
-
- /*
- * FIXME: This is inefficient. Use
- * something else, like MRU.
- */
- for (i = 0; i < 256; i++) {
- if (tcp->ids[i]) continue;
-
- request->proxy->id = i;
- break;
- }
-
- if (request->proxy->id >= 0) break;
- }
-
- if (request->proxy->id < 0) {
- /*
- * Allocate new listener for this home
- * server.
- *
- * FIXME: split the "connect" portion
- * into a separate call, so it can happen
- * when the mutex is NOT held!
- */
- this = proxy_new_tcp_listener(request->home_server);
- if (!this) {
- PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
- DEBUG2("ERROR: Failed to allocate id");
- return 0;
- }
-
- tcp = fr_listen2tcp(this);
- if (!tcp) return 0;
-
- rad_assert(tcp->used == 0);
- rad_assert(tcp->ids[0] == NULL);
- request->proxy->id = 0;
-
- /*
- * FIXME: Start timers as per RFC 3539
- */
-
+ 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 */
- rad_assert(tcp != NULL);
- rad_assert(tcp->ids[request->proxy->id] == NULL);
-
- tcp->ids[request->proxy->id] = &request->proxy;
- request->proxy_listener = this;
- request->proxy->sockfd = tcp->fd;
- request->proxy->src_ipaddr = tcp->src_ipaddr;
- request->proxy->dst_ipaddr = tcp->dst_ipaddr;
- request->proxy->src_port = tcp->src_port;
- request->proxy->dst_port = tcp->dst_port;
-
- tcp->num_packets++;
-
- if (!fr_packet_list_insert(proxy_list, &request->proxy)) {
- PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
- DEBUG2("ERROR: Failed to insert entry into proxy list");
+ tries++;
+ if (tries > 2) {
+ RDEBUG2("ERROR: Failed allocating Id for new socket when proxying requests.");
return 0;
}
-
- tcp->used++;
- tcp->ids[request->proxy->id] = &request->proxy;
-
- goto done;
+
+ goto retry;
}
-#endif
- if (retransmit) {
- RADIUS_PACKET packet;
+ request->proxy_listener = proxy_listener;
- packet = *request->proxy;
-
- if (!proxy_id_alloc(request, &packet)) {
- PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
- return 0;
- }
-
- /*
- * Yank the request, free the old Id, and
- * remember the new Id.
- */
- fr_packet_list_yank(proxy_list, request->proxy);
+ PTHREAD_MUTEX_LOCK(&proxy_mutex);
+ if (!fr_packet_list_insert(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);
+ radlog(L_PROXY, "Failed to insert entry into proxy list.");
return 0;
}
- rad_assert(request->proxy->sockfd >= 0);
+ request->in_proxy_hash = TRUE;
/*
- * FIXME: Hack until we get rid of rad_listen_t, and put
- * the information into the packet_list.
+ * Keep track of maximum outstanding requests to a
+ * particular home server. 'max_outstanding' is
+ * enforced in home_server_ldb(), in realms.c.
*/
- proxy = -1;
- for (i = 0; i < 32; i++) {
- if (proxy_fds[i] == request->proxy->sockfd) {
- proxy = i;
- break;
- }
- }
-
- if (proxy < 0) {
- PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
- RDEBUG2("ERROR: All sockets are full.");
- return 0;
- }
-
- rad_assert(proxy_fds[proxy] != -1);
- rad_assert(proxy_listeners[proxy] != NULL);
- request->proxy_listener = proxy_listeners[proxy];
-
- 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");
- return 0;
+ if (request->home_server) {
+ request->home_server->currently_outstanding++;
}
#ifdef WITH_TCP
- done:
+ request->proxy_listener->count++;
#endif
- request->in_proxy_hash = TRUE;
-
PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
RDEBUG3(" proxy: allocating destination %s port %d - Id %d",
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
#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));
}
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) {
}
#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);
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);
*/
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);
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;
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);
/*
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)),
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;
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);
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
/*
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
/*
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;
}
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);
if (have_children) wait_a_bit(request);
}
-#ifdef WITH_TCP
-static void no_response_to_tcp_request(REQUEST *request)
-{
- fr_tcp_radius_t *tcp;
- rad_listen_t *proxy_listener;
-
- proxy_listener = request->proxy_listener;
- tcp = fr_listen2tcp(proxy_listener);
- /* MAY be NULL if proxy_listener is NULL */
-
- /*
- * May have been closed by someone else.
- */
- if (!request->proxy_listener) return;
-
- rad_assert(tcp != NULL);
- rad_assert(tcp->state != HOME_STATE_IS_DEAD);
-
- /*
- * There's already an event set up to mark it dead.
- */
- if (tcp->state == HOME_STATE_ZOMBIE) {
- rad_assert(tcp->ev != NULL);
- return;
- }
-
- /*
- * Enable the zombie period when we notice that the home
- * server hasn't responded. We do NOT back-date the start
- * of the zombie period.
- */
- if (tcp->state == HOME_STATE_ALIVE) {
- char buffer[128];
- radlog(L_ERR, "PROXY: Marking TCP connection to %s port %d as zombie (it looks like it is dead).",
- inet_ntop(tcp->dst_ipaddr.af, &tcp->dst_ipaddr.ipaddr,
- buffer, sizeof(buffer)),
- tcp->dst_port);
- tcp->state = HOME_STATE_ZOMBIE;
- }
-}
-#endif /* WITH_TCP */
-
/* maybe check this against wait_for_proxy_id_to_expire? */
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);
/*
return;
}
- home = request->home_server;
-
#ifdef WITH_TCP
- /*
- * If there's no response, then the connection is likely
- * dead.
- */
- if (home->proto == IPPROTO_TCP) {
- no_response_to_tcp_request(request);
- } else
+ if (request->home_server->proto != IPPROTO_TCP)
#endif
- check_for_zombie_home_server(request);
+ check_for_zombie_home_server(request);
+
+ home = request->home_server;
/*
* The default as of 2.1.7 is to allow requests to
* well.
*/
if (home->no_response_fail) {
- radlog(L_ERR, "Rejecting request %d (proxy Id %d) due to lack of any response from home server %s port %d",
- request->number, request->proxy->id,
+ radlog_request(L_ERR, 0, request, "Rejecting request (proxy Id %d) due to lack of any response from home server %s port %d",
+ request->proxy->id,
inet_ntop(request->proxy->dst_ipaddr.af,
&request->proxy->dst_ipaddr.ipaddr,
buffer, sizeof(buffer)),
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) {
- home->state = HOME_STATE_ZOMBIE;
- home->zombie_period_start = now;
-
- 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);
-
+ 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
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
/*
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;
* 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);
break;
}
-#if defined(HAVE_PTHREAD_H) || defined(WITH_PROXY)
+ stop_processing:
+ request->master_state = REQUEST_STOP_PROCESSING;
+
/*
* A child thread MAY still be running on the
* request. Ask the thread to stop working on
* the request.
*/
- if (have_children) {
- /* FIXME: kill unresponsive children? */
-
- /*
- * Print this error message ONLY if
- * there's a child currently processing
- * the request. As we don't have thread
- * locks here, there may be race
- * conditions on this check. But it's
- * just an error message, so that's OK.
- */
- if (!pthread_equal(request->child_pid, NO_SUCH_CHILD_PID)) {
- radlog(L_ERR, "WARNING: Unresponsive child for request %d, in module %s component %s",
- request->number,
- request->module ? request->module : "<server core>",
- request->component ? request->component : "<server core>");
- }
+ if (have_children &&
+ (pthread_equal(request->child_pid, NO_SUCH_CHILD_PID) == 0)) {
+ radlog(L_ERR, "WARNING: Unresponsive child for request %u, in component %s module %s",
+ request->number,
+ request->component ? request->component : "<server core>",
+ request->module ? request->module : "<server core>");
- stop_processing:
- request->master_state = REQUEST_STOP_PROCESSING;
-
- request->delay = USEC / 4;
- tv_add(&request->when, request->delay);
- callback = wait_for_child_to_die;
- break;
}
-#else /* no child threads */
- stop_processing:
+
+ 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.
return;
}
#endif
- request_stats_final(request);
cleanup_delay(request);
return;
#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);
* 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;
}
{
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;
}
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,
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) {
} 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,
}
}
- 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:
/*
* 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;
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;
}
* 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);
* 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;
* 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
}
#endif
-#ifdef WITH_TCP
-static void tcp_socket_lifetime(void *ctx)
-{
- rad_listen_t *listener = ctx;
-
- DEBUG("Reached lifetime on outgoing connection to home server");
- proxy_close_tcp_listener(listener);
-}
-
-static void tcp_idle_timeout(void *ctx)
-{
- rad_listen_t *listener = ctx;
- fr_tcp_radius_t *tcp = fr_listen2tcp(listener);
-
- /*
- * Rather than adding && deleting the timer when "used"
- * changes from 1->0, or from 0->1, we just add it when
- * "used" reaches zero, and don't touch it after that.
- * However, in order to avoid deleting used sockets, we
- * double-check things here.
- */
- if (tcp->used != 0) {
- /*
- * If it's NOT idle, we still need to enforce
- * maximum lifetime.
- */
- if (tcp->lifetime > 0) {
- struct timeval when;
-
- when.tv_sec = tcp->opened;
- when.tv_sec += tcp->lifetime;
- when.tv_usec = fr_rand() & 0xffff;
-
- fr_event_insert(el, tcp_socket_lifetime,
- listener,
- &when, (fr_event_t **) &tcp->ev);
- }
- return;
- }
-
-
- DEBUG("Reasched idle timeout on outgoing connection to home server");
- proxy_close_tcp_listener(listener);
-}
-#endif
-
-
static int request_pre_handler(REQUEST *request)
{
int rcode;
*/
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;
}
* 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) {
}
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;
}
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;
}
}
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.
* 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
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) {
* 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
* 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;
* 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);
}
* 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);
/*
* 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;
}
if (!proxy_request(request)) {
- RDEBUG("ERROR: Failed to proxy request %d", request->number);
+ RDEBUG("ERROR: Failed to proxy request");
return -1;
}
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
*/
if ((request->packet->code == PW_AUTHENTICATION_REQUEST) &&
(request->reply->code == 0) &&
- ((vp = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL) &&
+ ((vp = pairfind(request->config_items, PW_AUTH_TYPE, 0)) != NULL) &&
(vp->vp_integer == PW_AUTHTYPE_REJECT)) {
request->reply->code = PW_AUTHENTICATION_REJECT;
}
* 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
/*
/*
* 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
* 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
* 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);
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;
* 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);
}
#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");
}
}
#endif
- RDEBUG2("Finished request %d.", request->number);
+ RDEBUG2("Finished request.");
rad_assert(child_state >= 0);
request->child_state = child_state;
}
-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);
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);
check_for_zombie_home_server(request);
/*
- * If we've just discovered that the home server
- * is dead, OR the socket has been closed, look for
- * another connection to a home server.
+ * Home server is still alive, and the proxy
+ * socket is OK. Just re-send the packet.
*/
- if (((request->packet->dst_port != 0) &&
- (request->home_server->state == HOME_STATE_IS_DEAD)) ||
- (request->proxy_listener->status != RAD_LISTEN_STATUS_KNOWN)) {
- home_server *home;
-
- remove_from_proxy_hash(request);
-
- home = home_server_ldb(NULL, request->home_pool, request);
- if (!home) {
- RDEBUG2("Failed to find live home server for request %d", request->number);
- no_home_servers:
- /*
- * Do post-request processing,
- * and any insertion of necessary
- * events.
- */
- post_proxy_fail_handler(request);
- return;
- }
-
- request->proxy->code = request->packet->code;
-
- /*
- * Free the old packet, to force re-encoding
- */
- free(request->proxy->data);
- request->proxy->data = NULL;
- request->proxy->data_len = 0;
-
- /*
- * This request failed over to a virtual
- * server. Push it back onto the queue
- * to be processed.
- */
- if (request->home_server->server) {
- proxy_fallback_handler(request);
- return;
- }
-
- /*
- * Try to proxy the request.
- */
- if (!proxy_request(request)) {
- RDEBUG("ERROR: Failed to re-proxy request %d", request->number);
- goto no_home_servers;
- }
-
- /*
- * This code executes in the main server
- * thread, so there's no need for locking.
- */
- rad_assert(request->next_callback != NULL);
- INSERT_EVENT(request->next_callback, request);
- request->next_callback = NULL;
- return;
- } /* else the home server is still alive */
-
-#ifdef WITH_TCP
- if (request->home_server->proto == IPPROTO_TCP) {
- DEBUG2("Suppressing duplicate proxied request to home server %s port %d proto TCP - ID: %d",
- inet_ntop(request->proxy->dst_ipaddr.af,
- &request->proxy->dst_ipaddr.ipaddr,
- buffer, sizeof(buffer)),
- request->proxy->dst_port,
- request->proxy->id);
+ if ((request->home_server->state != HOME_STATE_IS_DEAD) &&
+ (request->proxy_listener->status == RAD_LISTEN_STATUS_KNOWN)) {
+ rad_retransmit_packet(request);
break;
}
-#endif
- RDEBUG2("Sending duplicate proxied request to home server %s port %d - ID: %d",
- inet_ntop(request->proxy->dst_ipaddr.af,
- &request->proxy->dst_ipaddr.ipaddr,
- buffer, sizeof(buffer)),
- request->proxy->dst_port,
- request->proxy->id);
- request->num_proxied_requests++;
+ /*
+ * Otherwise, we need to fail over to another
+ * home server, and possibly run "post-proxy-type
+ * fail". Add an event waiting for the child to
+ * have a result.
+ */
+ INSERT_EVENT(wait_a_bit, request);
- DEBUG_PACKET(request, request->proxy, 1);
- request->proxy_listener->send(request->proxy_listener,
- request);
+ request->priority = RAD_LISTEN_PROXY;
+ thread_pool_addrequest(request, rad_retransmit);
break;
#endif
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);
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;
INSERT_EVENT(wait_for_child_to_die, request);
return;
-#endif
/*
* Catch race conditions. It may have switched
case REQUEST_CLEANUP_DELAY:
case REQUEST_DONE:
break;
-
- /*
- * 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.
- */
- case REQUEST_PROXIED:
- default:
- rad_assert(request->ev != NULL);
- break;
}
}
*/
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);
*/
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);
/*
* We may want to quench the new request.
*/
- if ((listener->type != RAD_LISTEN_DETAIL) &&
+ if (
+#ifdef WITH_DETAIL
+ (listener->type != RAD_LISTEN_DETAIL) &&
+#endif
!can_handle_new_request(packet, client, root)) {
return 0;
}
if ((request->reply = rad_alloc(0)) == NULL) {
radlog(L_ERR, "No memory");
- exit(1);
+ return 0;
}
request->listener = 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;
}
#ifdef WITH_PROXY
-#ifdef WITH_TCP
-/*
- * received_proxy_response is split in two for TCP handling.
- */
-static REQUEST *received_proxy_response_p2(RADIUS_PACKET *packet,
- REQUEST *request);
-#endif
-
REQUEST *received_proxy_response(RADIUS_PACKET *packet)
{
-#ifndef WITH_TCP
char buffer[128];
- home_server *home;
-#endif
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);
-#ifdef WITH_TCP
- return received_proxy_response_p2(packet, request);
-}
-
-static REQUEST *received_proxy_response_p2(RADIUS_PACKET *packet,
- REQUEST *request)
-{
- char buffer[128];
- fr_tcp_radius_t *tcp;
-#endif
-
if (!request) {
radlog(L_PROXY, "No outstanding request was found for reply from host %s port %d - ID %d",
inet_ntop(packet->src_ipaddr.af,
}
/*
- * 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
* 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
/*
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
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
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)),
}
#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;
request->priority = RAD_LISTEN_PROXY;
tv_add(&request->when, request->delay);
+ /*
+ * Wait a bit will take care of max_request_time
+ */
+ INSERT_EVENT(wait_a_bit, request);
+
+ return request;
+}
+
+#endif /* WITH_PROXY */
+
#ifdef WITH_TCP
- tcp = fr_listen2tcp(request->proxy_listener);
- if (tcp) {
- if ((tcp->lifetime > 0) &&
- (now.tv_sec > (tcp->opened + tcp->lifetime))) {
- proxy_close_tcp_listener(request->proxy_listener);
- } else
+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
+
+int event_new_fd(rad_listen_t *this)
+{
+ char buffer[1024];
+
+ 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 {
+ 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
/*
- * No outstanding packets. Set up idle timeout
- * timer, but only if we didn't already do so
- * this second.
+ * Detail files are always known, and aren't
+ * put into the socket event loop.
*/
- if ((tcp->used == 0) &&
- (request->home_server->idle_timeout != 0) &&
- (tcp->last_packet != now.tv_sec)) {
- struct timeval when;
-
- when = now;
- when.tv_sec += request->home_server->idle_timeout;
+ if (this->type == RAD_LISTEN_DETAIL) {
+ this->status = RAD_LISTEN_STATUS_KNOWN;
- fr_event_insert(el, tcp_idle_timeout,
- request->proxy_listener,
- &when, (fr_event_t **) &tcp->ev);
+ /*
+ * Set up the first poll interval.
+ */
+ event_poll_detail(this);
+ return 1;
}
+#endif
- tcp->last_packet = request->proxy_reply->timestamp = now.tv_sec;
+ FD_MUTEX_LOCK(&fd_mutex);
+ if (!fr_event_fd_insert(el, 0, this->fd,
+ event_socket_handler, this)) {
+ radlog(L_ERR, "Failed adding event handler for proxy socket!");
+ exit(1);
+ }
+ FD_MUTEX_UNLOCK(&fd_mutex);
+
+ this->status = RAD_LISTEN_STATUS_KNOWN;
+ return 1;
}
-#endif
/*
- * Wait a bit will take care of max_request_time
+ * Something went wrong with the socket: make it harmless.
*/
- INSERT_EVENT(wait_a_bit, request);
+ if (this->status == RAD_LISTEN_STATUS_REMOVE_FD) {
+ int devnull;
- return request;
-}
+ /*
+ * 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
-REQUEST *received_proxy_tcp_response(RADIUS_PACKET *packet,
- fr_tcp_radius_t *tcp)
-{
- REQUEST *request;
-
- /*
- * Not found. Print the same message as all the other
- * code paths.
- */
- if (!tcp || !tcp->ids[packet->id]) {
- return received_proxy_response_p2(packet, NULL);
- }
-
- /*
- * Find request from: tcp->ids[packet->id] = &request->proxy
- */
- request = fr_packet2myptr(REQUEST, proxy, tcp->ids[packet->id]);
+ /*
+ * 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);
+ }
- /*
- * TCP sockets don't do re-transmits. Just nuke it.
- */
- remove_from_proxy_hash(request);
+ if (this->count == 0) {
+ this->status = RAD_LISTEN_STATUS_FINISH;
+ goto finish;
+ }
+ }
+#ifdef WITH_PROXY
+ else {
+ int count;
- /*
- * FIXME: Update RFC 3539 watchdog timer.
- */
+ /*
+ * 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);
+ }
- return received_proxy_response_p2(packet, request);
-}
-#endif /* WITH_TCP */
-#endif /* WITH_PROXY */
+ /*
+ * 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);
-void event_new_fd(rad_listen_t *this)
-{
- char buffer[1024];
+ if (count == 0) {
+ this->status = RAD_LISTEN_STATUS_FINISH;
+ goto finish;
+ }
+ }
+#endif /* WITH_PROXY */
+#endif /* WITH_TCP */
- if (this->status == RAD_LISTEN_STATUS_KNOWN) return;
-
- 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);
+ /*
+ * 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 (!fr_event_fd_insert(el, 0, this->fd,
- event_socket_handler, this)) {
- radlog(L_ERR, "Failed remembering handle for proxy socket!");
+ if (dup2(devnull, this->fd) < 0) {
+ radlog(L_ERR, "FATAL failure closing socket: %s",
+ strerror(errno));
exit(1);
}
-
-#ifdef WITH_TCP
- if (this->type == RAD_LISTEN_PROXY) {
- fr_tcp_radius_t *tcp = fr_listen2tcp(this);
+ close(devnull);
+
+ this->status = RAD_LISTEN_STATUS_CLOSED;
+
+ /*
+ * Fall through to the next section.
+ */
+ }
- if (tcp && (tcp->lifetime > 0)) {
- struct timeval when;
+#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;
- gettimeofday(&when, NULL);
- when.tv_sec += tcp->lifetime;
+#ifdef WITH_DETAIL
+ rad_assert(this->type != RAD_LISTEN_DETAIL);
+#endif
- fr_event_insert(el, tcp_socket_lifetime,
- this,
- &when, (fr_event_t **) &tcp->ev);
+#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
- this->status = RAD_LISTEN_STATUS_KNOWN;
- return;
+ /*
+ * 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;
}
- if (this->status == RAD_LISTEN_STATUS_CLOSED) {
- DEBUG2(" ... closing socket %s", buffer);
-
- fr_event_fd_delete(el, 0, this->fd);
- this->status = RAD_LISTEN_STATUS_FINISH;
-
-#ifdef WITH_TCP
+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);
+
/*
- * FIXME: mark ALL requests for this connection
- * as MASTER FORCE STOP! we can't reply, so
- * there's no point in doing anything
- *
- * ALSO, create packet lists JUST for each proxy
+ * 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
/*
- * Once we're done, the caller free's the
- * listener. However, we've got to ensure that
- * all of the requests using this listener are
- * marked as dead.
+ * Remove it from the list of sockets to be used
+ * when proxying.
*/
if (this->type == RAD_LISTEN_PROXY) {
- int i;
- REQUEST *request;
- fr_tcp_radius_t *tcp = fr_listen2tcp(this);
-
- if (!tcp) return;
-
- for (i = 0; i < 256; i++) {
- if (!tcp->ids[i]) continue;
-
- request = fr_packet2myptr(REQUEST, proxy,
- tcp->ids[i]);
- request->proxy->sockfd = -1;
- request->proxy_listener = NULL;
- request->in_proxy_hash = FALSE;
- tcp->ids[i] = NULL;
- tcp->used--;
+ 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);
}
- tcp->used = 0; /* just to be safe */
-
- fr_event_delete(el, (fr_event_t **) &tcp->ev);
+ if (sock->home) sock->home->num_connections--;
+ PTHREAD_MUTEX_UNLOCK(&proxy_mutex);
}
#endif
/*
- * 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 any pending cleanups.
+ */
+ if (sock->ev) fr_event_delete(el, &sock->ev);
+
+ /*
+ * And finally, close the socket.
*/
- close(this->fd);
- this->fd = -1;
+ 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);
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);
}
#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
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;
/*
exit(1);
}
}
-
+#endif
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)) {
*/
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;
#ifdef WITH_PROXY
if (mainconfig.proxy_requests) {
- pthread_mutexattr_t attr;
-
/*
* Create the tree for managing proxied requests and
* responses.
if (!proxy_list) return 0;
#ifdef HAVE_PTHREAD_H
- pthread_mutexattr_init(&attr);
-
-#ifdef PTHREAD_MUTEX_RECURSIVE
- if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) < 0) {
- radlog(L_ERR, "FATAL: Failed to set type for proxy mutex: %s",
- strerror(errno));
- exit(1);
- }
-#endif
-
if (pthread_mutex_init(&proxy_mutex, NULL) != 0) {
radlog(L_ERR, "FATAL: Failed to initialize proxy mutex: %s",
strerror(errno));
exit(1);
}
-
- pthread_mutexattr_destroy(&attr);
#endif
}
#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);
if (check_config) {
DEBUG("%s: #### Skipping IP addresses and Ports ####",
mainconfig.name);
+ if (listen_init(cs, &head) < 0) {
+ fflush(NULL);
+ exit(1);
+ }
return 1;
}
}
#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);
_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);
-
- this->status = RAD_LISTEN_STATUS_KNOWN;
- }
-
- mainconfig.listen = head;
-
return 1;
}
* 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;
}