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
#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 in_addr hs_srcip4addr;
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_IPADDR,
- 0, &hs_srcip4addr, 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, 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 void null_free(UNUSED void *data)
+{
+}
+
static int home_server_add(realm_config_t *rc, CONF_SECTION *cs, int pool_type)
{
const char *name2;
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));
- memset(&hs_srcip4addr, 0, sizeof(hs_srcip4addr));
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 (home->no_response_fail == 2) home->no_response_fail = 0;
if (pool_type != home->type) {
mismatch:
cf_log_err(cf_sectiontoitem(cs),
- "Server pool cannot include home server %s of type \"%s\"",
+ "Home server %s of unexpected type \"%s\"",
name2, hs_type);
goto error;
}
} else if (strcasecmp(hs_type, "acct") == 0) {
home->type = HOME_TYPE_ACCT;
+ if (home->no_response_fail == 2) home->no_response_fail = 1;
if (pool_type != home->type) goto mismatch;
} else if (strcasecmp(hs_type, "auth+acct") == 0) {
free(hs_type);
hs_type = NULL;
- /*
- * FIXME: Add support for IPv6 source addresses
- */
- if (hs_srcip4addr.s_addr != htonl(INADDR_ANY)) {
- home->src_ipaddr.af = AF_INET;
- home->src_ipaddr.ipaddr.ip4addr = hs_srcip4addr;
- }
-
if (!hs_check || (strcasecmp(hs_check, "none") == 0)) {
home->ping_check = HOME_PING_CHECK_NONE;
}
}
- 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;
}
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;
}
}
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
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)) {
return 0;
}
}
+
+ /*
+ * CoA home servers aren't tied to realms.
+ */
+ for (cs = cf_subsection_find_next(config, NULL, "home_server");
+ cs != NULL;
+ cs = cf_subsection_find_next(config, cs, "home_server")) {
+ /*
+ * Server was already loaded.
+ */
+ if (cf_data_find(cs, "home_server")) continue;
+
+ if (!home_server_add(rc, cs, HOME_TYPE_COA)) {
+ return 0;
+ }
+ }
#endif
int start;
int count;
home_server *found = NULL;
+ home_server *zombie = NULL;
VALUE_PAIR *vp;
/*
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;
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_FAIL_OVER) {
+ if (pool->type != HOME_POOL_LOAD_BALANCE) {
found = home;
break;
}
} /* 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) {
* the 'hints' file.
*/
request->proxy->vps = paircopy(request->packet->vps);
-
- /*
- * Set the source IP address for proxying.
- */
- request->proxy->src_ipaddr = found->src_ipaddr;
}
/*
* 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;
*/
if (found->message_authenticator &&
(request->packet->code == PW_AUTHENTICATION_REQUEST) &&
- !pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR)) {
+ !pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR, 0)) {
radius_pairmake(request, &request->proxy->vps,
"Message-Authenticator", "0x00",
T_OP_SET);
}
-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);
}
#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