From: Alan T. DeKok Date: Thu, 24 Sep 2009 15:28:56 +0000 (+0200) Subject: Jumbo patch to clean up socket handling X-Git-Tag: release_3_0_0_beta0~1735 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e578278cb8c2807dd5ec049683471fb75bf13655;p=thirdparty%2Ffreeradius-server.git Jumbo patch to clean up socket handling The transport protocol code is now more merged, and the "fr_tcp_radius_t" structure and API are deleted. The resulting code is simpler and smaller. Cleaned up how the listeners and even handlers deal with sockets. The proxy sockets are now pushed into the proxy packet list, and are not used in the main listener list. The proxy packet list now deals with src/dst, and not just destination. --- diff --git a/src/include/libradius.h b/src/include/libradius.h index c480671dbdb..882ca3810a0 100644 --- a/src/include/libradius.h +++ b/src/include/libradius.h @@ -53,6 +53,10 @@ RCSIDH(libradius_h, "$Id$") #include #include +#ifndef WITHOUT_TCP +#define WITH_TCP (1) +#endif + #define EAP_START 2 #define AUTH_VECTOR_LEN 16 diff --git a/src/include/packet.h b/src/include/packet.h index ed0cfdefd1c..f00eb904c9c 100644 --- a/src/include/packet.h +++ b/src/include/packet.h @@ -30,9 +30,11 @@ RCSIDH(packet_h, "$Id$") uint32_t fr_request_packet_hash(const RADIUS_PACKET *packet); uint32_t fr_reply_packet_hash(const RADIUS_PACKET *packet); int fr_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b); +int fr_inaddr_any(fr_ipaddr_t *ipaddr); void fr_request_from_reply(RADIUS_PACKET *request, const RADIUS_PACKET *reply); int fr_socket(fr_ipaddr_t *ipaddr, int port); +int fr_nonblock(int fd); typedef struct fr_packet_list_t fr_packet_list_t; @@ -49,11 +51,15 @@ RADIUS_PACKET **fr_packet_list_yank(fr_packet_list_t *pl, RADIUS_PACKET *request); int fr_packet_list_num_elements(fr_packet_list_t *pl); int fr_packet_list_id_alloc(fr_packet_list_t *pl, - RADIUS_PACKET *request); + RADIUS_PACKET *request, void **pctx); int fr_packet_list_id_free(fr_packet_list_t *pl, RADIUS_PACKET *request); -int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd); -int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd); +int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, + fr_ipaddr_t *dst_ipaddr, int dst_port, + void *ctx); +int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd, + void **pctx); +int fr_packet_list_socket_freeze(fr_packet_list_t *pl, int sockfd); int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx, fr_hash_table_walk_t callback); int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set); diff --git a/src/include/radiusd.h b/src/include/radiusd.h index 349fb79d2ee..9d775956d59 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -335,10 +335,49 @@ struct rad_listen_t { #endif }; +/* + * This shouldn't really be exposed... + */ +typedef struct listen_socket_t { + /* + * For normal sockets. + */ + fr_ipaddr_t my_ipaddr; + int my_port; + +#ifdef SO_BINDTODEVICE + const char *interface; +#endif + + /* for outgoing sockets */ + home_server *home; + fr_ipaddr_t other_ipaddr; + int other_port; + +#ifdef WITH_TCP + int proto; + + /* for a proxy connecting to home servers */ + time_t last_packet; + time_t opened; + fr_event_t *ev; + + /* for clients connecting to the server */ + int max_connections; + int num_connections; + struct listen_socket_t *parent; + RADCLIENT *client; + + RADIUS_PACKET *packet; /* for reading partial packets */ +#endif + RADCLIENT_LIST *clients; +} listen_socket_t; + #define RAD_LISTEN_STATUS_INIT (0) #define RAD_LISTEN_STATUS_KNOWN (1) -#define RAD_LISTEN_STATUS_CLOSED (2) -#define RAD_LISTEN_STATUS_FINISH (3) +#define RAD_LISTEN_STATUS_REMOVE_FD (2) +#define RAD_LISTEN_STATUS_CLOSED (3) +#define RAD_LISTEN_STATUS_FINISH (4) typedef enum radlog_dest_t { RADLOG_STDOUT = 0, @@ -634,14 +673,9 @@ void fr_suid_down_permanent(void); /* listen.c */ void listen_free(rad_listen_t **head); int listen_init(CONF_SECTION *cs, rad_listen_t **head); -rad_listen_t *proxy_new_listener(fr_ipaddr_t *ipaddr, int exists); +int proxy_new_listener(home_server *home, int src_port); RADCLIENT *client_listener_find(const rad_listen_t *listener, const fr_ipaddr_t *ipaddr, int src_port); -#ifdef WITH_TCP -fr_tcp_radius_t *fr_listen2tcp(rad_listen_t *this); -rad_listen_t *proxy_new_tcp_listener(home_server *home); -void proxy_close_tcp_listener(rad_listen_t *listener); -#endif #ifdef WITH_STATS RADCLIENT_LIST *listener_find_client_list(const fr_ipaddr_t *ipaddr, @@ -659,10 +693,6 @@ int received_request(rad_listen_t *listener, RADCLIENT *client); REQUEST *received_proxy_response(RADIUS_PACKET *packet); void event_new_fd(rad_listen_t *listener); -#ifdef WITH_TCP -REQUEST *received_proxy_tcp_response(RADIUS_PACKET *packet, - fr_tcp_radius_t *tcp); -#endif /* evaluate.c */ int radius_evaluate_condition(REQUEST *request, int modreturn, int depth, diff --git a/src/include/realms.h b/src/include/realms.h index ca3a233e35c..c4d6891c175 100644 --- a/src/include/realms.h +++ b/src/include/realms.h @@ -40,12 +40,12 @@ typedef struct home_server { #ifdef WITH_TCP int proto; +#endif int max_connections; - int num_connections; - int max_requests; + int num_connections; /* protected by proxy mutex */ + int max_requests; /* for one connection */ int lifetime; int idle_timeout; -#endif /* * Maybe also have list of source IP/ports, && socket? @@ -94,10 +94,6 @@ typedef struct home_server { fr_stats_ema_t ema; #endif - -#ifdef WITH_TCP - struct rad_listen_t *listeners[1]; -#endif } home_server; @@ -143,7 +139,6 @@ REALM *realm_find2(const char *name); /* ... with name taken from realm_find */ home_server *home_server_ldb(const char *realmname, home_pool_t *pool, REQUEST *request); home_server *home_server_find(fr_ipaddr_t *ipaddr, int port); -int home_server_create_listeners(void *head); #ifdef WITH_COA home_server *home_server_byname(const char *name, int type); #endif diff --git a/src/include/tcp.h b/src/include/tcp.h index 5f9bea48778..fed2d57641d 100644 --- a/src/include/tcp.h +++ b/src/include/tcp.h @@ -26,59 +26,10 @@ #include RCSIDH(tcp_h, "$Id$") -/* - * Application-layer watchdog from RFC 3539, Appendix A. - */ -typedef enum fr_watchdog_t { - ALW_INITIAL = 0, - ALW_OK, - ALW_SUSPECT, - ALW_DOWN, - ALW_REOPEN -} fr_watchdog_t; - -#define ALW_TWINIT (6) - -typedef struct fr_tcp_radius_t { - int fd; - fr_ipaddr_t src_ipaddr; - fr_ipaddr_t dst_ipaddr; - int src_port; - int dst_port; - - int num_packets; - int lifetime; - - time_t opened; - time_t last_packet; -#ifdef WITH_TCP_PING - struct timeval when; - void *ev; -#endif - - int state; - int ping_interval; - int num_pings_to_alive; - int num_received_pings; - int num_pings_sent; - int ping_timeout; - - int used; - void *ev; - RADIUS_PACKET **ids[256]; -} fr_tcp_radius_t; - int fr_tcp_socket(fr_ipaddr_t *ipaddr, int port); -int fr_tcp_client_socket(fr_ipaddr_t *ipaddr, int port); +int fr_tcp_client_socket(fr_ipaddr_t *src_ipaddr, fr_ipaddr_t *dst_ipaddr, int dst_port); int fr_tcp_read_packet(RADIUS_PACKET *packet, int flags); RADIUS_PACKET *fr_tcp_recv(int sockfd, int flags); RADIUS_PACKET *fr_tcp_accept(int sockfd); ssize_t fr_tcp_write_packet(RADIUS_PACKET *packet); - -int fr_tcp_list_init(fr_tcp_radius_t *list); -int fr_tcp_list_insert(RADIUS_PACKET **packet, int num, - fr_tcp_radius_t *array[]); - -int fr_tcp_id_free(int ids[256], int id); - #endif /* FR_TCP_H */ diff --git a/src/lib/packet.c b/src/lib/packet.c index 0898d19d0ca..d85d59820f9 100644 --- a/src/lib/packet.c +++ b/src/lib/packet.c @@ -29,6 +29,8 @@ RCSID("$Id$") #include #endif +#include + /* * Take the key fields of a request packet, and convert it to a * hash. @@ -153,6 +155,29 @@ int fr_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b) return fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr); } +int fr_inaddr_any(fr_ipaddr_t *ipaddr) +{ + + if (ipaddr->af == AF_INET) { + if (ipaddr->ipaddr.ip4addr.s_addr == INADDR_ANY) { + return 1; + } + +#ifdef HAVE_STRUCT_SOCKADDR_IN6 + } else if (ipaddr->af == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&(ipaddr->ipaddr.ip6addr))) { + return 1; + } +#endif + + } else { + fr_strerror_printf("Unknown address family"); + return -1; + } + + return 0; +} + /* * Create a fake "request" from a reply, for later lookup. @@ -169,6 +194,21 @@ void fr_request_from_reply(RADIUS_PACKET *request, } +int fr_nonblock(UNUSED int fd) +{ + int flags = 0; + +#ifdef O_NONBLOCK + + flags = fcntl(fd, F_GETFL, NULL); + if (flags >= 0) { + flags |= O_NONBLOCK; + return fcntl(fd, F_SETFL, flags); + } +#endif + return flags; +} + /* * Open a socket on the given IP and port. */ @@ -200,6 +240,10 @@ int fr_socket(fr_ipaddr_t *ipaddr, int port) } #endif + if (fr_nonblock(sockfd) < 0) { + close(sockfd); + return -1; + } if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) { return sockfd; @@ -264,21 +308,30 @@ int fr_socket(fr_ipaddr_t *ipaddr, int port) */ typedef struct fr_packet_socket_t { int sockfd; + void *ctx; int num_outgoing; - int offset; /* 0..31 */ - int inaddr_any; - fr_ipaddr_t ipaddr; - int port; + int src_any; + fr_ipaddr_t src_ipaddr; + int src_port; + + int dst_any; + fr_ipaddr_t dst_ipaddr; + int dst_port; + + int dont_use; + #ifdef WITH_TCP int type; #endif + + uint8_t id[32]; } fr_packet_socket_t; #define FNV_MAGIC_PRIME (0x01000193) -#define MAX_SOCKETS (32) +#define MAX_SOCKETS (256) #define SOCKOFFSET_MASK (MAX_SOCKETS - 1) #define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK) @@ -291,13 +344,10 @@ typedef struct fr_packet_socket_t { struct fr_packet_list_t { fr_hash_table_t *ht; - fr_hash_table_t *dst2id_ht; - int alloc_id; int num_outgoing; + int last_recv; - uint32_t mask; - int last_recv; fr_packet_socket_t sockets[MAX_SOCKETS]; }; @@ -306,7 +356,7 @@ struct fr_packet_list_t { * Ugh. Doing this on every sent/received packet is not nice. */ static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl, - int sockfd) + int sockfd) { int i, start; @@ -321,7 +371,21 @@ static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl, return NULL; } -int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd) +int fr_packet_list_socket_freeze(fr_packet_list_t *pl, int sockfd) +{ + fr_packet_socket_t *ps; + + if (!pl) return 0; + + ps = fr_socket_find(pl, sockfd); + if (!ps) return 0; + + ps->dont_use = 1; + return 1; +} + +int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd, + void **pctx) { fr_packet_socket_t *ps; @@ -336,20 +400,24 @@ int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd) if (ps->num_outgoing != 0) return 0; ps->sockfd = -1; - pl->mask &= ~(1 << ps->offset); - + if (pctx) *pctx = ps->ctx; return 1; } -int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd) +int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, + fr_ipaddr_t *dst_ipaddr, int dst_port, + void *ctx) { int i, start; struct sockaddr_storage src; socklen_t sizeof_src; fr_packet_socket_t *ps; - if (!pl) return 0; + if (!pl || !dst_ipaddr || (dst_ipaddr->af == AF_UNSPEC)) { + fr_strerror_printf("Invalid argument"); + return 0; + } ps = NULL; i = start = SOCK2OFFSET(sockfd); @@ -365,18 +433,21 @@ int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd) } while (i != start); if (!ps) { + fr_strerror_printf("All socket entries are full"); return 0; } memset(ps, 0, sizeof(*ps)); - ps->sockfd = sockfd; - ps->offset = start; + ps->ctx = ctx; + #ifdef WITH_TCP sizeof_src = sizeof(ps->type); if (getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &ps->type, &sizeof_src) < 0) { + fr_strerror_printf("%s", strerror(errno)); return 0; } + #endif /* @@ -390,32 +461,30 @@ int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd) memset(&src, 0, sizeof_src); if (getsockname(sockfd, (struct sockaddr *) &src, &sizeof_src) < 0) { + fr_strerror_printf("%s", strerror(errno)); return 0; } - if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->ipaddr, &ps->port)) { + if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->src_ipaddr, + &ps->src_port)) { + fr_strerror_printf("Failed to get IP"); return 0; } + ps->dst_ipaddr = *dst_ipaddr; + ps->dst_port = dst_port; + + ps->src_any = fr_inaddr_any(&ps->src_ipaddr); + if (ps->src_any < 0) return 0; + + ps->dst_any = fr_inaddr_any(&ps->dst_ipaddr); + if (ps->dst_any < 0) return 0; + /* - * Grab IP addresses & ports from the sockaddr. + * As the last step before returning. */ - if (src.ss_family == AF_INET) { - if (ps->ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY) { - ps->inaddr_any = 1; - } - -#ifdef HAVE_STRUCT_SOCKADDR_IN6 - } else if (src.ss_family == AF_INET6) { - if (IN6_IS_ADDR_UNSPECIFIED(&ps->ipaddr.ipaddr.ip6addr)) { - ps->inaddr_any = 1; - } -#endif - } else { - return 0; - } + ps->sockfd = sockfd; - pl->mask |= (1 << ps->offset); return 1; } @@ -432,76 +501,11 @@ static int packet_entry_cmp(const void *one, const void *two) return fr_packet_cmp(*a, *b); } -/* - * A particular socket can have 256 RADIUS ID's outstanding to - * any one destination IP/port. So we have a structure that - * manages destination IP & port, and has an array of 256 ID's. - * - * The only magic here is that we map the socket number (0..256) - * into an "internal" socket number 0..31, that we use to set - * bits in the ID array. If a bit is 1, then that ID is in use - * for that socket, and the request MUST be in the packet hash! - * - * Note that as a minor memory leak, we don't have an API to free - * this structure, except when we discard the whole packet list. - * This means that if destinations are added and removed, they - * won't be removed from this tree. - */ -typedef struct fr_packet_dst2id_t { - fr_ipaddr_t dst_ipaddr; - int dst_port; - uint32_t id[1]; /* really id[256] */ -} fr_packet_dst2id_t; - - -static uint32_t packet_dst2id_hash(const void *data) -{ - uint32_t hash; - const fr_packet_dst2id_t *pd = data; - - hash = fr_hash(&pd->dst_port, sizeof(pd->dst_port)); - - switch (pd->dst_ipaddr.af) { - case AF_INET: - hash = fr_hash_update(&pd->dst_ipaddr.ipaddr.ip4addr, - sizeof(pd->dst_ipaddr.ipaddr.ip4addr), - hash); - break; - case AF_INET6: - hash = fr_hash_update(&pd->dst_ipaddr.ipaddr.ip6addr, - sizeof(pd->dst_ipaddr.ipaddr.ip6addr), - hash); - break; - default: - break; - } - - return hash; -} - -static int packet_dst2id_cmp(const void *one, const void *two) -{ - const fr_packet_dst2id_t *a = one; - const fr_packet_dst2id_t *b = two; - - if (a->dst_port < b->dst_port) return -1; - if (a->dst_port > b->dst_port) return +1; - - return fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr); -} - -static void packet_dst2id_free(void *data) -{ - free(data); -} - - void fr_packet_list_free(fr_packet_list_t *pl) { if (!pl) return; fr_hash_table_free(pl->ht); - fr_hash_table_free(pl->dst2id_ht); free(pl); } @@ -530,17 +534,7 @@ fr_packet_list_t *fr_packet_list_create(int alloc_id) pl->sockets[i].sockfd = -1; } - if (alloc_id) { - pl->alloc_id = 1; - - pl->dst2id_ht = fr_hash_table_create(packet_dst2id_hash, - packet_dst2id_cmp, - packet_dst2id_free); - if (!pl->dst2id_ht) { - fr_packet_list_free(pl); - return NULL; - } - } + pl->alloc_id = alloc_id; return pl; } @@ -593,12 +587,12 @@ RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl, my_request.sockfd = reply->sockfd; my_request.id = reply->id; - if (ps->inaddr_any) { - my_request.src_ipaddr = ps->ipaddr; + if (ps->src_any) { + my_request.src_ipaddr = ps->src_ipaddr; } else { my_request.src_ipaddr = reply->dst_ipaddr; } - my_request.src_port = ps->port;; + my_request.src_port = ps->src_port; my_request.dst_ipaddr = reply->src_ipaddr; my_request.dst_port = reply->src_port; @@ -638,35 +632,42 @@ int fr_packet_list_num_elements(fr_packet_list_t *pl) * should be protected by a mutex. This does NOT have to be * the same mutex as the one protecting the insert/find/yank * calls! + * + * We assume that the packet has dst_ipaddr && dst_port + * already initialized. We will use those to find an + * outgoing socket. The request MAY also have src_ipaddr set. + * + * We also assume that the sender doesn't care which protocol + * should be used. */ int fr_packet_list_id_alloc(fr_packet_list_t *pl, - RADIUS_PACKET *request) + RADIUS_PACKET *request, void **pctx) { - int i, id, start, fd; - uint32_t free_mask; - fr_packet_dst2id_t my_pd, *pd; + int i, j, k, fd, id, start_i, start_j, start_k; + int src_any = 0; fr_packet_socket_t *ps; - if (!pl || !pl->alloc_id || !request) return 0; - - my_pd.dst_ipaddr = request->dst_ipaddr; - my_pd.dst_port = request->dst_port; - - pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd); - if (!pd) { - pd = malloc(sizeof(*pd) + 255 * sizeof(pd->id[0])); - if (!pd) return 0; + if ((request->dst_ipaddr.af == AF_UNSPEC) || + (request->dst_port == 0)) { + fr_strerror_printf("No destination address/port specified"); + return 0; + } - memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0])); + /* + * Special case: unspec == "don't care" + */ + if (request->src_ipaddr.af == AF_UNSPEC) { + memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr)); + request->src_ipaddr.af = request->dst_ipaddr.af; + } - pd->dst_ipaddr = request->dst_ipaddr; - pd->dst_port = request->dst_port; + src_any = fr_inaddr_any(&request->src_ipaddr); + if (src_any < 0) return 0; - if (!fr_hash_table_insert(pl->dst2id_ht, pd)) { - free(pd); - return 0; - } - } + /* + * MUST specify a destination address. + */ + if (fr_inaddr_any(&request->dst_ipaddr) != 0) return 0; /* * FIXME: Go to an LRU system. This prevents ID re-use @@ -678,60 +679,108 @@ int fr_packet_list_id_alloc(fr_packet_list_t *pl, * The LRU can be avoided if the caller takes care to free * Id's only when all responses have been received, OR after * a timeout. + * + * Right now, the random approach is almost OK... it's + * brute-force over all of the available ID's, BUT using + * random numbers for everything spreads the load a bit. + * + * The old method had a hash lookup on allocation AND + * on free. The new method has brute-force on allocation, + * and near-zero cost on free. */ - id = start = (int) fr_rand() & 0xff; - - while (pd->id[id] == pl->mask) { /* all sockets are using this ID */ - redo: - id++; - id &= 0xff; - if (id == start) return 0; - } - free_mask = ~((~pd->id[id]) & pl->mask); + id = fd = -1; + start_i = fr_rand() & SOCKOFFSET_MASK; - /* - * This ID has at least one socket free. Check the sockets - * to see if they are satisfactory for the caller. - */ - fd = -1; +#define ID_i ((i + start_i) & SOCKOFFSET_MASK) for (i = 0; i < MAX_SOCKETS; i++) { - if (pl->sockets[i].sockfd == -1) continue; /* paranoia */ + if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */ + + ps = &(pl->sockets[ID_i]); /* - * This ID is allocated. + * This socket is marked as "don't use for new + * packets". But we can still receive packets + * that are outstanding. */ - if ((free_mask & (1 << i)) != 0) continue; - + if (ps->dont_use) continue; + + /* + * All IDs are allocated: ignore it. + */ + if (ps->num_outgoing == 256) continue; + + /* + * MUST match dst port, if one has been given. + */ + if ((ps->dst_port != 0) && + (ps->dst_port != request->dst_port)) continue; + /* - * If the caller cares about the source address, - * try to re-use that. This means that the - * requested source address is set, AND this - * socket wasn't bound to "*", AND the requested - * source address is the same as this socket - * address. + * We're sourcing from *, and they asked for a + * specific source address: ignore it. */ - if ((request->src_ipaddr.af != AF_UNSPEC) && - !pl->sockets[i].inaddr_any && - (fr_ipaddr_cmp(&request->src_ipaddr, &pl->sockets[i].ipaddr) != 0)) continue; + if (ps->src_any && !src_any) continue; /* - * They asked for a specific address, and this socket - * is bound to a wildcard address. Ignore this one, too. + * We're sourcing from a specific IP, and they + * asked for a source IP that isn't us: ignore + * it. */ - if ((request->src_ipaddr.af != AF_UNSPEC) && - pl->sockets[i].inaddr_any) continue; + if (!ps->src_any && !src_any && + (fr_ipaddr_cmp(&request->src_ipaddr, + &ps->src_ipaddr) != 0)) continue; + + /* + * UDP sockets are allowed to match + * destination IPs exactly, OR a socket + * with destination * is allowed to match + * any requested destination. + * + * TCP sockets must match the destination + * exactly. They *always* have dst_any=0, + * so the first check always matches. + */ + if (!ps->dst_any && + (fr_ipaddr_cmp(&request->dst_ipaddr, + &ps->dst_ipaddr) != 0)) continue; - fd = i; - break; - } + /* + * Otherwise, this socket is OK to use. + */ - if (fd < 0) { - goto redo; /* keep searching IDs */ + /* + * Look for a free Id, starting from a random number. + */ + start_j = fr_rand() & 0x1f; +#define ID_j ((j + start_j) & 0x1f) + for (j = 0; j < 32; j++) { + if (ps->id[ID_j] == 0xff) continue; + + + start_k = fr_rand() & 0x07; +#define ID_k ((k + start_k) & 0x07) + for (k = 0; k < 8; k++) { + if ((ps->id[ID_j] & (1 << ID_k)) != 0) continue; + + ps->id[ID_j] |= (1 << ID_k); + id = (ID_j * 8) + ID_k; + fd = i; + break; + } + if (fd >= 0) break; + } +#undef ID_i +#undef ID_j +#undef ID_k + if (fd >= 0) break; + break; } - pd->id[id] |= (1 << fd); - ps = &pl->sockets[fd]; + /* + * Ask the caller to allocate a new ID. + */ + if (fd < 0) return 0; ps->num_outgoing++; pl->num_outgoing++; @@ -742,8 +791,10 @@ int fr_packet_list_id_alloc(fr_packet_list_t *pl, request->id = id; request->sockfd = ps->sockfd; - request->src_ipaddr = ps->ipaddr; - request->src_port = ps->port; + request->src_ipaddr = ps->src_ipaddr; + request->src_port = ps->src_port; + + if (pctx) *pctx = ps->ctx; return 1; } @@ -756,20 +807,19 @@ int fr_packet_list_id_free(fr_packet_list_t *pl, RADIUS_PACKET *request) { fr_packet_socket_t *ps; - fr_packet_dst2id_t my_pd, *pd; if (!pl || !request) return 0; ps = fr_socket_find(pl, request->sockfd); if (!ps) return 0; - my_pd.dst_ipaddr = request->dst_ipaddr; - my_pd.dst_port = request->dst_port; - - pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd); - if (!pd) return 0; +#if 0 + if (!ps->id[(request->id >> 3) & 0x1f] & (1 << (request->id & 0x07))) { + exit(1); + } +#endif - pd->id[request->id] &= ~(1 << ps->offset); + ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07)); request->hash = 0; /* invalidate the cached hash */ ps->num_outgoing--; diff --git a/src/lib/tcp.c b/src/lib/tcp.c index 6500a95966d..2f0946b890c 100644 --- a/src/lib/tcp.c +++ b/src/lib/tcp.c @@ -51,8 +51,14 @@ int fr_tcp_socket(fr_ipaddr_t *ipaddr, int port) return sockfd; } + if (fr_nonblock(sockfd) < 0) { + close(sockfd); + return -1; + } + if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) { - return sockfd; /* don't bind it */ + close(sockfd); + return -1; } #ifdef HAVE_STRUCT_SOCKADDR_IN6 @@ -91,28 +97,6 @@ int fr_tcp_socket(fr_ipaddr_t *ipaddr, int port) return -1; } -#if 0 -#ifdef O_NONBLOCK - { - int flags; - - if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0) { - fr_strerror_printf("Failure getting socket flags: %s", - strerror(errno)); - close(sockfd); - return -1; - } - - flags |= O_NONBLOCK; - if( fcntl(sockfd, F_SETFL, flags) < 0) { - fr_strerror_printf("Failure setting socket flags: %s", - strerror(errno)); - close(sockfd); - return -1; - } - } -#endif -#endif return sockfd; } @@ -120,18 +104,22 @@ int fr_tcp_socket(fr_ipaddr_t *ipaddr, int port) /* * Open a socket TO the given IP and port. */ -int fr_tcp_client_socket(fr_ipaddr_t *ipaddr, int port) +int fr_tcp_client_socket(fr_ipaddr_t *src_ipaddr, + fr_ipaddr_t *dst_ipaddr, int dst_port) { int sockfd; struct sockaddr_storage salocal; socklen_t salen; - if ((port < 0) || (port > 65535)) { - fr_strerror_printf("Port %d is out of allowed bounds", port); + if ((dst_port < 0) || (dst_port > 65535)) { + fr_strerror_printf("Port %d is out of allowed bounds", + dst_port); return -1; } - sockfd = socket(ipaddr->af, SOCK_STREAM, 0); + if (!dst_ipaddr) return -1; + + sockfd = socket(dst_ipaddr->af, SOCK_STREAM, 0); if (sockfd < 0) { return sockfd; } @@ -158,8 +146,25 @@ int fr_tcp_client_socket(fr_ipaddr_t *ipaddr, int port) } #endif #endif + /* + * Allow the caller to bind us to a specific source IP. + */ + if (src_ipaddr && (src_ipaddr->af != AF_UNSPEC)) { + if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &salocal, &salen)) { + close(sockfd); + return -1; + } + + if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) { + fr_strerror_printf("Failure binding to IP: %s", + strerror(errno)); + close(sockfd); + return -1; + } + } - if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) { + if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &salocal, &salen)) { + close(sockfd); return -1; } diff --git a/src/main/event.c b/src/main/event.c index 5f4c82aa227..648d25782cd 100644 --- a/src/main/event.c +++ b/src/main/event.c @@ -70,6 +70,7 @@ 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; #endif #define PTHREAD_MUTEX_LOCK if (have_children) pthread_mutex_lock @@ -89,17 +90,29 @@ int thread_pool_addrequest(REQUEST *request, RAD_REQUEST_FUNP fun) } #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]; #else #define remove_from_proxy_hash(foo) #endif @@ -146,19 +159,9 @@ static void remove_from_request_hash(REQUEST *request) #ifdef WITH_TCP request->listener->count--; - - /* - * The TCP socket was closed, but we've got to - * hang around until done. - */ - if ((request->listener->status == RAD_LISTEN_STATUS_FINISH) && - (request->listener->count == 0)) { - listen_free(&request->listener); - } #endif } - #ifdef WITH_PROXY static REQUEST *lookup_in_proxy_hash(RADIUS_PACKET *reply) { @@ -187,21 +190,7 @@ static REQUEST *lookup_in_proxy_hash(RADIUS_PACKET *reply) * 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--; + remove_from_proxy_hash(request); } PTHREAD_MUTEX_UNLOCK(&proxy_mutex); @@ -231,28 +220,13 @@ static void remove_from_proxy_hash(REQUEST *request) } 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 && @@ -260,10 +234,15 @@ 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; @@ -307,67 +286,113 @@ static void ev_request_free(REQUEST **prequest) request_free(prequest); } -#ifdef WITH_PROXY -static int proxy_id_alloc(REQUEST *request, RADIUS_PACKET *packet) +#ifdef WITH_TCP +static int remove_all_requests(void *ctx, void *data) { - int i, proxy, found; - rad_listen_t *proxy_listener; + rad_listen_t *this = ctx; + RADIUS_PACKET **packet_p = data; + REQUEST *request; + + request = fr_packet2myptr(REQUEST, packet, packet_p); + if (request->packet->sockfd != this->fd) return 0; - if (fr_packet_list_id_alloc(proxy_list, packet)) return 1; + 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; - /* - * 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."); + /* + * 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. + */ + case REQUEST_PROXIED: + remove_from_request_hash(request); + request->child_state = REQUEST_DONE; return 0; + + case REQUEST_REJECT_DELAY: + case REQUEST_CLEANUP_DELAY: + case REQUEST_DONE: + ev_request_free(&request); + break; } + + return 0; +} + +#ifdef WITH_PROXY +static int remove_all_proxied_requests(void *ctx, void *data) +{ + rad_listen_t *this = ctx; + RADIUS_PACKET **proxy_p = data; + REQUEST *request; - /* - * Cache it locally. - */ - found = -1; - proxy = proxy_listener->fd; - for (i = 0; i < 32; i++) { + request = fr_packet2myptr(REQUEST, proxy, proxy_p); + if (request->proxy->sockfd != this->fd) return 0; + + 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; + /* - * 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; - } + case REQUEST_PROXIED: + break; + + /* + * Keep it in the cache for duplicate detection. + */ + case REQUEST_REJECT_DELAY: + case REQUEST_CLEANUP_DELAY: + case REQUEST_DONE: + 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."); + + remove_from_proxy_hash(request); + return 0; +} +#endif /* WITH_PROXY */ +#endif /* WITH_TCP */ + + +#ifdef WITH_PROXY +static int proxy_id_alloc(REQUEST *request, RADIUS_PACKET *packet) +{ + void *proxy_listener; + + if (fr_packet_list_id_alloc(proxy_list, packet, + &proxy_listener)) { + request->proxy_listener = proxy_listener; + return 1; + } + + if (!proxy_new_listener(request->home_server, 0)) { + 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."); + + if (!fr_packet_list_id_alloc(proxy_list, packet, + &proxy_listener)) { + RDEBUG2("ERROR: Failed allocating Id for new socket when proxying requests."); return 0; } - /* - * Signal the main thread to add the new FD to the list - * of listening FD's. - */ - radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD); + request->proxy_listener = proxy_listener; return 1; } static int insert_into_proxy_hash(REQUEST *request, int retransmit) { - int i, proxy; char buf[128]; rad_assert(request->proxy != NULL); @@ -375,120 +400,18 @@ static int insert_into_proxy_hash(REQUEST *request, int retransmit) 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; - - /* - * 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... - */ - 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 - */ - - } - - 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)) { + if (!retransmit) { + if (!proxy_id_alloc(request, request->proxy)) { PTHREAD_MUTEX_UNLOCK(&proxy_mutex); - DEBUG2("ERROR: Failed to insert entry into proxy list"); return 0; } + } else { + RADIUS_PACKET packet; - tcp->used++; - tcp->ids[request->proxy->id] = &request->proxy; - - goto done; - } +#ifdef WITH_TCP + rad_assert(request->home_server->proto != IPPROTO_TCP); #endif - if (retransmit) { - RADIUS_PACKET packet; - packet = *request->proxy; if (!proxy_id_alloc(request, &packet)) { @@ -503,49 +426,30 @@ static int insert_into_proxy_hash(REQUEST *request, int retransmit) 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)) { + 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; } - 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", @@ -1160,48 +1064,6 @@ static void post_proxy_fail_handler(REQUEST *request) 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) { @@ -1222,18 +1084,10 @@ static void no_response_to_proxied_request(void *ctx) 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); /* * The default as of 2.1.7 is to allow requests to @@ -1252,9 +1106,35 @@ static void no_response_to_proxied_request(void *ctx) post_proxy_fail_handler(request); } + home = request->home_server; + /* * Don't touch request due to race conditions */ + +#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 + if (home->state == HOME_STATE_IS_DEAD) { rad_assert(home->ev != NULL); /* or it will never wake up */ return; @@ -1936,53 +1816,6 @@ static int process_proxy_reply(REQUEST *request) } #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; @@ -2010,8 +1843,14 @@ 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); + /* + * FIXME: For now, we can only proxy RADIUS packets. + * + * In order to proxy other packets, we need to + * somehow cache the "decode" function. + */ + rcode = rad_decode(request->proxy_reply, request->proxy, + request->home_server->secret); DEBUG_PACKET(request, request->proxy_reply, 0); } else #endif @@ -2959,7 +2798,6 @@ static void received_conflicting_request(REQUEST *request, */ case REQUEST_PROXIED: default: - rad_assert(request->ev != NULL); break; } } @@ -3202,20 +3040,9 @@ int received_request(rad_listen_t *listener, #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; /* @@ -3223,17 +3050,6 @@ REQUEST *received_proxy_response(RADIUS_PACKET *packet) */ 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, @@ -3431,36 +3247,6 @@ static REQUEST *received_proxy_response_p2(RADIUS_PACKET *packet, request->priority = RAD_LISTEN_PROXY; tv_add(&request->when, request->delay); -#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 - - /* - * No outstanding packets. Set up idle timeout - * timer, but only if we didn't already do so - * this second. - */ - 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; - - fr_event_insert(el, tcp_idle_timeout, - request->proxy_listener, - &when, (fr_event_t **) &tcp->ev); - } - - tcp->last_packet = request->proxy_reply->timestamp = now.tv_sec; - } -#endif - /* * Wait a bit will take care of max_request_time */ @@ -3469,133 +3255,364 @@ static REQUEST *received_proxy_response_p2(RADIUS_PACKET *packet, return request; } +#endif /* WITH_PROXY */ + #ifdef WITH_TCP -REQUEST *received_proxy_tcp_response(RADIUS_PACKET *packet, - fr_tcp_radius_t *tcp) +static void tcp_socket_lifetime(void *ctx) { - REQUEST *request; + rad_listen_t *listener = ctx; + char buffer[256]; - /* - * 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); - } + listener->print(listener, buffer, sizeof(buffer)); - /* - * Find request from: tcp->ids[packet->id] = &request->proxy - */ - request = fr_packet2myptr(REQUEST, proxy, tcp->ids[packet->id]); + DEBUG("Reached maximum lifetime on socket %s", buffer); - /* - * TCP sockets don't do re-transmits. Just nuke it. - */ - remove_from_proxy_hash(request); + 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); /* - * FIXME: Update RFC 3539 watchdog timer. + * 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); - return received_proxy_response_p2(packet, request); + listener->status = RAD_LISTEN_STATUS_CLOSED; + event_new_fd(listener); } -#endif /* WITH_TCP */ -#endif /* WITH_PROXY */ +#endif void event_new_fd(rad_listen_t *this) { char buffer[1024]; 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); } + +#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->other_ipaddr, sock->other_port, + this)) { + radlog(L_ERR, "Fatal error adding socket: %s", + fr_strerror()); + exit(1); + + } + + 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; + } +#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!"); exit(1); } + FD_MUTEX_UNLOCK(&fd_mutex); -#ifdef WITH_TCP - if (this->type == RAD_LISTEN_PROXY) { - fr_tcp_radius_t *tcp = fr_listen2tcp(this); + this->status = RAD_LISTEN_STATUS_KNOWN; + return; + } + + /* + * 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_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 = this->count; - if (tcp && (tcp->lifetime > 0)) { - struct timeval when; + /* + * 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); + } - gettimeofday(&when, NULL); - when.tv_sec += tcp->lifetime; + /* + * 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); - fr_event_insert(el, tcp_socket_lifetime, - this, - &when, (fr_event_t **) &tcp->ev); + if (count == 0) { + this->status = RAD_LISTEN_STATUS_FINISH; + goto finish; } } #endif - this->status = RAD_LISTEN_STATUS_KNOWN; - return; - } - - 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 /* - * FIXME: mark ALL requests for this connection - * as MASTER FORCE STOP! we can't reply, so - * there's no point in doing anything + * Re-open the socket, pointing it to /dev/null. + * This means that all writes proceed without + * blocking, and all reads return "no data". * - * ALSO, create packet lists JUST for each proxy + * 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. */ + } + /* + * 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; + rad_assert(this->type != RAD_LISTEN_DETAIL); + +#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 active sockets, so + * that it isn't used when proxying new packets. */ if (this->type == RAD_LISTEN_PROXY) { - int i; - REQUEST *request; - fr_tcp_radius_t *tcp = fr_listen2tcp(this); - - if (!tcp) return; + 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; - 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--; + if (!fr_event_insert(el, + (fr_event_callback_t) event_new_fd, + this, &when, &sock->ev)) { + rad_panic("Failed to insert event"); } - tcp->used = 0; /* just to be safe */ + + return; + } + + /* + * 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); + DEBUG2(" ... closing socket %s", buffer); - fr_event_delete(el, (fr_event_t **) &tcp->ev); + /* + * 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 + /* + * Remove it from the list of sockets to be used + * when proxying. + */ + 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 /* - * 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); } } @@ -3656,15 +3673,50 @@ static void handle_signal_self(int flag) } #endif +#ifdef WITH_PROXY + /* + * Add event handlers for idle timeouts && maximum lifetime.XXX + */ 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; + + if (!fr_event_now(el, &now)) gettimeofday(&now, NULL); + + 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 } #ifndef WITH_SELF_PIPE @@ -3842,7 +3894,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; @@ -3952,17 +4004,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); @@ -3979,65 +4020,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); - - this->status = RAD_LISTEN_STATUS_KNOWN; - } - - mainconfig.listen = head; - return 1; } diff --git a/src/main/listen.c b/src/main/listen.c index b5f45ad8d72..2feb8ed8a58 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -50,6 +50,19 @@ RCSID("$Id$") #endif +void print_packet(RADIUS_PACKET *packet) +{ + char src[256], dst[256]; + + ip_ntoh(&packet->src_ipaddr, src, sizeof(src)); + ip_ntoh(&packet->dst_ipaddr, dst, sizeof(dst)); + + fprintf(stderr, "ID %d: %s %d -> %s %d\n", packet->id, + src, packet->src_port, dst, packet->dst_port); + +} + + /* * We'll use this below. */ @@ -66,35 +79,6 @@ typedef struct rad_listen_master_t { rad_listen_decode_t decode; } rad_listen_master_t; -typedef struct listen_socket_t { - /* - * For normal sockets. - */ - fr_ipaddr_t ipaddr; - int port; - -#ifdef SO_BINDTODEVICE - const char *interface; -#endif - -#ifdef WITH_TCP - int proto; - - int max_connections; - int num_connections; - struct listen_socket_t *parent; - - fr_ipaddr_t src_ipaddr; - int src_port; - RADCLIENT *client; /* for server sockets */ - - fr_tcp_radius_t *tcp; /* for RAD_LISTEN_PROXY */ - home_server *home; - RADIUS_PACKET *packet; /* for reading partial packets */ -#endif - RADCLIENT_LIST *clients; -} listen_socket_t; - static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type); /* @@ -130,7 +114,6 @@ RADCLIENT *client_listener_find(const rad_listen_t *listener, #endif ); if (!client) { - static time_t last_printed = 0; char name[256], buffer[128]; #ifdef WITH_DYNAMIC_CLIENTS @@ -138,11 +121,13 @@ RADCLIENT *client_listener_find(const rad_listen_t *listener, #endif /* - * DoS attack quenching, but only in debug mode. + * DoS attack quenching, but only in daemon mode. * If they're running in debug mode, show them * every packet. */ if (debug_flag == 0) { + static time_t last_printed = 0; + now = time(NULL); if (last_printed == now) return NULL; @@ -407,7 +392,247 @@ static int rad_status_server(REQUEST *request) return 0; } +#ifdef WITH_TCP +static int auth_tcp_recv(rad_listen_t *listener, + RAD_REQUEST_FUNP *pfun, REQUEST **prequest) +{ + int rcode; + RADIUS_PACKET *packet; + RAD_REQUEST_FUNP fun = NULL; + listen_socket_t *sock = listener->data; + RADCLIENT *client = sock->client; + + /* + * Allocate a packet for partial reads. + */ + if (!sock->packet) { + sock->packet = rad_alloc(0); + if (!sock->packet) return 0; + + sock->packet->sockfd = listener->fd; + sock->packet->src_ipaddr = sock->other_ipaddr; + sock->packet->src_port = sock->other_port; + } + + /* + * Grab the packet currently being processed. + */ + packet = sock->packet; + sock->packet = NULL; + + rcode = fr_tcp_read_packet(packet, 0); + + /* + * Still only a partial packet. Put it back, and return, + * so that we'll read more data when it's ready. + */ + if (rcode == 0) { + sock->packet = packet; + return 0; + } + + if (rcode == -1) { /* error reading packet */ + char buffer[256]; + + radlog(L_ERR, "Invalid packet from %s port %d: closing socket", + ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), + packet->src_port); + } + + if (rcode < 0) { /* error or connection reset */ + rad_free(&packet); + + listener->status = RAD_LISTEN_STATUS_REMOVE_FD; + + /* + * Decrement the number of connections. + */ + if (sock->parent->num_connections > 0) { + sock->parent->num_connections--; + } + if (sock->client->num_connections > 0) { + sock->client->num_connections--; + } + + /* + * Tell the event handler that an FD has disappeared. + */ + DEBUG("Client has closed connection"); + event_new_fd(listener); + /* + * Do NOT free the listener here. It's in use by + * a request, and will need to hang around until + * all of the requests are done. + * + * It is instead free'd in remove_from_request_hash() + */ + + return 0; + } + + RAD_STATS_TYPE_INC(listener, total_requests); + + /* + * Some sanity checks, based on the packet code. + */ + switch(packet->code) { + case PW_AUTHENTICATION_REQUEST: + RAD_STATS_CLIENT_INC(listener, client, total_requests); + fun = rad_authenticate; + break; + + case PW_STATUS_SERVER: + if (!mainconfig.status_server) { + rad_free(&packet); + RAD_STATS_TYPE_INC(listener, total_packets_dropped); + RAD_STATS_CLIENT_INC(listener, client, total_packets_dropped); + DEBUG("WARNING: Ignoring Status-Server request due to security configuration"); + return 0; + } + fun = rad_status_server; + break; + + default: + rad_free(&packet); + RAD_STATS_INC(radius_auth_stats.total_unknown_types); + RAD_STATS_CLIENT_INC(listener, client, total_unknown_types); + + DEBUG("Invalid packet code %d sent to authentication port from client %s port %d : IGNORED", + packet->code, client->shortname, packet->src_port); + return 0; + break; + } /* switch over packet types */ + + if (!received_request(listener, packet, prequest, sock->client)) { + RAD_STATS_TYPE_INC(listener, total_packets_dropped); + RAD_STATS_CLIENT_INC(listener, sock->client, total_packets_dropped); + rad_free(&packet); + return 0; + } + + packet->dst_ipaddr = sock->my_ipaddr; + packet->dst_port = sock->my_port; + + *pfun = fun; + return 1; +} + +static int auth_tcp_accept(rad_listen_t *listener, + UNUSED RAD_REQUEST_FUNP *pfun, + UNUSED REQUEST **prequest) +{ + int newfd, src_port; + rad_listen_t *this; + socklen_t salen; + struct sockaddr_storage src; + listen_socket_t *sock; + fr_ipaddr_t src_ipaddr; + RADCLIENT *client; + + salen = sizeof(src); + + DEBUG2(" ... new connection request on TCP socket."); + + newfd = accept(listener->fd, (struct sockaddr *) &src, &salen); + if (newfd < 0) { + /* + * Non-blocking sockets must handle this. + */ + if (errno == EWOULDBLOCK) { + return 0; + } + + DEBUG2(" ... failed to accept connection."); + return -1; + } + + if (!fr_sockaddr2ipaddr(&src, salen, &src_ipaddr, &src_port)) { + DEBUG2(" ... unknown address family."); + return 0; + } + + /* + * Enforce client IP address checks on accept, not on + * every packet. + */ + if ((client = client_listener_find(listener, + &src_ipaddr, src_port)) == NULL) { + close(newfd); + RAD_STATS_TYPE_INC(listener, total_invalid_requests); + return 0; + } + + /* + * Enforce max_connectionsx on client && listen section. + */ + if ((client->max_connections != 0) && + (client->max_connections == client->num_connections)) { + /* + * FIXME: Print client IP/port, and server IP/port. + */ + radlog(L_INFO, "Ignoring new connection due to client max_connections (%d)", client->max_connections); + close(newfd); + return 0; + } + + sock = listener->data; + if ((sock->max_connections != 0) && + (sock->max_connections == sock->num_connections)) { + /* + * FIXME: Print client IP/port, and server IP/port. + */ + radlog(L_INFO, "Ignoring new connection due to socket max_connections"); + close(newfd); + return 0; + } + client->num_connections++; + sock->num_connections++; + + /* + * Add the new listener. + */ + this = listen_alloc(listener->type); + if (!this) return -1; + + /* + * Copy everything, including the pointer to the socket + * information. + */ + sock = this->data; + memcpy(this->data, listener->data, sizeof(*sock)); + memcpy(this, listener, sizeof(*this)); + this->next = NULL; + this->data = sock; /* fix it back */ + + sock->parent = listener->data; + sock->other_ipaddr = src_ipaddr; + sock->other_port = src_port; + sock->client = client; + + this->fd = newfd; + this->status = RAD_LISTEN_STATUS_INIT; + this->recv = auth_tcp_recv; + + /* + * FIXME: set O_NONBLOCK on the accept'd fd. + * See djb's portability rants for details. + */ + + /* + * Tell the event loop that we have a new FD. + * This can be called from a child thread... + */ + event_new_fd(this); + + return 0; +} +#endif + + +/* + * This function is stupid and complicated. + */ static int socket_print(rad_listen_t *this, char *buffer, size_t bufsize) { size_t len; @@ -473,34 +698,36 @@ static int socket_print(rad_listen_t *this, char *buffer, size_t bufsize) #endif #ifdef WITH_TCP - if (sock->proto == IPPROTO_TCP) { + if (this->recv == auth_tcp_accept) { ADDSTRING(" proto tcp"); } +#endif +#ifdef WITH_TCP /* * TCP sockets get printed a little differently, to make * it clear what's going on. */ if (sock->client) { ADDSTRING(" from client ("); - ip_ntoh(&sock->src_ipaddr, buffer, bufsize); + ip_ntoh(&sock->other_ipaddr, buffer, bufsize); FORWARD; ADDSTRING(", "); - snprintf(buffer, bufsize, "%d", sock->src_port); + snprintf(buffer, bufsize, "%d", sock->other_port); FORWARD; ADDSTRING(") -> ("); - if ((sock->ipaddr.af == AF_INET) && - (sock->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) { + if ((sock->my_ipaddr.af == AF_INET) && + (sock->my_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) { strlcpy(buffer, "*", bufsize); } else { - ip_ntoh(&sock->ipaddr, buffer, bufsize); + ip_ntoh(&sock->my_ipaddr, buffer, bufsize); } FORWARD; ADDSTRING(", "); - snprintf(buffer, bufsize, "%d", sock->port); + snprintf(buffer, bufsize, "%d", sock->my_port); FORWARD; if (this->server) { @@ -519,24 +746,24 @@ static int socket_print(rad_listen_t *this, char *buffer, size_t bufsize) if ((sock->proto == IPPROTO_TCP) && (this->type == RAD_LISTEN_PROXY)) { ADDSTRING(" ("); - ip_ntoh(&sock->src_ipaddr, buffer, bufsize); + ip_ntoh(&sock->my_ipaddr, buffer, bufsize); FORWARD; ADDSTRING(", "); - snprintf(buffer, bufsize, "%d", sock->src_port); + snprintf(buffer, bufsize, "%d", sock->my_port); FORWARD; ADDSTRING(") -> home_server ("); - if ((sock->ipaddr.af == AF_INET) && - (sock->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) { + if ((sock->other_ipaddr.af == AF_INET) && + (sock->other_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) { strlcpy(buffer, "*", bufsize); } else { - ip_ntoh(&sock->ipaddr, buffer, bufsize); + ip_ntoh(&sock->other_ipaddr, buffer, bufsize); } FORWARD; ADDSTRING(", "); - snprintf(buffer, bufsize, "%d", sock->port); + snprintf(buffer, bufsize, "%d", sock->other_port); FORWARD; ADDSTRING(")"); @@ -547,16 +774,16 @@ static int socket_print(rad_listen_t *this, char *buffer, size_t bufsize) ADDSTRING(" address "); - if ((sock->ipaddr.af == AF_INET) && - (sock->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) { + if ((sock->my_ipaddr.af == AF_INET) && + (sock->my_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_ANY))) { strlcpy(buffer, "*", bufsize); } else { - ip_ntoh(&sock->ipaddr, buffer, bufsize); + ip_ntoh(&sock->my_ipaddr, buffer, bufsize); } FORWARD; ADDSTRING(" port "); - snprintf(buffer, bufsize, "%d", sock->port); + snprintf(buffer, bufsize, "%d", sock->my_port); FORWARD; if (this->server) { @@ -651,11 +878,22 @@ static int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) return -1; } free(proto); + + /* + * TCP requires a destination IP for sockets. + * UDP doesn't, so it's allowed. + */ + if ((this->type == RAD_LISTEN_PROXY) && + (sock->proto != IPPROTO_UDP)) { + cf_log_err(cf_sectiontoitem(cs), + "Proxy listeners can only listen on proto = udp"); + return -1; + } #endif } - sock->ipaddr = ipaddr; - sock->port = listen_port; + sock->my_ipaddr = ipaddr; + sock->my_port = listen_port; /* * If we can bind to interfaces, do so, @@ -688,8 +926,8 @@ static int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) char buffer[128]; cf_log_err(cf_sectiontoitem(cs), "Error binding to port for %s port %d", - ip_ntoh(&sock->ipaddr, buffer, sizeof(buffer)), - sock->port); + ip_ntoh(&sock->my_ipaddr, buffer, sizeof(buffer)), + sock->my_port); return -1; } @@ -759,8 +997,11 @@ static int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) #ifdef WITH_TCP if (sock->proto == IPPROTO_TCP) { - cf_log_err(cf_sectiontoitem(cs), "TCP is not yet finished"); - return -1; + /* + * Re-write the listener receive function to + * allow us to accept the socket. + */ + this->recv = auth_tcp_accept; } #endif @@ -775,10 +1016,6 @@ static int auth_socket_send(rad_listen_t *listener, REQUEST *request) rad_assert(request->listener == listener); rad_assert(listener->send == auth_socket_send); -#ifdef WITH_TCP - if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; -#endif - return rad_send(request->reply, request->packet, request->client->secret); } @@ -793,10 +1030,6 @@ static int acct_socket_send(rad_listen_t *listener, REQUEST *request) rad_assert(request->listener == listener); rad_assert(listener->send == acct_socket_send); -#ifdef WITH_TCP - if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; -#endif - /* * Accounting reject's are silently dropped. * @@ -818,18 +1051,9 @@ static int acct_socket_send(rad_listen_t *listener, REQUEST *request) */ static int proxy_socket_send(rad_listen_t *listener, REQUEST *request) { - listen_socket_t *sock = listener->data; - rad_assert(request->proxy_listener == listener); rad_assert(listener->send == proxy_socket_send); -#ifdef WITH_TCP - if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; -#endif - - request->proxy->src_ipaddr = sock->ipaddr; - request->proxy->src_port = sock->port; - return rad_send(request->proxy, request->packet, request->home_server->secret); } @@ -1262,7 +1486,6 @@ static int coa_socket_recv(rad_listen_t *listener, int code, src_port; RADIUS_PACKET *packet; RAD_REQUEST_FUNP fun = NULL; - char buffer[128]; RADCLIENT *client; fr_ipaddr_t src_ipaddr; @@ -1280,22 +1503,6 @@ static int coa_socket_recv(rad_listen_t *listener, &src_ipaddr, src_port)) == NULL) { rad_recv_discard(listener->fd); RAD_STATS_TYPE_INC(listener, total_invalid_requests); - - if (debug_flag > 0) { - char name[1024]; - - listener->print(listener, name, sizeof(name)); - - /* - * This is debugging rather than logging, so that - * DoS attacks don't affect us. - */ - DEBUG("Ignoring request to %s from unknown client %s port %d", - name, - inet_ntop(src_ipaddr.af, &src_ipaddr.ipaddr, - buffer, sizeof(buffer)), src_port); - } - return 0; } @@ -1427,11 +1634,13 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener, REQUEST *request; RADIUS_PACKET *packet; RAD_REQUEST_FUNP fun = NULL; + listen_socket_t *sock = listener->data; char buffer[128]; packet = fr_tcp_recv(listener->fd, 0); if (!packet) { - proxy_close_tcp_listener(listener); + listener->status = RAD_LISTEN_STATUS_REMOVE_FD; + event_new_fd(listener); return 0; } @@ -1464,13 +1673,26 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener, return 0; } - request = received_proxy_tcp_response(packet, - fr_listen2tcp(listener)); + packet->src_ipaddr = sock->other_ipaddr; + packet->src_port = sock->other_port; + packet->dst_ipaddr = sock->my_ipaddr; + packet->dst_port = sock->my_port; + + /* + * FIXME: Have it return an indication of packets that + * are OK to ignore (dups, too late), versus ones that + * aren't OK to ignore (unknown response, spoofed, etc.) + * + * Close the socket on bad packets... + */ + request = received_proxy_response(packet); if (!request) { return 0; } rad_assert(fun != NULL); + sock->opened = sock->last_packet = request->timestamp; + *pfun = fun; *prequest = request; @@ -1632,16 +1854,16 @@ static int listen_bind(rad_listen_t *this) * If the port is zero, then it means the appropriate * thing from /etc/services. */ - if (sock->port == 0) { + if (sock->my_port == 0) { struct servent *svp; switch (this->type) { case RAD_LISTEN_AUTH: svp = getservbyname ("radius", proto_for_port); if (svp != NULL) { - sock->port = ntohs(svp->s_port); + sock->my_port = ntohs(svp->s_port); } else { - sock->port = PW_AUTH_UDP_PORT; + sock->my_port = PW_AUTH_UDP_PORT; } break; @@ -1649,28 +1871,28 @@ static int listen_bind(rad_listen_t *this) case RAD_LISTEN_ACCT: svp = getservbyname ("radacct", proto_for_port); if (svp != NULL) { - sock->port = ntohs(svp->s_port); + sock->my_port = ntohs(svp->s_port); } else { - sock->port = PW_ACCT_UDP_PORT; + sock->my_port = PW_ACCT_UDP_PORT; } break; #endif #ifdef WITH_PROXY case RAD_LISTEN_PROXY: - sock->port = 0; + /* leave it at zero */ break; #endif #ifdef WITH_VMPS case RAD_LISTEN_VQP: - sock->port = 1589; + sock->my_port = 1589; break; #endif #ifdef WITH_COA case RAD_LISTEN_COA: - sock->port = PW_COA_UDP_PORT; + sock->my_port = PW_COA_UDP_PORT; break; #endif @@ -1683,7 +1905,7 @@ static int listen_bind(rad_listen_t *this) /* * Copy fr_socket() here, as we may need to bind to a device. */ - this->fd = socket(sock->ipaddr.af, sock_type, 0); + this->fd = socket(sock->my_ipaddr.af, sock_type, 0); if (this->fd < 0) { radlog(L_ERR, "Failed opening socket: %s", strerror(errno)); return -1; @@ -1739,13 +1961,13 @@ static int listen_bind(rad_listen_t *this) /* * Set up sockaddr stuff. */ - if (!fr_ipaddr2sockaddr(&sock->ipaddr, sock->port, &salocal, &salen)) { + if (!fr_ipaddr2sockaddr(&sock->my_ipaddr, sock->my_port, &salocal, &salen)) { close(this->fd); return -1; } #ifdef HAVE_STRUCT_SOCKADDR_IN6 - if (sock->ipaddr.af == AF_INET6) { + if (sock->my_ipaddr.af == AF_INET6) { /* * Listening on '::' does NOT get you IPv4 to * IPv6 mapping. You've got to listen on an IPv4 @@ -1754,7 +1976,7 @@ static int listen_bind(rad_listen_t *this) */ #ifdef IPV6_V6ONLY - if (IN6_IS_ADDR_UNSPECIFIED(&sock->ipaddr.ipaddr.ip6addr)) { + if (IN6_IS_ADDR_UNSPECIFIED(&sock->my_ipaddr.ipaddr.ip6addr)) { int on = 1; setsockopt(this->fd, IPPROTO_IPV6, IPV6_V6ONLY, @@ -1764,8 +1986,7 @@ static int listen_bind(rad_listen_t *this) } #endif /* HAVE_STRUCT_SOCKADDR_IN6 */ - - if (sock->ipaddr.af == AF_INET) { + if (sock->my_ipaddr.af == AF_INET) { UNUSED int flag; #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) @@ -1792,40 +2013,42 @@ static int listen_bind(rad_listen_t *this) /* * May be binding to priviledged ports. */ - fr_suid_up(); - rcode = bind(this->fd, (struct sockaddr *) &salocal, salen); - fr_suid_down(); - if (rcode < 0) { - char buffer[256]; - close(this->fd); - - this->print(this, buffer, sizeof(buffer)); - radlog(L_ERR, "Failed binding to %s: %s\n", - buffer, strerror(errno)); - return -1; - } - - /* - * FreeBSD jail issues. We bind to 0.0.0.0, but the - * kernel instead binds us to a 1.2.3.4. If this - * happens, notice, and remember our real IP. - */ - { - struct sockaddr_storage src; - socklen_t sizeof_src = sizeof(src); - - memset(&src, 0, sizeof_src); - if (getsockname(this->fd, (struct sockaddr *) &src, - &sizeof_src) < 0) { - radlog(L_ERR, "Failed getting socket name: %s", - strerror(errno)); + if (sock->my_port != 0) { + fr_suid_up(); + rcode = bind(this->fd, (struct sockaddr *) &salocal, salen); + fr_suid_down(); + if (rcode < 0) { + char buffer[256]; + close(this->fd); + + this->print(this, buffer, sizeof(buffer)); + radlog(L_ERR, "Failed binding to %s: %s\n", + buffer, strerror(errno)); return -1; } - - if (!fr_sockaddr2ipaddr(&src, sizeof_src, - &sock->ipaddr, &sock->port)) { - radlog(L_ERR, "Socket has unsupported address family"); - return -1; + + /* + * FreeBSD jail issues. We bind to 0.0.0.0, but the + * kernel instead binds us to a 1.2.3.4. If this + * happens, notice, and remember our real IP. + */ + { + struct sockaddr_storage src; + socklen_t sizeof_src = sizeof(src); + + memset(&src, 0, sizeof_src); + if (getsockname(this->fd, (struct sockaddr *) &src, + &sizeof_src) < 0) { + radlog(L_ERR, "Failed getting socket name: %s", + strerror(errno)); + return -1; + } + + if (!fr_sockaddr2ipaddr(&src, sizeof_src, + &sock->my_ipaddr, &sock->my_port)) { + radlog(L_ERR, "Socket has unsupported address family"); + return -1; + } } } @@ -1839,24 +2062,17 @@ static int listen_bind(rad_listen_t *this) } else #endif -#ifdef O_NONBLOCK - { - int flags; - - if ((flags = fcntl(this->fd, F_GETFL, NULL)) < 0) { - radlog(L_ERR, "Failure getting socket flags: %s)\n", - strerror(errno)); - return -1; - } - - flags |= O_NONBLOCK; - if( fcntl(this->fd, F_SETFL, flags) < 0) { - radlog(L_ERR, "Failure setting socket flags: %s)\n", - strerror(errno)); - return -1; - } - } -#endif + if (fr_nonblock(this->fd) < 0) { + close(this->fd); + radlog(L_ERR, "Failed setting non-blocking on socket: %s", + strerror(errno)); + return -1; + } + + /* + * Mostly for proxy sockets. + */ + sock->other_ipaddr.af = sock->my_ipaddr.af; /* * Don't screw up other people. @@ -1934,7 +2150,6 @@ static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type) return this; } - #ifdef WITH_PROXY /* * Externally visible function for creating a new proxy LISTENER. @@ -1942,95 +2157,90 @@ static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type) * Not thread-safe, but all calls to it are protected by the * proxy mutex in event.c */ -rad_listen_t *proxy_new_listener(fr_ipaddr_t *ipaddr, int exists) +int proxy_new_listener(home_server *home, int src_port) { - int last_proxy_port, port; - rad_listen_t *this, *tmp, **last; - listen_socket_t *sock, *old; + rad_listen_t *this; + listen_socket_t *sock; - /* - * Find an existing proxy socket to copy. - */ - last_proxy_port = 0; - old = NULL; - last = &mainconfig.listen; - for (tmp = mainconfig.listen; tmp != NULL; tmp = tmp->next) { - /* - * Not proxy, ignore it. - */ - if (tmp->type != RAD_LISTEN_PROXY) continue; + if (!home) return 0; - sock = tmp->data; + if ((home->max_connections > 0) && + (home->num_connections >= home->max_connections)) { + DEBUG("WARNING: Home server has too many open connections (%d)", + home->max_connections); + return 0; + } - /* - * If we were asked to copy a specific one, do - * so. If we're just finding one that already - * exists, return a pointer to it. Otherwise, - * create ANOTHER one with the same IP address. - */ - if ((ipaddr->af != AF_UNSPEC) && - (fr_ipaddr_cmp(&sock->ipaddr, ipaddr) != 0)) { - if (exists) return tmp; - continue; - } - - if (sock->port > last_proxy_port) { - last_proxy_port = sock->port + 1; - } - if (!old) old = sock; + this = listen_alloc(RAD_LISTEN_PROXY); - last = &(tmp->next); - } + sock = this->data; + sock->other_ipaddr = home->ipaddr; + sock->other_port = home->port; + sock->home = home; + + sock->my_ipaddr = home->src_ipaddr; + sock->my_port = src_port; + +#ifdef WITH_TCP + sock->proto = home->proto; + sock->last_packet = time(NULL); + + if (home->proto == IPPROTO_TCP) { + this->recv = proxy_socket_tcp_recv; - if (!old) { /* - * The socket MUST already exist if we're binding - * to an address while proxying. + * FIXME: connect() is blocking! + * We do this with the proxy mutex locked, which may + * cause large delays! * - * If we're initializing the server, it's OK for the - * socket to NOT exist. + * http://www.developerweb.net/forum/showthread.php?p=13486 */ - if (!exists) return NULL; - - this = listen_alloc(RAD_LISTEN_PROXY); - - sock = this->data; - sock->ipaddr = *ipaddr; + this->fd = fr_tcp_client_socket(&home->src_ipaddr, + &home->ipaddr, home->port); + } else +#endif + this->fd = fr_socket(&home->src_ipaddr, src_port); - } else { - this = listen_alloc(RAD_LISTEN_PROXY); - - sock = this->data; - sock->ipaddr = old->ipaddr; + if (this->fd < 0) { + DEBUG("Failed opening client socket: %s", fr_strerror()); + listen_free(&this); + return 0; } /* - * Keep going until we find an unused port. + * Figure out which port we were bound to. */ - for (port = last_proxy_port; port < 64000; port++) { - int rcode; - - sock->port = port; - - rcode = listen_bind(this); - if (rcode < 0) { + if (sock->my_port == 0) { + struct sockaddr_storage src; + socklen_t sizeof_src = sizeof(src); + + memset(&src, 0, sizeof_src); + if (getsockname(this->fd, (struct sockaddr *) &src, + &sizeof_src) < 0) { + radlog(L_ERR, "Failed getting socket name: %s", + strerror(errno)); listen_free(&this); - return NULL; + return 0; } - /* - * Add the new listener to the list of - * listeners. - */ - *last = this; - return this; + if (!fr_sockaddr2ipaddr(&src, sizeof_src, + &sock->my_ipaddr, &sock->my_port)) { + radlog(L_ERR, "Socket has unsupported address family"); + listen_free(&this); + return 0; + } } - listen_free(&this); - return NULL; + /* + * Tell the event loop that we have a new FD + */ + event_new_fd(this); + + return 1; } #endif + static const FR_NAME_NUMBER listen_compare[] = { #ifdef WITH_STATS { "status", RAD_LISTEN_NONE }, @@ -2090,7 +2300,7 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, const char *server) return NULL; } free(listen_type); - + /* * Allow listen sections in the default config to * refer to a server. @@ -2105,6 +2315,18 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, const char *server) if (rcode < 0) return NULL; } +#ifdef WITH_PROXY + /* + * We were passed a virtual server, so the caller is + * defining a proxy listener inside of a virtual server. + * This isn't allowed right now. + */ + else if (this->type == RAD_LISTEN_PROXY) { + radlog(L_ERR, "Error: listen type \"proxy\" Cannot appear in a virtual server section"); + return NULL; + } +#endif + /* * Set up cross-type data. */ @@ -2195,8 +2417,8 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) sock = this->data; - sock->ipaddr = server_ipaddr; - sock->port = auth_port; + sock->my_ipaddr = server_ipaddr; + sock->my_port = auth_port; sock->clients = clients_parse_section(config); if (!sock->clients) { @@ -2208,11 +2430,11 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) if (listen_bind(this) < 0) { listen_free(head); - radlog(L_ERR, "There appears to be another RADIUS server running on the authentication port %d", sock->port); + radlog(L_ERR, "There appears to be another RADIUS server running on the authentication port %d", sock->my_port); listen_free(&this); return -1; } - auth_port = sock->port; /* may have been updated in listen_bind */ + auth_port = sock->my_port; /* may have been updated in listen_bind */ if (override) { cs = cf_section_sub_find_name2(config, "server", mainconfig.name); @@ -2226,7 +2448,7 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) /* * No acct for vmpsd */ - if (strcmp(progname, "vmpsd") == 0) goto do_proxy; + if (strcmp(progname, "vmpsd") == 0) goto add_sockets; #endif #ifdef WITH_ACCOUNTING @@ -2245,8 +2467,8 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) * The accounting port is always the * authentication port + 1 */ - sock->ipaddr = server_ipaddr; - sock->port = auth_port + 1; + sock->my_ipaddr = server_ipaddr; + sock->my_port = auth_port + 1; sock->clients = clients_parse_section(config); if (!sock->clients) { @@ -2258,7 +2480,7 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) if (listen_bind(this) < 0) { listen_free(&this); listen_free(head); - radlog(L_ERR, "There appears to be another RADIUS server running on the accounting port %d", sock->port); + radlog(L_ERR, "There appears to be another RADIUS server running on the accounting port %d", sock->my_port); return -1; } @@ -2287,7 +2509,7 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) cs = cf_section_sub_find_name2(config, "server", mainconfig.name); - if (!cs) goto do_proxy; + if (!cs) goto add_sockets; /* * Should really abstract this code... @@ -2301,15 +2523,11 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) return -1; } -#ifdef WITH_PROXY - if (this->type == RAD_LISTEN_PROXY) defined_proxy = 1; -#endif - *last = this; last = &(this->next); } /* loop over "listen" directives in server */ - goto do_proxy; + goto add_sockets; } /* @@ -2324,10 +2542,6 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) return -1; } -#ifdef WITH_PROXY - if (this->type == RAD_LISTEN_PROXY) defined_proxy = 1; -#endif - *last = this; last = &(this->next); } @@ -2352,36 +2566,47 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) return -1; } -#ifdef WITH_PROXY - if (this->type == RAD_LISTEN_PROXY) { - radlog(L_ERR, "Error: listen type \"proxy\" Cannot appear in a virtual server section"); - listen_free(head); - return -1; - } -#endif - *last = this; last = &(this->next); } /* loop over "listen" directives in virtual servers */ } /* loop over virtual servers */ +add_sockets: + /* + * Print out which sockets we're listening on, and + * add them to the event list. + */ + for (this = *head; this != NULL; this = this->next) { +#ifdef WITH_PROXY + if (this->type == RAD_LISTEN_PROXY) { + defined_proxy = 1; + } + +#endif + event_new_fd(this); + } + /* * If we're proxying requests, open the proxy FD. * Otherwise, don't do anything. */ - do_proxy: #ifdef WITH_PROXY - if (mainconfig.proxy_requests == TRUE) { - int port = -1; + if ((mainconfig.proxy_requests == TRUE) && + (*head != NULL) && !defined_proxy) { listen_socket_t *sock = NULL; + int port = 0; + home_server home; + + memset(&home, 0, sizeof(home)); /* - * No sockets to receive packets, therefore - * proxying is pointless. + * */ - if (!*head) return -1; - - if (defined_proxy) goto check_home_servers; +#ifdef WITH_TCP + home.proto = IPPROTO_UDP; +#endif + + home.src_ipaddr = server_ipaddr; /* * Find the first authentication port, @@ -2390,71 +2615,48 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head) for (this = *head; this != NULL; this = this->next) { if (this->type == RAD_LISTEN_AUTH) { sock = this->data; - if (server_ipaddr.af == AF_UNSPEC) { - server_ipaddr = sock->ipaddr; + if (home.src_ipaddr.af == AF_UNSPEC) { + home.src_ipaddr = sock->my_ipaddr; } - port = sock->port + 2; /* skip acct port */ + port = sock->my_port + 2; break; } -#ifdef WITH_VMPS - if (this->type == RAD_LISTEN_VQP) { +#ifdef WITH_ACCT + if (this->type == RAD_LISTEN_ACCT) { sock = this->data; - if (server_ipaddr.af == AF_UNSPEC) { - server_ipaddr = sock->ipaddr; + if (home.src_ipaddr.af == AF_UNSPEC) { + home.src_ipaddr = sock->my_ipaddr; } - port = sock->port + 1; + port = sock->my_port + 1; break; } #endif } - if (port < 0) port = 1024 + (fr_rand() & 0x1ff); - /* * Address is still unspecified, use IPv4. */ - if (server_ipaddr.af == AF_UNSPEC) { - server_ipaddr.af = AF_INET; - server_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_ANY); + if (home.src_ipaddr.af == AF_UNSPEC) { + home.src_ipaddr.af = AF_INET; + /* everything else is already set to zero */ } - this = listen_alloc(RAD_LISTEN_PROXY); - sock = this->data; - - /* - * Create the first proxy socket. - */ - sock->ipaddr = server_ipaddr; + home.ipaddr.af = home.src_ipaddr.af; + /* everything else is already set to zero */ - /* - * Try to find a proxy port (value doesn't matter) - */ - for (sock->port = port; - sock->port < 64000; - sock->port++) { - if (listen_bind(this) == 0) { - *last = this; - last = &(this->next); /* just in case */ - break; - } - } - - if (sock->port >= 64000) { + if (!proxy_new_listener(&home, port)) { listen_free(head); - listen_free(&this); - radlog(L_ERR, "Failed to open socket for proxying"); return -1; } - - /* - * Create *additional* proxy listeners, based - * on their src_ipaddr. - */ - check_home_servers: - if (home_server_create_listeners(*head) != 0) return -1; } #endif + /* + * Haven't defined any sockets. Die. + */ + if (!*head) return -1; + + return 0; } @@ -2479,12 +2681,21 @@ void listen_free(rad_listen_t **head) if (master_listen[this->type].free) { master_listen[this->type].free(this); } + #ifdef WITH_TCP - if (this->type == RAD_LISTEN_PROXY) { + if ((this->type == RAD_LISTEN_AUTH) || +#ifdef WITH_ACCT + (this->type == RAD_LISTEN_ACCT) || +#endif +#ifdef WITH_PROXY + (this->type == RAD_LISTEN_PROXY) +#endif + ) { listen_socket_t *sock = this->data; - free(sock->tcp); + rad_free(&sock->packet); } #endif + free(this->data); free(this); @@ -2494,170 +2705,6 @@ void listen_free(rad_listen_t **head) *head = NULL; } -#ifdef WITH_TCP -fr_tcp_radius_t *fr_listen2tcp(rad_listen_t *this) -{ - listen_socket_t *sock; - - if (!this || (this->type != RAD_LISTEN_PROXY) || !this->data) { - return NULL; - } - - sock = this->data; - return sock->tcp; -} - -rad_listen_t *proxy_new_tcp_listener(home_server *home) -{ - int i; - fr_tcp_radius_t *tcp; - struct sockaddr_storage src; - socklen_t sizeof_src = sizeof(src); - rad_listen_t *this; - listen_socket_t *sock; - - if (!home || - ((home->max_connections > 0) && - (home->num_connections >= home->max_connections))) { - DEBUG("WARNING: Home server has too many open connections (%d)", - home->max_connections); - return NULL; - } - - this = NULL; - - /* - * FIXME: Move to RBTrees. - */ - for (i = 0; i < home->max_connections; i++) { - if (home->listeners[i]) continue; - - this = home->listeners[i] = listen_alloc(RAD_LISTEN_PROXY); - if (!this) { - DEBUG("WARNING: Failed allocating memory"); - return NULL; - } - break; - } - - if (!this) { - DEBUG("WARNING: Failed to find a free connection slot"); - return NULL; - } - sock = this->data; - - tcp = sock->tcp = rad_malloc(sizeof(*tcp)); - memset(tcp, 0, sizeof(*tcp)); - - /* - * Initialize th - * - * Open a new socket... - * - * Do stuff... - */ - tcp->dst_ipaddr = home->ipaddr; - tcp->dst_port = home->port; - tcp->lifetime = home->lifetime; - tcp->opened = time(NULL); - - /* - * FIXME: connect() is blocking! - * We do this with the proxy mutex locked, which may - * cause large delays! - * - * http://www.developerweb.net/forum/showthread.php?p=13486 - */ - this->fd = tcp->fd = fr_tcp_client_socket(&tcp->dst_ipaddr, tcp->dst_port); - if (tcp->fd < 0) { - listen_free(&this); - DEBUG("WARNING: Failed opening socket to home server"); - return NULL; - } - memset(&src, 0, sizeof_src); - if (getsockname(tcp->fd, (struct sockaddr *) &src, &sizeof_src) < 0) { - close(tcp->fd); - listen_free(&this); - return NULL; - } - - if (!fr_sockaddr2ipaddr(&src, sizeof_src, - &tcp->src_ipaddr, &tcp->src_port)) { - close(tcp->fd); - listen_free(&this); - return NULL; - } - - /* - * Fill in socket information. - */ - sock->proto = IPPROTO_TCP; - sock->tcp = tcp; - - sock->ipaddr = tcp->src_ipaddr; - sock->port = tcp->src_port; - - /* - * Don't ask. Just don't ask. - */ - sock->src_ipaddr = tcp->dst_ipaddr; - sock->src_port = tcp->dst_port; - sock->home = home; - sock->home->num_connections++; - - this->recv = proxy_socket_tcp_recv; - - /* - * Tell the event handler about the new socket. - * - * It keeps track of "this", so we don't have to insert - * it into the main list of listeners. - */ - event_new_fd(this); - - return this; -} - -void proxy_close_tcp_listener(rad_listen_t *listener) -{ - int i; - listen_socket_t *sock = listener->data; - - /* - * This is the second time around for the socket. Free - * the memory now. - */ - if (listener->status != RAD_LISTEN_STATUS_KNOWN) { - listen_free(&listener); - return; - } - - listener->status = RAD_LISTEN_STATUS_CLOSED; - event_new_fd(listener); - - /* - * Find the home server, and mark this listener as - * no longer being active. - */ - for (i = 0; i < sock->home->max_connections; i++) { - if (sock->home->listeners[i] == listener) { - sock->home->listeners[i] = NULL; - sock->home->num_connections--; - break; - } - } - - /* - * There are still one or more requests using this socket. - * leave it marked as "closed", but don't free it. When the - * last requeast using it is cleaned up, it will be deleted. - */ - if (sock->tcp->used > 0) return; - - listen_free(&listener); -} -#endif - #ifdef WITH_STATS RADCLIENT_LIST *listener_find_client_list(const fr_ipaddr_t *ipaddr, int port) @@ -2672,8 +2719,8 @@ RADCLIENT_LIST *listener_find_client_list(const fr_ipaddr_t *ipaddr, sock = this->data; - if ((sock->port == port) && - (fr_ipaddr_cmp(ipaddr, &sock->ipaddr) == 0)) { + if ((sock->my_port == port) && + (fr_ipaddr_cmp(ipaddr, &sock->my_ipaddr) == 0)) { return sock->clients; } } @@ -2698,21 +2745,21 @@ rad_listen_t *listener_find_byipaddr(const fr_ipaddr_t *ipaddr, int port) sock = this->data; - if ((sock->port == port) && - (fr_ipaddr_cmp(ipaddr, &sock->ipaddr) == 0)) { + if ((sock->my_port == port) && + (fr_ipaddr_cmp(ipaddr, &sock->my_ipaddr) == 0)) { return this; } - if ((sock->port == port) && - ((sock->ipaddr.af == AF_INET) && - (sock->ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY))) { + if ((sock->my_port == port) && + ((sock->my_ipaddr.af == AF_INET) && + (sock->my_ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY))) { return this; } #ifdef HAVE_STRUCT_SOCKADDR_IN6 - if ((sock->port == port) && - (sock->ipaddr.af == AF_INET6) && - (IN6_IS_ADDR_UNSPECIFIED(&sock->ipaddr.ipaddr.ip6addr))) { + if ((sock->my_port == port) && + (sock->my_ipaddr.af == AF_INET6) && + (IN6_IS_ADDR_UNSPECIFIED(&sock->my_ipaddr.ipaddr.ip6addr))) { return this; } #endif diff --git a/src/main/radclient.c b/src/main/radclient.c index 73b8a31b4e2..4df0c926aff 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -504,14 +504,15 @@ static int send_one_packet(radclient_t *radclient) * this packet. */ retry: - radclient->request->src_ipaddr.af = AF_UNSPEC; - rcode = fr_packet_list_id_alloc(pl, radclient->request); + radclient->request->src_ipaddr.af = server_ipaddr.af; + rcode = fr_packet_list_id_alloc(pl, radclient->request, NULL); if (rcode < 0) { int mysockfd; #ifdef WITH_TCP if (proto) { - mysockfd = fr_tcp_client_socket(&server_ipaddr, + mysockfd = fr_tcp_client_socket(NULL, + &server_ipaddr, server_port); } else #endif @@ -520,7 +521,9 @@ static int send_one_packet(radclient_t *radclient) fprintf(stderr, "radclient: Can't open new socket\n"); exit(1); } - if (!fr_packet_list_socket_add(pl, mysockfd)) { + if (!fr_packet_list_socket_add(pl, mysockfd, + &server_ipaddr, + server_port, NULL)) { fprintf(stderr, "radclient: Can't add new socket\n"); exit(1); } @@ -1088,7 +1091,7 @@ int main(int argc, char **argv) } #ifdef WITH_TCP if (proto) { - sockfd = fr_tcp_client_socket(&server_ipaddr, server_port); + sockfd = fr_tcp_client_socket(NULL, &server_ipaddr, server_port); } else #endif sockfd = fr_socket(&client_ipaddr, client_port); @@ -1103,7 +1106,8 @@ int main(int argc, char **argv) exit(1); } - if (!fr_packet_list_socket_add(pl, sockfd)) { + if (!fr_packet_list_socket_add(pl, sockfd, &server_ipaddr, + server_port, NULL)) { fprintf(stderr, "radclient: Out of memory\n"); exit(1); } diff --git a/src/main/realms.c b/src/main/realms.c index 65b8b37fed2..db70d8c3bd3 100644 --- a/src/main/realms.c +++ b/src/main/realms.c @@ -239,29 +239,6 @@ static size_t xlat_server_pool(UNUSED void *instance, REQUEST *request, } #endif -#ifdef WITH_PROXY -#ifndef WITH_TCP -#define home_server_free free -#else -static void home_server_free(void *data) -{ - int i; - home_server *home = data; - - if (home->proto == IPPROTO_TCP) { - for (i = 0; i < home->max_connections; i++) { - if (!home->listeners[i]) continue; - - listen_free(&home->listeners[i]); - } - } - - free(home); -} - -#endif /* WITH_TCP */ -#endif /* WITH_PROXY */ - void realms_free(void) { #ifdef WITH_PROXY @@ -302,8 +279,7 @@ void realms_free(void) #ifdef WITH_PROXY -#ifdef WITH_TCP -static CONF_PARSER tcp_config[] = { +static CONF_PARSER limit_config[] = { { "max_connections", PW_TYPE_INTEGER, offsetof(home_server, max_connections), NULL, "16" }, @@ -318,7 +294,6 @@ static CONF_PARSER tcp_config[] = { { NULL, -1, 0, NULL, NULL } /* end the list */ }; -#endif static struct in_addr hs_ip4addr; static struct in6_addr hs_ip6addr; @@ -405,9 +380,7 @@ static CONF_PARSER home_server_config[] = { offsetof(home_server, coa_mrd), 0, Stringify(30) }, #endif -#ifdef WITH_TCP - { "limit", PW_TYPE_SUBSECTION, 0, NULL, (const void *) tcp_config }, -#endif + { "limit", PW_TYPE_SUBSECTION, 0, NULL, (const void *) limit_config }, { NULL, -1, 0, NULL, NULL } /* end the list */ }; @@ -419,10 +392,6 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) home_server *home; int dual = FALSE; CONF_PAIR *cp; -#ifdef WITH_TCP - CONF_SECTION *limit; - int max_connections; -#endif free(hs_virtual_server); /* used only for printing during parsing */ hs_virtual_server = NULL; @@ -441,41 +410,8 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) return 0; } -#ifdef WITH_TCP - max_connections = 16; - cp = NULL; - limit = cf_subsection_find_next(cs, NULL, "limit"); - if (limit) cp = cf_pair_find(limit, "max_connections"); - if (cp) { - const char *value = cf_pair_value(cp); - - if (value) max_connections = atoi(value); - - if ((max_connections > 1024) || - (max_connections == 0)) max_connections = 1024; - - /* - * Set max_connections to 1 for non-TCP sockets. - */ - cp = cf_pair_find(cs, "proto"); - if (!cp || - (((value = cf_pair_value(cp)) != NULL) && - (strcmp(value, "tcp") != 0))) { - max_connections = 1; - } - } -#endif - - home = rad_malloc(sizeof(*home) -#ifdef WITH_TCP - + (sizeof(home->listeners[0]) * max_connections) -#endif - ); - memset(home, 0, sizeof(*home) -#ifdef WITH_TCP - + (sizeof(home->listeners[0]) * max_connections) -#endif - ); + home = rad_malloc(sizeof(*home)); + memset(home, 0, sizeof(*home)); home->name = name2; home->cs = cs; @@ -658,42 +594,39 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) goto error; } } - - home->listeners[0] = NULL; - - /* - * We need SOME value. Set it high enough that no one - * will run into it. - */ - - if (home->proto == IPPROTO_TCP) { - home->max_connections = max_connections; - - /* - * FIXME: Parse "min_connections", too, and set - * those up. - */ - memset(home->listeners, 0, - home->max_connections * sizeof(home->listeners[0])); - } #endif - if ((home->ipaddr.af != AF_UNSPEC) && /* could be virtual server */ + if (!home->server && rbtree_finddata(home_servers_byaddr, home)) { cf_log_err(cf_sectiontoitem(cs), "Duplicate home server"); goto error; } /* - * Look up the name using the *same* address family as - * for the home server. + * If the home is a virtual server, don't look up source IP. */ - if (hs_srcipaddr && (home->ipaddr.af != AF_UNSPEC)) { - if (ip_hton(hs_srcipaddr, home->ipaddr.af, &home->src_ipaddr) < 0) { - cf_log_err(cf_sectiontoitem(cs), "Failed parsing src_ipaddr"); - goto error; + if (!home->server) { + rad_assert(home->ipaddr.af != AF_UNSPEC); + + /* + * Otherwise look up the source IP using the same + * address family as the destination IP. + */ + if (hs_srcipaddr) { + if (ip_hton(hs_srcipaddr, home->ipaddr.af, &home->src_ipaddr) < 0) { + cf_log_err(cf_sectiontoitem(cs), "Failed parsing src_ipaddr"); + goto error; + } + + } else { + /* + * Source isn't specified: Source is + * the correct address family, but all zeros. + */ + home->src_ipaddr.af = home->ipaddr.af; } } + free(hs_srcipaddr); hs_srcipaddr = NULL; @@ -704,7 +637,7 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) goto error; } - if ((home->ipaddr.af != AF_UNSPEC) && /* could be virtual server */ + if (!home->server && !rbtree_insert(home_servers_byaddr, home)) { rbtree_deletebydata(home_servers_byname, home); cf_log_err(cf_sectiontoitem(cs), @@ -766,14 +699,21 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) if (home->coa_mrd > 60 ) home->coa_mrd = 60; #endif + if (home->max_connections > 1024) home->max_connections = 1024; + #ifdef WITH_TCP + /* + * UDP sockets can't be connection limited. + */ + if (home->proto != IPPROTO_TCP) home->max_connections = 0; +#endif + if ((home->idle_timeout > 0) && (home->idle_timeout < 5)) home->idle_timeout = 5; if ((home->lifetime > 0) && (home->lifetime < 5)) home->lifetime = 5; if ((home->lifetime > 0) && (home->idle_timeout > home->lifetime)) home->idle_timeout = 0; -#endif if (dual) { home_server *home2 = rad_malloc(sizeof(*home2)); @@ -793,7 +733,7 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) return 0; } - if ((home->ipaddr.af != AF_UNSPEC) && + if (!home->server && !rbtree_insert(home_servers_byaddr, home2)) { rbtree_deletebydata(home_servers_byname, home2); cf_log_err(cf_sectiontoitem(cs), @@ -803,15 +743,11 @@ static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type) return 0; } -#ifdef WITH_TCP - home2->proto = home->proto; -#endif - #ifdef WITH_STATS home2->number = home_server_max_number++; if (!rbtree_insert(home_servers_bynumber, home2)) { rbtree_deletebydata(home_servers_byname, home2); - if (home2->ipaddr.af != AF_UNSPEC) { + if (!home2->server) { rbtree_deletebydata(home_servers_byname, home2); } cf_log_err(cf_sectiontoitem(cs), @@ -1233,6 +1169,7 @@ static int old_server_add(realm_config_t *rc, CONF_SECTION *cs, free(q); return 0; } + home->src_ipaddr.af = home->ipaddr.af; } else { home->ipaddr.af = AF_UNSPEC; home->server = server; @@ -1786,7 +1723,7 @@ int realms_init(CONF_SECTION *config) } #ifdef WITH_PROXY - home_servers_byaddr = rbtree_create(home_server_addr_cmp, home_server_free, 0); + home_servers_byaddr = rbtree_create(home_server_addr_cmp, free, 0); if (!home_servers_byaddr) { realms_free(); return 0; @@ -2202,6 +2139,8 @@ home_server *home_server_ldb(const char *realmname, /* * Update the various fields as appropriate. */ + request->proxy->src_ipaddr = found->src_ipaddr; + request->proxy->src_port = 0; request->proxy->dst_ipaddr = found->ipaddr; request->proxy->dst_port = found->port; request->home_server = found; @@ -2326,54 +2265,3 @@ home_pool_t *home_pool_byname(const char *name, int type) } #endif - -#ifdef WITH_PROXY -static int home_server_create_callback(void *ctx, void *data) -{ - rad_listen_t *head = ctx; - home_server *home = data; - rad_listen_t *this; - - /* - * If there WAS a src address defined, ensure that a - * proxy listener has been defined. - */ - if (home->src_ipaddr.af != AF_UNSPEC) { - this = proxy_new_listener(&home->src_ipaddr, TRUE); - - /* - * Failed to create it: Die - */ - if (!this) return 1; - - this->next = head->next; - head->next = this; - } - - return 0; -} - -/* - * Taking a void* here solves some header issues. - */ -int home_server_create_listeners(void *ctx) -{ - rad_listen_t *head = ctx; - - if (!home_servers_byaddr) return 0; - - rad_assert(head != NULL); - - /* - * Add the listeners to the TAIL of the list. - */ - while (head->next) head = head->next; - - if (rbtree_walk(home_servers_byaddr, InOrder, - home_server_create_callback, head) != 0) { - return -1; - } - - return 0; -} -#endif