/* * Compare two handlers. */ static int eap_handler_cmp(const void *a, const void *b) { int rcode; const eap_handler_t *one = a; const eap_handler_t *two = b; if (one->eap_id < two->eap_id) return -1; if (one->eap_id > two->eap_id) return +1; rcode = memcmp(one->state, two->state, sizeof(one->state)); if (rcode != 0) return rcode; /* * As of 2.1.8, we don't key off of source IP. This * a NAS to send packets load-balanced (or fail-over) * across multiple intermediate proxies, and still have * EAP work. */ if (fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr) != 0) { DEBUGW("EAP packets are arriving from two different upstream " "servers. Has there been a proxy fail-over?"); } return 0; }
static int data_cmp(const void *one, const void *two) { rlm_stats_data_t const *a = one; rlm_stats_data_t const *b = two; return fr_ipaddr_cmp(&a->ipaddr, &b->ipaddr); }
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); }
/* * Compare two handlers. */ static int eap_handler_cmp(const void *a, const void *b) { int rcode; const EAP_HANDLER *one = a; const EAP_HANDLER *two = b; rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr); if (rcode != 0) return rcode; return memcmp(one->state, two->state, sizeof(one->state)); }
/* * See if two packets are identical. * * Note that we do NOT compare the authentication vectors. * That's because if the authentication vector is different, * it means that the NAS has given up on the earlier request. */ int fr_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b) { int rcode; if (a->sockfd < b->sockfd) return -1; if (a->sockfd > b->sockfd) return +1; if (a->id < b->id) return -1; if (a->id > b->id) return +1; if (a->src_port < b->src_port) return -1; if (a->src_port > b->src_port) return +1; if (a->dst_port < b->dst_port) return -1; if (a->dst_port > b->dst_port) return +1; rcode = fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr); if (rcode != 0) return rcode; return fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr); }
/* * See if two packets are identical. * * Note that we do NOT compare the authentication vectors. * That's because if the authentication vector is different, * it means that the NAS has given up on the earlier request. */ int fr_packet_cmp(RADIUS_PACKET const *a, RADIUS_PACKET const *b) { int rcode; rcode = a->id - b->id; if (rcode != 0) return rcode; rcode = (int) a->src_port - (int) b->src_port; if (rcode != 0) return rcode; rcode = (int) a->dst_port - (int) b->dst_port; if (rcode != 0) return rcode; rcode = a->sockfd - b->sockfd; if (rcode != 0) return rcode; rcode = fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr); if (rcode != 0) return rcode; return fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr); }
/* * Callback for comparing two clients. */ static int client_ipaddr_cmp(void const *one, void const *two) { RADCLIENT const *a = one; RADCLIENT const *b = two; #ifndef WITH_TCP return fr_ipaddr_cmp(&a->ipaddr, &b->ipaddr); #else int rcode; rcode = fr_ipaddr_cmp(&a->ipaddr, &b->ipaddr); if (rcode != 0) return rcode; /* * Wildcard match */ if ((a->proto == IPPROTO_IP) || (b->proto == IPPROTO_IP)) return 0; return (a->proto - b->proto); #endif }
/* comparison function to find session in the tree */ static int securid_session_cmp(const void *a, const void *b) { int rcode; const SECURID_SESSION *one = a; const SECURID_SESSION *two = b; rad_assert(one != NULL); rad_assert(two != NULL); rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr); if (rcode != 0) return rcode; return memcmp(one->state, two->state, sizeof(one->state)); }
/* * Internal function to cut down on duplicated code. * * Returns -1 on failure, 0 on no failure. returnrealm * is NULL on don't proxy, realm otherwise. */ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm) { char *namebuf; char *username; char const *realmname = NULL; char *ptr; VALUE_PAIR *vp; REALM *realm; struct realm_config_t *inst = instance; /* initiate returnrealm */ *returnrealm = NULL; /* * If the request has a proxy entry, then it's a proxy * reply, and we're walking through the module list again. * * In that case, don't bother trying to proxy the request * again. * * Also, if there's no User-Name attribute, we can't * proxy it, either. */ if ((!request->username) #ifdef WITH_PROXY || (request->proxy != NULL) #endif ) { RDEBUG2("Proxy reply, or no User-Name. Ignoring."); return RLM_MODULE_OK; } /* * Check for 'Realm' attribute. If it exists, then we've proxied * it already ( via another rlm_realm instance ) and should return. */ if (pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL ) { RDEBUG2("Request already proxied. Ignoring."); return RLM_MODULE_OK; } /* * We will be modifing this later, so we want our own copy * of it. */ namebuf = talloc_strdup(request, request->username->vp_strvalue); username = namebuf; switch(inst->format) { case REALM_FORMAT_SUFFIX: /* DEBUG2(" rlm_realm: Checking for suffix after \"%c\"", inst->delim[0]); */ ptr = strrchr(username, inst->delim[0]); if (ptr) { *ptr = '\0'; realmname = ptr + 1; } break; case REALM_FORMAT_PREFIX: /* DEBUG2(" rlm_realm: Checking for prefix before \"%c\"", inst->delim[0]); */ ptr = strchr(username, inst->delim[0]); if (ptr) { *ptr = '\0'; ptr++; realmname = username; username = ptr; } break; default: realmname = NULL; break; } /* * Print out excruciatingly descriptive debugging messages * for the people who find it too difficult to think about * what's going on. */ if (realmname) { RDEBUG2("Looking up realm \"%s\" for User-Name = \"%s\"", realmname, request->username->vp_strvalue); } else { if (inst->ignore_null ) { RDEBUG2("No '%c' in User-Name = \"%s\", skipping NULL due to config.", inst->delim[0], request->username->vp_strvalue); talloc_free(namebuf); return RLM_MODULE_NOOP; } RDEBUG2("No '%c' in User-Name = \"%s\", looking up realm NULL", inst->delim[0], request->username->vp_strvalue); } /* * Allow DEFAULT realms unless told not to. */ realm = realm_find(realmname); if (!realm) { RDEBUG2("No such realm \"%s\"", (!realmname) ? "NULL" : realmname); talloc_free(namebuf); return RLM_MODULE_NOOP; } if( inst->ignore_default && (strcmp(realm->name, "DEFAULT")) == 0) { RDEBUG2("Found DEFAULT, but skipping due to config."); talloc_free(namebuf); return RLM_MODULE_NOOP; } RDEBUG2("Found realm \"%s\"", realm->name); /* * If we've been told to strip the realm off, then do so. */ if (realm->striprealm) { /* * Create the Stripped-User-Name attribute, if it * doesn't exist. * */ if (request->username->da->attr != PW_STRIPPED_USER_NAME) { vp = radius_paircreate(request, &request->packet->vps, PW_STRIPPED_USER_NAME, 0); RDEBUG2("Adding Stripped-User-Name = \"%s\"", username); } else { vp = request->username; RDEBUG2("Setting Stripped-User-Name = \"%s\"", username); } pairstrcpy(vp, username); request->username = vp; } /* * Add the realm name to the request. * If the realm is a regex, the use the realm as entered * by the user. Otherwise, use the configured realm name, * as realm name comparison is case insensitive. We want * to use the configured name, rather than what the user * entered. */ if (realm->name[0] != '~') realmname = realm->name; pairmake_packet("Realm", realmname, T_OP_EQ); RDEBUG2("Adding Realm = \"%s\"", realmname); talloc_free(namebuf); realmname = username = NULL; /* * Figure out what to do with the request. */ switch (request->packet->code) { default: RDEBUG2("Unknown packet code %d\n", request->packet->code); return RLM_MODULE_OK; /* don't do anything */ /* * Perhaps accounting proxying was turned off. */ case PW_ACCOUNTING_REQUEST: if (!realm->acct_pool) { RDEBUG2("Accounting realm is LOCAL."); return RLM_MODULE_OK; } break; /* * Perhaps authentication proxying was turned off. */ case PW_AUTHENTICATION_REQUEST: if (!realm->auth_pool) { RDEBUG2("Authentication realm is LOCAL."); return RLM_MODULE_OK; } break; } #ifdef WITH_PROXY RDEBUG2("Proxying request from user %s to realm %s", request->username->vp_strvalue, realm->name); /* * Skip additional checks if it's not an accounting * request. */ if (request->packet->code != PW_ACCOUNTING_REQUEST) { *returnrealm = realm; return RLM_MODULE_UPDATED; } /* * FIXME: Each server should have a unique server key, * and put it in the accounting packet. Every server * should know about the keys, and NOT proxy requests to * a server with key X if the packet already contains key * X. */ /* * If this request has arrived from another freeradius server * that has already proxied the request, we don't need to do * it again. */ vp = pairfind(request->packet->vps, PW_FREERADIUS_PROXIED_TO, 0, TAG_ANY); if (vp && (request->packet->src_ipaddr.af == AF_INET)) { int i; fr_ipaddr_t my_ipaddr; my_ipaddr.af = AF_INET; my_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; /* * Loop over the home accounting servers for this * realm. If one of them has the same IP as the * FreeRADIUS-Proxied-To attribute, then the * packet has already been sent there. Don't * send it there again. */ for (i = 0; i < realm->acct_pool->num_home_servers; i++) { if (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &my_ipaddr) == 0) { RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To"); return RLM_MODULE_OK; } } /* * See detail_recv() in src/main/listen.c for the * additional checks. */ #ifdef WITH_DETAIL } else if ((request->listener->type == RAD_LISTEN_DETAIL) && !fr_inaddr_any(&request->packet->src_ipaddr)) { int i; /* * Loop over the home accounting servers for this * realm. If one of them has the same IP as the * FreeRADIUS-Proxied-To attribute, then the * packet has already been sent there. Don't * send it there again. */ for (i = 0; i < realm->acct_pool->num_home_servers; i++) { if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &request->packet->src_ipaddr) == 0) && (realm->acct_pool->servers[i]->port == request->packet->src_port)) { RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm"); return RLM_MODULE_OK; } } #endif /* WITH_DETAIL */ } #endif /* WITH_PROXY */ /* * We got this far, which means we have a realm, set returnrealm */ *returnrealm = realm; return RLM_MODULE_UPDATED; }
/** Add a client to a RADCLIENT_LIST * * @param clients list to add client to, may be NULL if global client list is being used. * @param client to add. * @return true on success, false on failure. */ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) { RADCLIENT *old; char buffer[INET6_ADDRSTRLEN + 3]; if (!client) return false; /* * Hack to fixup wildcard clients * * If the IP is all zeros, with a 32 or 128 bit netmask * assume the user meant to configure 0.0.0.0/0 instead * of 0.0.0.0/32 - which would require the src IP of * the client to be all zeros. */ if (fr_inaddr_any(&client->ipaddr) == 1) switch (client->ipaddr.af) { case AF_INET: if (client->ipaddr.prefix == 32) client->ipaddr.prefix = 0; break; case AF_INET6: if (client->ipaddr.prefix == 128) client->ipaddr.prefix = 0; break; default: rad_assert(0); } fr_ntop(buffer, sizeof(buffer), &client->ipaddr); DEBUG3("Adding client %s (%s) to prefix tree %i", buffer, client->longname, client->ipaddr.prefix); /* * If the client also defines a server, do that now. */ if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; /* * If "clients" is NULL, it means add to the global list, * unless we're trying to add it to a virtual server... */ if (!clients) { if (client->server != NULL) { CONF_SECTION *cs; cs = cf_section_sub_find_name2(main_config.config, "server", client->server); if (!cs) { ERROR("Failed to find virtual server %s", client->server); return false; } /* * If the client list already exists, use that. * Otherwise, create a new client list. */ clients = cf_data_find(cs, "clients"); if (!clients) { clients = client_list_init(cs); if (!clients) { ERROR("Out of memory"); return false; } if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { ERROR("Failed to associate clients with virtual server %s", client->server); client_list_free(clients); return false; } } } else { /* * Initialize the global list, if not done already. */ if (!root_clients) { root_clients = client_list_init(NULL); if (!root_clients) return false; } clients = root_clients; } } /* * Create a tree for it. */ if (!clients->trees[client->ipaddr.prefix]) { clients->trees[client->ipaddr.prefix] = rbtree_create(clients, client_ipaddr_cmp, NULL, 0); if (!clients->trees[client->ipaddr.prefix]) { return false; } } #define namecmp(a) ((!old->a && !client->a) || (old->a && client->a && (strcmp(old->a, client->a) == 0))) /* * Cannot insert the same client twice. */ old = rbtree_finddata(clients->trees[client->ipaddr.prefix], client); if (old) { /* * If it's a complete duplicate, then free the new * one, and return "OK". */ if ((fr_ipaddr_cmp(&old->ipaddr, &client->ipaddr) == 0) && (old->ipaddr.prefix == client->ipaddr.prefix) && namecmp(longname) && namecmp(secret) && namecmp(shortname) && namecmp(nas_type) && namecmp(login) && namecmp(password) && namecmp(server) && #ifdef WITH_DYNAMIC_CLIENTS (old->lifetime == client->lifetime) && namecmp(client_server) && #endif #ifdef WITH_COA namecmp(coa_name) && (old->coa_server == client->coa_server) && (old->coa_pool == client->coa_pool) && #endif (old->message_authenticator == client->message_authenticator)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return true; } ERROR("Failed to add duplicate client %s", client->shortname); return false; } #undef namecmp /* * Other error adding client: likely is fatal. */ if (!rbtree_insert(clients->trees[client->ipaddr.prefix], client)) { return false; } #ifdef WITH_STATS if (!tree_num) { tree_num = rbtree_create(clients, client_num_cmp, NULL, 0); } #ifdef WITH_DYNAMIC_CLIENTS /* * More catching of clients added by rlm_sql. * * The sql modules sets the dynamic flag BEFORE calling * us. The client_afrom_request() function sets it AFTER * calling us. */ if (client->dynamic && (client->lifetime == 0)) { RADCLIENT *network; /* * If there IS an enclosing network, * inherit the lifetime from it. */ network = client_find(clients, &client->ipaddr, client->proto); if (network) { client->lifetime = network->lifetime; } } #endif client->number = tree_num_max; tree_num_max++; if (tree_num) rbtree_insert(tree_num, client); #endif if (client->ipaddr.prefix < clients->min_prefix) { clients->min_prefix = client->ipaddr.prefix; } (void) talloc_steal(clients, client); /* reparent it */ return true; }
RADCLIENT *client_from_request(RADCLIENT_LIST *clients, REQUEST *request) { int i, *pi; char **p; RADCLIENT *c; char buffer[128]; if (!clients || !request) return NULL; c = talloc_zero(clients, RADCLIENT); c->cs = request->client->cs; c->ipaddr.af = AF_UNSPEC; c->src_ipaddr.af = AF_UNSPEC; for (i = 0; dynamic_config[i].name != NULL; i++) { DICT_ATTR const *da; VALUE_PAIR *vp; da = dict_attrbyname(dynamic_config[i].name); if (!da) { DEBUG("- Cannot add client %s: attribute \"%s\"is not in the dictionary", ip_ntoh(&request->packet->src_ipaddr, buffer, sizeof(buffer)), dynamic_config[i].name); error: client_free(c); return NULL; } vp = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (!vp) { /* * Not required. Skip it. */ if (!dynamic_config[i].dflt) continue; DEBUG("- Cannot add client %s: Required attribute \"%s\" is missing.", ip_ntoh(&request->packet->src_ipaddr, buffer, sizeof(buffer)), dynamic_config[i].name); goto error; } switch (dynamic_config[i].type) { case PW_TYPE_IPV4_ADDR: if (da->attr == PW_FREERADIUS_CLIENT_IP_ADDRESS) { c->ipaddr.af = AF_INET; c->ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; c->ipaddr.prefix = 32; } else if (da->attr == PW_FREERADIUS_CLIENT_SRC_IP_ADDRESS) { #ifdef WITH_UDPFROMTO c->src_ipaddr.af = AF_INET; c->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; #else WARN("Server not built with udpfromto, ignoring FreeRADIUS-Client-Src-IP-Address"); #endif } break; case PW_TYPE_IPV6_ADDR: if (da->attr == PW_FREERADIUS_CLIENT_IPV6_ADDRESS) { c->ipaddr.af = AF_INET6; c->ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; c->ipaddr.prefix = 128; } else if (da->attr == PW_FREERADIUS_CLIENT_SRC_IPV6_ADDRESS) { #ifdef WITH_UDPFROMTO c->src_ipaddr.af = AF_INET6; c->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; #else WARN("Server not built with udpfromto, ignoring FreeRADIUS-Client-Src-IPv6-Address"); #endif } break; case PW_TYPE_IPV4_PREFIX: if (da->attr == PW_FREERADIUS_CLIENT_IP_PREFIX) { c->ipaddr.af = AF_INET; memcpy(&c->ipaddr.ipaddr.ip4addr.s_addr, &(vp->vp_ipv4prefix[2]), sizeof(c->ipaddr.ipaddr.ip4addr.s_addr)); fr_ipaddr_mask(&c->ipaddr, (vp->vp_ipv4prefix[1] & 0x3f)); } break; case PW_TYPE_IPV6_PREFIX: if (da->attr == PW_FREERADIUS_CLIENT_IPV6_PREFIX) { c->ipaddr.af = AF_INET6; memcpy(&c->ipaddr.ipaddr.ip6addr, &(vp->vp_ipv6prefix[2]), sizeof(c->ipaddr.ipaddr.ip6addr)); fr_ipaddr_mask(&c->ipaddr, vp->vp_ipv6prefix[1]); } break; case PW_TYPE_STRING: p = (char **) ((char *) c + dynamic_config[i].offset); if (*p) talloc_free(*p); if (vp->vp_strvalue[0]) { *p = talloc_typed_strdup(c->cs, vp->vp_strvalue); } else { *p = NULL; } break; case PW_TYPE_BOOLEAN: pi = (int *) ((bool *) ((char *) c + dynamic_config[i].offset)); *pi = vp->vp_integer; break; default: goto error; } } if (c->ipaddr.af == AF_UNSPEC) { DEBUG("- Cannot add client %s: No IP address was specified.", ip_ntoh(&request->packet->src_ipaddr, buffer, sizeof(buffer))); goto error; } { fr_ipaddr_t addr; /* * Need to apply the same mask as we set for the client * else clients created with FreeRADIUS-Client-IPv6-Prefix * or FreeRADIUS-Client-IPv4-Prefix will fail this check. */ addr = request->packet->src_ipaddr; fr_ipaddr_mask(&addr, c->ipaddr.prefix); if (fr_ipaddr_cmp(&addr, &c->ipaddr) != 0) { char buf2[128]; DEBUG("- Cannot add client %s: IP address %s do not match", ip_ntoh(&request->packet->src_ipaddr, buffer, sizeof(buffer)), ip_ntoh(&c->ipaddr, buf2, sizeof(buf2))); goto error; } } if (!c->secret || !*c->secret) { DEBUG("- Cannot add client %s: No secret was specified.", ip_ntoh(&request->packet->src_ipaddr, buffer, sizeof(buffer))); goto error; } if (!client_validate(clients, request->client, c)) { return NULL; } if ((c->src_ipaddr.af != AF_UNSPEC) && (c->src_ipaddr.af != c->ipaddr.af)) { DEBUG("- Cannot add client %s: Client IP and src address are different IP version.", ip_ntoh(&request->packet->src_ipaddr, buffer, sizeof(buffer))); goto error; } return c; }
/* * Add a client to the tree. */ int client_add(RADCLIENT_LIST *clients, RADCLIENT *client) { RADCLIENT *old; char buffer[INET6_ADDRSTRLEN + 3]; if (!client) { return 0; } /* * Hack to fixup wildcard clients */ if (is_wildcard(&client->ipaddr)) client->ipaddr.prefix = 0; fr_ntop(buffer, sizeof(buffer), &client->ipaddr); DEBUG3("Adding client %s (%s) to prefix tree %i", buffer, client->longname, client->ipaddr.prefix); /* * If "clients" is NULL, it means add to the global list. */ if (!clients) { /* * Initialize it, if not done already. */ if (!root_clients) { root_clients = clients_init(NULL); if (!root_clients) return 0; } clients = root_clients; } /* * Create a tree for it. */ if (!clients->trees[client->ipaddr.prefix]) { clients->trees[client->ipaddr.prefix] = rbtree_create(client_ipaddr_cmp, NULL, 0); if (!clients->trees[client->ipaddr.prefix]) { return 0; } } #define namecmp(a) ((!old->a && !client->a) || (old->a && client->a && (strcmp(old->a, client->a) == 0))) /* * Cannot insert the same client twice. */ old = rbtree_finddata(clients->trees[client->ipaddr.prefix], client); if (old) { /* * If it's a complete duplicate, then free the new * one, and return "OK". */ if ((fr_ipaddr_cmp(&old->ipaddr, &client->ipaddr) == 0) && (old->ipaddr.prefix == client->ipaddr.prefix) && namecmp(longname) && namecmp(secret) && namecmp(shortname) && namecmp(nas_type) && namecmp(login) && namecmp(password) && namecmp(server) && #ifdef WITH_DYNAMIC_CLIENTS (old->lifetime == client->lifetime) && namecmp(client_server) && #endif #ifdef WITH_COA namecmp(coa_name) && (old->coa_server == client->coa_server) && (old->coa_pool == client->coa_pool) && #endif (old->message_authenticator == client->message_authenticator)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return 1; } ERROR("Failed to add duplicate client %s", client->shortname); return 0; } #undef namecmp /* * Other error adding client: likely is fatal. */ if (!rbtree_insert(clients->trees[client->ipaddr.prefix], client)) { return 0; } #ifdef WITH_STATS if (!tree_num) { tree_num = rbtree_create(client_num_cmp, NULL, 0); } #ifdef WITH_DYNAMIC_CLIENTS /* * More catching of clients added by rlm_sql. * * The sql modules sets the dynamic flag BEFORE calling * us. The client_from_request() function sets it AFTER * calling us. */ if (client->dynamic && (client->lifetime == 0)) { RADCLIENT *network; /* * If there IS an enclosing network, * inherit the lifetime from it. */ network = client_find(clients, &client->ipaddr, client->proto); if (network) { client->lifetime = network->lifetime; } } #endif client->number = tree_num_max; tree_num_max++; if (tree_num) rbtree_insert(tree_num, client); #endif if (client->ipaddr.prefix < clients->min_prefix) { clients->min_prefix = client->ipaddr.prefix; } (void) talloc_steal(clients, client); /* reparent it */ return 1; }
/* * 1 == ID was allocated & assigned * 0 == error allocating memory * -1 == all ID's are used, caller should open a new socket. * * Note that this ALSO assigns a socket to use, and updates * packet->request->src_ipaddr && packet->request->src_port * * In multi-threaded systems, the calls to id_alloc && id_free * should be protected by a mutex. This does NOT have to be * the same mutex as the one protecting the insert/find/yank * calls! */ int fr_packet_list_id_alloc(fr_packet_list_t *pl, RADIUS_PACKET *request) { int i, id, start; int src_any = 0; uint32_t free_mask; fr_packet_dst2id_t my_pd, *pd; fr_packet_socket_t *ps; bzero(&my_pd,sizeof(my_pd)); if (!pl || !pl->alloc_id || !request) { fr_strerror_printf("Invalid arguments"); return 0; } /* * Error out if no destination is specified. */ if ((request->dst_ipaddr.af == AF_UNSPEC) || (request->dst_port == 0)) { fr_strerror_printf("No destination address/port specified"); return 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; } src_any = fr_inaddr_any(&request->src_ipaddr); if (src_any < 0) { fr_strerror_printf("Error checking src IP address"); return 0; } /* * MUST specify a destination address. */ if (fr_inaddr_any(&request->dst_ipaddr) != 0) { fr_strerror_printf("Error checking dst IP address"); 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; memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0])); pd->dst_ipaddr = request->dst_ipaddr; pd->dst_port = request->dst_port; if (!fr_hash_table_insert(pl->dst2id_ht, pd)) { free(pd); fr_strerror_printf("Failed inserting into hash"); return 0; } } /* * FIXME: Go to an LRU system. This prevents ID re-use * for as long as possible. The main problem with that * approach is that it requires us to populate the * LRU/FIFO when we add a new socket, or a new destination, * which can be expensive. * * 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. */ id = start = (int) fr_rand() & 0xff; while (pd->id[id] == pl->mask) { /* all sockets are using this ID */ id++; id &= 0xff; if (id == start) { fr_strerror_printf("All IDs are being used"); return 0; } } free_mask = ~((~pd->id[id]) & pl->mask); start = -1; for (i = 0; i < MAX_SOCKETS; i++) { if (pl->sockets[i].sockfd == -1) continue; /* paranoia */ ps = &(pl->sockets[i]); /* * Address families don't match, skip it. */ if (ps->ipaddr.af != request->dst_ipaddr.af) continue; /* * We're sourcing from *, and they asked for a * specific source address: ignore it. */ if (ps->inaddr_any && !src_any) continue; /* * We're sourcing from a specific IP, and they * asked for a source IP that isn't us: ignore * it. */ if (!ps->inaddr_any && !src_any && (fr_ipaddr_cmp(&request->src_ipaddr, &ps->ipaddr) != 0)) continue; if ((free_mask & (1 << i)) == 0) { start = i; break; } } if (start < 0) { fr_strerror_printf("Internal sanity check failed"); return 0; /* bad error */ } pd->id[id] |= (1 << start); ps = &pl->sockets[start]; pd->num_outgoing++; ps->num_outgoing++; pl->num_outgoing++; /* * Set the ID, source IP, and source port. */ request->id = id; request->sockfd = ps->sockfd; request->src_ipaddr = ps->ipaddr; request->src_port = ps->port; return 1; }
/* * 1 == ID was allocated & assigned * 0 == couldn't allocate ID. * * Note that this ALSO assigns a socket to use, and updates * packet->request->src_ipaddr && packet->request->src_port * * In multi-threaded systems, the calls to id_alloc && id_free * 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. */ bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, RADIUS_PACKET **request_p, void **pctx) { int i, fd, start_i; int src_any = 0; fr_packet_socket_t *ps; RADIUS_PACKET *request = *request_p; if ((request->dst_ipaddr.af == AF_UNSPEC) || (request->dst_port == 0)) { fr_strerror_printf("No destination address/port specified"); return false; } #ifndef WITH_TCP if ((proto != 0) && (proto != IPPROTO_UDP)) { fr_strerror_printf("Invalid destination protocol"); return false; } #endif /* * 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; } src_any = fr_inaddr_any(&request->src_ipaddr); if (src_any < 0) { fr_strerror_printf("Can't check src_ipaddr"); return false; } /* * MUST specify a destination address. */ if (fr_inaddr_any(&request->dst_ipaddr) != 0) { fr_strerror_printf("Must specify a dst_ipaddr"); return false; } fd = -1; start_i = fr_rand() & SOCKOFFSET_MASK; /* * Pick a random socket which has a free ID */ #define ID_i ((i + start_i) & SOCKOFFSET_MASK) for (i = 0; i < MAX_SOCKETS; i++) { if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */ ps = &(pl->sockets[ID_i]); /* * This socket is marked as "don't use for new * packets". But we can still receive packets * that are outstanding. */ if (ps->dont_use) continue; /* * All IDs are allocated: ignore it. */ if (ps->num_outgoing == 256) continue; #ifdef WITH_TCP if (ps->proto != proto) continue; #endif /* * Address families don't match, skip it. */ if (ps->src_ipaddr.af != request->dst_ipaddr.af) continue; /* * MUST match dst port, if we have one. */ if ((ps->dst_port != 0) && (ps->dst_port != request->dst_port)) continue; /* * MUST match requested src port, if one has been given. */ if ((request->src_port != 0) && (ps->src_port != request->src_port)) continue; /* * We're sourcing from *, and they asked for a * specific source address: ignore it. */ if (ps->src_any && !src_any) continue; /* * We're sourcing from a specific IP, and they * asked for a source IP that isn't us: ignore * it. */ 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; /* * We have a socket with less than 256 outgoing * connections. There MUST be an ID free. */ fd = i; break; } /* * Ask the caller to allocate a new ID. */ if (fd < 0) { fr_strerror_printf("Failed finding socket, caller must allocate a new one"); return false; } /* * Set the ID, source IP, and source port. */ request->id = ps->ids[ps->ids_index]; request->sockfd = ps->sockfd; request->src_ipaddr = ps->src_ipaddr; request->src_port = ps->src_port; /* * If we managed didn't insert it, undo the work above. */ if (!fr_packet_list_insert(pl, request_p)) { request->id = -1; request->sockfd = -1; request->src_ipaddr.af = AF_UNSPEC; request->src_port = 0; return false; } /* * We did insert it. Update the counters. */ if (pctx) *pctx = ps->ctx; ps->num_outgoing++; pl->num_outgoing++; ps->ids_index++; /* * Refill the ID array with random IDs. */ if (ps->ids_index == 256) { for (i = 0; i < 256; i++) { ps->ids[fr_rand() & 0xff] = i; } ps->ids_index = 0; } return true; }