if (a->server && !b->server) return -1;
if (!a->server && b->server) return +1;
+
if (a->server && b->server) {
int rcode = a->type - b->type;
if (rcode != 0) return rcode;
return strcmp(a->server, b->server);
}
+#ifdef WITH_TCP
+ if (a->proto < b->proto) return -1;
+ if (a->proto > b->proto) return +1;
+#endif
+
if (a->port < b->port) return -1;
if (a->port > b->port) return +1;
}
+static size_t xlat_cs(CONF_SECTION *cs, char *fmt, char *out, size_t outlen)
+
+{
+ const char *value = NULL;
+
+ /*
+ * Instance name
+ */
+ if (strcmp(fmt, "instance") == 0) {
+ value = cf_section_name2(cs);
+ if (!value) {
+ *out = '\0';
+ return 0;
+ }
+ } else {
+ CONF_PAIR *cp;
+
+ cp = cf_pair_find(cs, fmt);
+ if (!cp || !(value = cf_pair_value(cp))) {
+ *out = '\0';
+ return 0;
+ }
+ }
+
+ strlcpy(out, value, outlen);
+
+ return strlen(out);
+}
+
+
/*
* Xlat for %{home_server:foo}
*/
char *fmt, char *out, size_t outlen,
UNUSED RADIUS_ESCAPE_STRING func)
{
- const char *value = NULL;
- CONF_PAIR *cp;
-
if (!fmt || !out || (outlen < 1)) return 0;
if (!request || !request->home_server) {
return 0;
}
- cp = cf_pair_find(request->home_server->cs, fmt);
- if (!cp || !(value = cf_pair_value(cp))) {
- *out = '\0';
- return 0;
- }
-
- strlcpy(out, value, outlen);
-
- return strlen(out);
+ return xlat_cs(request->home_server->cs, fmt, out, outlen);
}
char *fmt, char *out, size_t outlen,
UNUSED RADIUS_ESCAPE_STRING func)
{
- const char *value = NULL;
- CONF_PAIR *cp;
-
if (!fmt || !out || (outlen < 1)) return 0;
if (!request || !request->home_pool) {
return 0;
}
- cp = cf_pair_find(request->home_pool->cs, fmt);
- if (!cp || !(value = cf_pair_value(cp))) {
- *out = '\0';
- return 0;
- }
-
- strlcpy(out, value, outlen);
-
- return strlen(out);
+ return xlat_cs(request->home_pool->cs, fmt, out, outlen);
}
#endif
free(this->realm);
free(this);
}
+ realms_regex = NULL;
}
#endif
free(realm_config);
+ realm_config = NULL;
}
#ifdef WITH_PROXY
+static CONF_PARSER limit_config[] = {
+ { "max_connections", PW_TYPE_INTEGER,
+ offsetof(home_server, max_connections), NULL, "16" },
+
+ { "max_requests", PW_TYPE_INTEGER,
+ offsetof(home_server,max_requests), NULL, "0" },
+
+ { "lifetime", PW_TYPE_INTEGER,
+ offsetof(home_server,lifetime), NULL, "0" },
+
+ { "idle_timeout", PW_TYPE_INTEGER,
+ offsetof(home_server,idle_timeout), NULL, "0" },
+
+ { NULL, -1, 0, NULL, NULL } /* end the list */
+};
+
static struct in_addr hs_ip4addr;
static struct in6_addr hs_ip6addr;
+static char *hs_srcipaddr = NULL;
static char *hs_type = NULL;
static char *hs_check = NULL;
static char *hs_virtual_server = NULL;
+#ifdef WITH_TCP
+static char *hs_proto = NULL;
+#endif
static CONF_PARSER home_server_config[] = {
{ "ipaddr", PW_TYPE_IPADDR,
{ "type", PW_TYPE_STRING_PTR,
0, &hs_type, NULL },
+#ifdef WITH_TCP
+ { "proto", PW_TYPE_STRING_PTR,
+ 0, &hs_proto, NULL },
+#endif
+
{ "secret", PW_TYPE_STRING_PTR,
offsetof(home_server,secret), NULL, NULL},
+ { "src_ipaddr", PW_TYPE_STRING_PTR,
+ 0, &hs_srcipaddr, NULL },
+
{ "response_window", PW_TYPE_INTEGER,
offsetof(home_server,response_window), NULL, "30" },
+ { "no_response_fail", PW_TYPE_BOOLEAN,
+ offsetof(home_server,no_response_fail), NULL, NULL },
{ "max_outstanding", PW_TYPE_INTEGER,
offsetof(home_server,max_outstanding), NULL, "65536" },
+ { "require_message_authenticator", PW_TYPE_BOOLEAN,
+ offsetof(home_server, message_authenticator), 0, NULL },
{ "zombie_period", PW_TYPE_INTEGER,
offsetof(home_server,zombie_period), NULL, "40" },
offsetof(home_server, coa_mrd), 0, Stringify(30) },
#endif
+ { "limit", PW_TYPE_SUBSECTION, 0, NULL, (const void *) limit_config },
+
{ NULL, -1, 0, NULL, NULL } /* end the list */
};
-static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type)
+static void null_free(UNUSED void *data)
+{
+}
+
+static int home_server_add(realm_config_t *rc, CONF_SECTION *cs)
{
const char *name2;
home_server *home;
free(hs_virtual_server); /* used only for printing during parsing */
hs_virtual_server = NULL;
- name2 = cf_section_name1(cs);
- if (!name2 || (strcasecmp(name2, "home_server") != 0)) {
- cf_log_err(cf_sectiontoitem(cs),
- "Section is not a home_server.");
- return 0;
- }
-
name2 = cf_section_name2(cs);
if (!name2) {
cf_log_err(cf_sectiontoitem(cs),
home->name = name2;
home->cs = cs;
+ /*
+ * For zombie period calculations. We want to count
+ * zombies from the time when the server starts, instead
+ * of from 1970.
+ */
+ home->last_packet = time(NULL);
+
+ /*
+ * Authentication servers have a default "no_response_fail = 0".
+ * Accounting servers have a default "no_response_fail = 1".
+ *
+ * This is because authentication packets are retried, so
+ * they can fail over to another home server. Accounting
+ * packets are not retried, so they cannot fail over, and
+ * instead should be rejected immediately.
+ */
+ home->no_response_fail = 2;
+
memset(&hs_ip4addr, 0, sizeof(hs_ip4addr));
memset(&hs_ip6addr, 0, sizeof(hs_ip6addr));
if (cf_section_parse(cs, home, home_server_config) < 0) {
- free(home);
- return 0;
+ goto error;
}
/*
hs_type = NULL;
free(hs_check);
hs_check = NULL;
+ free(hs_srcipaddr);
+ hs_srcipaddr = NULL;
+#ifdef WITH_TCP
+ free(hs_proto);
+ hs_proto = NULL;
+#endif
return 0;
}
if (strcasecmp(hs_type, "auth") == 0) {
home->type = HOME_TYPE_AUTH;
- if (pool_type != home->type) {
- mismatch:
- cf_log_err(cf_sectiontoitem(cs),
- "Server pool cannot include home server %s of type \"%s\"",
- name2, hs_type);
- goto error;
- }
+ if (home->no_response_fail == 2) home->no_response_fail = 0;
} else if (strcasecmp(hs_type, "acct") == 0) {
home->type = HOME_TYPE_ACCT;
- if (pool_type != home->type) goto mismatch;
+ if (home->no_response_fail == 2) home->no_response_fail = 1;
} else if (strcasecmp(hs_type, "auth+acct") == 0) {
home->type = HOME_TYPE_AUTH;
home->type = HOME_TYPE_COA;
dual = FALSE;
- if (pool_type != home->type) goto mismatch;
-
if (home->server != NULL) {
cf_log_err(cf_sectiontoitem(cs),
"Home servers of type \"coa\" cannot point to a virtual server");
}
}
- if ((home->ipaddr.af != AF_UNSPEC) && /* could be virtual server */
+ home->proto = IPPROTO_UDP;
+#ifdef WITH_TCP
+ if (hs_proto) {
+ if (strcmp(hs_proto, "udp") == 0) {
+ free(hs_proto);
+ hs_proto = NULL;
+
+ } else if (strcmp(hs_proto, "tcp") == 0) {
+ free(hs_proto);
+ hs_proto = NULL;
+ home->proto = IPPROTO_TCP;
+
+ } else {
+ cf_log_err(cf_sectiontoitem(cs),
+ "Unknown proto \"%s\".", hs_proto);
+ goto error;
+ }
+ }
+#endif
+
+ if (!home->server &&
rbtree_finddata(home_servers_byaddr, home)) {
cf_log_err(cf_sectiontoitem(cs), "Duplicate home server");
goto error;
}
+ /*
+ * If the home is a virtual server, don't look up source IP.
+ */
+ 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;
+
if (!rbtree_insert(home_servers_byname, home)) {
cf_log_err(cf_sectiontoitem(cs),
"Internal error %d adding home server %s.",
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),
}
#endif
- if (home->response_window < 5) home->response_window = 5;
- if (home->response_window > 60) home->response_window = 60;
-
if (home->max_outstanding < 8) home->max_outstanding = 8;
if (home->max_outstanding > 65536*16) home->max_outstanding = 65536*16;
if (home->ping_interval < 6) home->ping_interval = 6;
if (home->ping_interval > 120) home->ping_interval = 120;
- if (home->zombie_period < 20) home->zombie_period = 20;
+ if (home->response_window < 1) home->response_window = 1;
+ if (home->response_window > 60) home->response_window = 60;
+
+ if (home->zombie_period < 1) home->zombie_period = 1;
if (home->zombie_period > 120) home->zombie_period = 120;
if (home->zombie_period < home->response_window) {
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;
+
if (dual) {
home_server *home2 = rad_malloc(sizeof(*home2));
home2->ping_user_password = NULL;
home2->cs = cs;
+ if (home->no_response_fail == 2) home->no_response_fail = 0;
+ if (home2->no_response_fail == 2) home2->no_response_fail = 1;
+
if (!rbtree_insert(home_servers_byname, home2)) {
cf_log_err(cf_sectiontoitem(cs),
"Internal error %d adding home server %s.",
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),
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),
#endif
}
+ /*
+ * Mark it as already processed
+ */
+ cf_data_add(cs, "home_server", null_free, null_free);
+
return 1;
}
return 0;
}
- if (!home_server_add(rc, server_cs, server_type)) {
- return 0;
- }
-
home = rbtree_finddata(home_servers_byname, &myhome);
if (!home) {
+ cf_log_err(cf_pairtoitem(cp),
+ "Internal error %d adding home server \"%s\".",
+ __LINE__, name);
return 0;
}
if (!pool_check_home_server(rc, cp, cf_pair_value(cp),
server_type, &home)) {
-
return 0;
}
}
if (do_print) cf_log_info(cs, " }");
- cf_data_add(cs, "home_server_pool", pool, NULL);
+ cf_data_add(cs, "home_server_pool", pool, free);
rad_assert(pool->server_type != 0);
home->type = type;
home->secret = secret;
home->cs = cs;
+ home->proto = IPPROTO_UDP;
p = strchr(name, ':');
if (!p) {
free(q);
return 0;
}
+ home->src_ipaddr.af = home->ipaddr.af;
} else {
home->ipaddr.af = AF_UNSPEC;
home->server = server;
#ifdef HAVE_REGEX_H
if (name2[0] == '~') {
+ int rcode;
regex_t reg;
/*
* Include substring matches.
*/
- if (regcomp(®, name2 + 1,
- REG_EXTENDED | REG_NOSUB | REG_ICASE) != 0) {
+ rcode = regcomp(®, name2 + 1,
+ REG_EXTENDED | REG_NOSUB | REG_ICASE);
+ if (rcode != 0) {
+ char buffer[256];
+
+ regerror(rcode, ®, buffer, sizeof(buffer));
+
cf_log_err(cf_sectiontoitem(cs),
- "Invalid regex in realm \"%s\"", name2);
+ "Invalid regex \"%s\": %s",
+ name2 + 1, buffer);
goto error;
}
regfree(®);
}
#ifdef WITH_COA
-static int pool_peek_type(CONF_SECTION *cs)
+static const FR_NAME_NUMBER home_server_types[] = {
+ { "auth", HOME_TYPE_AUTH },
+ { "auth+acct", HOME_TYPE_AUTH },
+ { "acct", HOME_TYPE_ACCT },
+ { "coa", HOME_TYPE_COA },
+ { NULL, 0 }
+};
+
+static int pool_peek_type(CONF_SECTION *config, CONF_SECTION *cs)
{
- const char *name;
+ int home;
+ const char *name, *type;
CONF_PAIR *cp;
- home_server *home;
+ CONF_SECTION *server_cs;
cp = cf_pair_find(cs, "home_server");
if (!cp) {
return HOME_TYPE_INVALID;
}
- home = home_server_byname(name);
- if (!home) {
+ server_cs = cf_section_sub_find_name2(config, "home_server", name);
+ if (!server_cs) {
cf_log_err(cf_pairtoitem(cp), "home_server \"%s\" does not exist", name);
return HOME_TYPE_INVALID;
}
- return home->type;
+ cp = cf_pair_find(server_cs, "type");
+ if (!cp) {
+ cf_log_err(cf_sectiontoitem(server_cs), "home_server %s does not contain a \"type\" entry", name);
+ return HOME_TYPE_INVALID;
+ }
+
+ type = cf_pair_value(cp);
+ if (!type) {
+ cf_log_err(cf_sectiontoitem(server_cs), "home_server %s contains an empty \"type\" entry", name);
+ return HOME_TYPE_INVALID;
+ }
+
+ home = fr_str2int(home_server_types, type, HOME_TYPE_INVALID);
+ if (home == HOME_TYPE_INVALID) {
+ cf_log_err(cf_sectiontoitem(server_cs), "home_server %s contains an invalid \"type\" entry of value \"%s\"", name, type);
+ return HOME_TYPE_INVALID;
+ }
+
+ return home; /* 'cause we miss it so much */
}
#endif
}
#endif
- home_pools_byname = rbtree_create(home_pool_name_cmp, free, 0);
+ home_pools_byname = rbtree_create(home_pool_name_cmp, NULL, 0);
if (!home_pools_byname) {
realms_free();
return 0;
}
#endif
+ for (cs = cf_subsection_find_next(config, NULL, "home_server");
+ cs != NULL;
+ cs = cf_subsection_find_next(config, cs, "home_server")) {
+ if (!home_server_add(rc, cs)) {
+ free(rc);
+ realms_free();
+ return 0;
+ }
+ }
+
for (cs = cf_subsection_find_next(config, NULL, "realm");
cs != NULL;
cs = cf_subsection_find_next(config, cs, "realm")) {
cs = cf_subsection_find_next(config, cs, "home_server_pool")) {
int type;
+ /*
+ * Pool was already loaded.
+ */
if (cf_data_find(cs, "home_server_pool")) continue;
- type = pool_peek_type(cs);
+ type = pool_peek_type(config, cs);
if (type == HOME_TYPE_INVALID) return 0;
if (!server_pool_add(rc, cs, type, TRUE)) {
int start;
int count;
home_server *found = NULL;
+ home_server *zombie = NULL;
VALUE_PAIR *vp;
- start = 0;
-
/*
* Determine how to pick choose the home server.
*/
break;
case HOME_POOL_KEYED_BALANCE:
- if ((vp = pairfind(request->config_items, PW_LOAD_BALANCE_KEY)) != NULL) {
+ if ((vp = pairfind(request->config_items, PW_LOAD_BALANCE_KEY, 0)) != NULL) {
hash = fr_hash(vp->vp_strvalue, vp->length);
start = hash % pool->num_home_servers;
break;
/* FALL-THROUGH */
case HOME_POOL_LOAD_BALANCE:
- found = pool->servers[0];
+ case HOME_POOL_FAIL_OVER:
+ start = 0;
+ break;
- default:
+ default: /* this shouldn't happen... */
start = 0;
break;
+
}
/*
for (count = 0; count < pool->num_home_servers; count++) {
home_server *home = pool->servers[(start + count) % pool->num_home_servers];
+ if (!home) continue;
+
+ /*
+ * Skip dead home servers.
+ */
if (home->state == HOME_STATE_IS_DEAD) {
continue;
}
}
#endif
+ /*
+ * It's zombie, so we remember the first zombie
+ * we find, but we don't mark it as a "live"
+ * server.
+ */
+ if (home->state == HOME_STATE_ZOMBIE) {
+ if (!zombie) zombie = home;
+ continue;
+ }
+
+ /*
+ * We've found the first "live" one. Use that.
+ */
if (pool->type != HOME_POOL_LOAD_BALANCE) {
found = home;
break;
}
+ /*
+ * Otherwise we're doing some kind of load balancing.
+ * If we haven't found one yet, pick this one.
+ */
if (!found) {
found = home;
continue;
/*
* Prefer this server if it's less busy than the
- * one we previously found.
+ * one we had previously found.
*/
if (home->currently_outstanding < found->currently_outstanding) {
RDEBUG3("PROXY Choosing %s: It's less busy than %s",
if (((count + 1) * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) {
found = home;
}
-
} /* loop over the home servers */
/*
+ * We have no live servers, BUT we have a zombie. Use
+ * the zombie as a last resort.
+ */
+ if (!found && zombie) {
+ found = zombie;
+ zombie = NULL;
+ }
+
+ /*
* There's a fallback if they're all dead.
*/
if (!found && pool->fallback) {
/*
* 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;
+ /*
+ * We're supposed to add a Message-Authenticator
+ * if it doesn't exist, and it doesn't exist.
+ */
+ if (found->message_authenticator &&
+ (request->packet->code == PW_AUTHENTICATION_REQUEST) &&
+ !pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR, 0)) {
+ radius_pairmake(request, &request->proxy->vps,
+ "Message-Authenticator", "0x00",
+ T_OP_SET);
+ }
+
return found;
}
for (count = 0; count < pool->num_home_servers; count++) {
home_server *home = pool->servers[count];
+ if (!home) continue;
+
if ((home->state == HOME_STATE_IS_DEAD) &&
(home->ping_check == HOME_PING_CHECK_NONE)) {
home->state = HOME_STATE_ALIVE;
}
-home_server *home_server_find(fr_ipaddr_t *ipaddr, int port)
+home_server *home_server_find(fr_ipaddr_t *ipaddr, int port, int proto)
{
home_server myhome;
memset(&myhome, 0, sizeof(myhome));
myhome.ipaddr = *ipaddr;
myhome.port = port;
+#ifdef WITH_TCP
+ myhome.proto = proto;
+#else
+ myhome.proto = IPPROTO_UDP;
+#endif
myhome.server = NULL; /* we're not called for internal proxying */
return rbtree_finddata(home_servers_byaddr, &myhome);
}
#ifdef WITH_COA
-home_server *home_server_byname(const char *name)
+home_server *home_server_byname(const char *name, int type)
{
home_server myhome;
memset(&myhome, 0, sizeof(myhome));
+ myhome.type = type;
myhome.name = name;
return rbtree_finddata(home_servers_byname, &myhome);