#include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "triton.h" #include "events.h" #include "cli.h" #include "utils.h" #include "crypto.h" #include "radius_p.h" #include "memdebug.h" static int conf_acct_on; static int conf_fail_timeout; static int conf_max_fail; static int conf_req_limit; static int num; static LIST_HEAD(serv_list); static void __free_server(struct rad_server_t *); static void serv_ctx_close(struct triton_context_t *); static struct rad_server_t *__rad_server_get(int type, struct rad_server_t *exclude, in_addr_t addr, int port) { struct rad_server_t *s, *s0 = NULL, *s1 = NULL; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); list_for_each_entry(s, &serv_list, entry) { if (s == exclude) continue; if (s->fail_time && ts.tv_sec < s->fail_time) continue; if (type == RAD_SERV_AUTH && !s->auth_port) continue; else if (type == RAD_SERV_ACCT && !s->acct_port) continue; if (s->addr == addr) { if (type == RAD_SERV_AUTH && port == s->auth_port) s1 = s; else if (type == RAD_SERV_ACCT && port == s->acct_port) s1 = s; else if (!s1) s1 = s; } if (!s0) { s0 = s; continue; } if ((s->backup < s0->backup) || ((s->backup == s0->backup) && ((s->client_cnt[0] + s->client_cnt[1])*s0->weight < (s0->client_cnt[0] + s0->client_cnt[1])*s->weight))) s0 = s; } if (s1) s0 = s1; else if (!s0) return NULL; __sync_add_and_fetch(&s0->client_cnt[type], 1); return s0; } struct rad_server_t *rad_server_get(int type) { return __rad_server_get(type, NULL, 0, 0); } struct rad_server_t *rad_server_get2(int type, in_addr_t addr, int port) { return __rad_server_get(type, NULL, addr, port); } void rad_server_put(struct rad_server_t *s, int type) { __sync_sub_and_fetch(&s->client_cnt[type], 1); if ((s->need_free || s->need_close) && !s->client_cnt[0] && !s->client_cnt[1]) { if (s->need_close) triton_context_call(&s->ctx, (triton_event_func)serv_ctx_close, &s->ctx); else __free_server(s); } } static void req_wakeup(struct rad_req_t *req) { struct timespec ts; if (!req->rpd) log_switch(triton_context_self(), NULL); log_ppp_debug("radius(%i): wakeup %p %i\n", req->serv->id, req, req->active); if (!req->active) return; clock_gettime(CLOCK_MONOTONIC, &ts); pthread_mutex_lock(&req->serv->lock); if (ts.tv_sec < req->serv->fail_time || req->serv->need_free) { req->active = 0; req->serv->req_cnt--; log_ppp_debug("radius(%i): server failed\n", req->serv->id); pthread_mutex_unlock(&req->serv->lock); req->send(req, -1); return; } pthread_mutex_unlock(&req->serv->lock); req->send(req, 1); } static void req_wakeup_failed(struct rad_req_t *req) { if (!req->rpd) log_switch(triton_context_self(), NULL); req->send(req, -1); } int rad_server_req_cancel(struct rad_req_t *req, int full) { int r = 0; pthread_mutex_lock(&req->serv->lock); if (req->entry.next) { list_del(&req->entry); req->serv->queue_cnt--; r = 1; } pthread_mutex_unlock(&req->serv->lock); triton_cancel_call(req->rpd ? req->rpd->ses->ctrl->ctx : NULL, (triton_event_func)req_wakeup); if (!full) return r; if (req->active) rad_server_req_exit(req); if (req->timeout.tpd) triton_timer_del(&req->timeout); if (req->hnd.tpd) triton_md_unregister_handler(&req->hnd, 0); return r; } int rad_server_req_enter(struct rad_req_t *req) { struct timespec ts; int r = 0; if (req->serv->need_free) return -1; clock_gettime(CLOCK_MONOTONIC, &ts); if (ts.tv_sec < req->serv->fail_time) return -1; if (!req->serv->req_limit) { if (req->send) return req->send(req, 0); return 0; } assert(!req->active); assert(!req->entry.next); pthread_mutex_lock(&req->serv->lock); if (ts.tv_sec < req->serv->fail_time) { pthread_mutex_unlock(&req->serv->lock); return -1; } if (req->serv->req_cnt >= req->serv->req_limit) { if (req->send) { list_add_tail(&req->entry, &req->serv->req_queue[req->prio]); req->serv->queue_cnt++; log_ppp_debug("radius(%i): queue %p\n", req->serv->id, req); pthread_mutex_unlock(&req->serv->lock); if (req->hnd.tpd) triton_md_disable_handler(&req->hnd, MD_MODE_READ); return 0; } pthread_mutex_unlock(&req->serv->lock); return 1; } req->serv->req_cnt++; log_ppp_debug("radius(%i): req_enter %i\n", req->serv->id, req->serv->req_cnt); pthread_mutex_unlock(&req->serv->lock); req->active = 1; if (req->send) { r = req->send(req, 0); if (r) { if (r == -2) { req->active = 0; pthread_mutex_lock(&req->serv->lock); req->serv->req_cnt--; pthread_mutex_unlock(&req->serv->lock); rad_server_fail(req->serv); } else rad_server_req_exit(req); } } return r; } void rad_server_req_exit(struct rad_req_t *req) { struct rad_server_t *serv = req->serv; if (!req->serv->req_limit) return; assert(req->active); req->active = 0; pthread_mutex_lock(&serv->lock); serv->req_cnt--; log_ppp_debug("radius(%i): req_exit %i\n", serv->id, serv->req_cnt); assert(serv->req_cnt >= 0); if (serv->req_cnt < serv->req_limit) { struct list_head *list = NULL; if (!list_empty(&serv->req_queue[0])) list = &serv->req_queue[0]; else if (!list_empty(&serv->req_queue[1])) list = &serv->req_queue[1]; if (list) { struct rad_req_t *r = list_entry(list->next, typeof(*r), entry); log_ppp_debug("radius(%i): wakeup %p\n", serv->id, r); list_del(&r->entry); serv->queue_cnt--; serv->req_cnt++; r->active = 1; triton_context_call(r->rpd ? r->rpd->ses->ctrl->ctx : NULL, (triton_event_func)req_wakeup, r); } } pthread_mutex_unlock(&serv->lock); } int rad_server_realloc(struct rad_req_t *req) { struct rad_server_t *s = __rad_server_get(req->type, req->serv, 0, 0); if (!s) return -1; if (req->serv) rad_server_put(req->serv, req->type); req->serv = s; if (req->hnd.fd != -1) { if (req->hnd.tpd) triton_md_unregister_handler(&req->hnd, 1); else { close(req->hnd.fd); req->hnd.fd = -1; } } req->server_addr = req->serv->addr; if (req->type == RAD_SERV_ACCT) req->server_port = req->serv->acct_port; else req->server_port = req->serv->auth_port; return 0; } void rad_server_fail(struct rad_server_t *s) { struct rad_req_t *r; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); pthread_mutex_lock(&s->lock); if (ts.tv_sec >= s->fail_time) { s->fail_time = ts.tv_sec + s->fail_timeout; log_ppp_warn("radius: server(%i) not responding\n", s->id); log_warn("radius: server(%i) not responding\n", s->id); } while (!list_empty(&s->req_queue[0])) { r = list_entry(s->req_queue[0].next, typeof(*r), entry); list_del(&r->entry); triton_context_call(r->rpd ? r->rpd->ses->ctrl->ctx : NULL, (triton_event_func)req_wakeup_failed, r); } while (!list_empty(&s->req_queue[1])) { r = list_entry(s->req_queue[1].next, typeof(*r), entry); list_del(&r->entry); triton_context_call(r->rpd ? r->rpd->ses->ctrl->ctx : NULL, (triton_event_func)req_wakeup_failed, r); } s->queue_cnt = 0; s->stat_fail_cnt++; pthread_mutex_unlock(&s->lock); } void rad_server_timeout(struct rad_server_t *s) { if (!s->fail_timeout) return; if (__sync_add_and_fetch(&s->timeout_cnt, 1) >= s->max_fail) rad_server_fail(s); } void rad_server_reply(struct rad_server_t *s) { __sync_synchronize(); s->timeout_cnt = 0; } static int req_set_RA(struct rad_req_t *req, const char *secret) { MD5_CTX ctx; if (rad_packet_build(req->pack, req->RA)) return -1; MD5_Init(&ctx); MD5_Update(&ctx, req->pack->buf, req->pack->len); MD5_Update(&ctx, secret, strlen(secret)); MD5_Final(req->pack->buf + 4, &ctx); return 0; } static void acct_on_sent(struct rad_req_t *req, int res) { if (!res && !req->hnd.tpd) { triton_md_register_handler(&req->serv->ctx, &req->hnd); triton_md_enable_handler(&req->hnd, MD_MODE_READ); } } static void acct_on_recv(struct rad_req_t *req) { struct rad_server_t *s = req->serv; rad_req_free(req); if (req->serv->starting) { req->serv->starting = 0; req->serv->acct_on = 1; } else __free_server(s); } static void acct_on_timeout(struct triton_timer_t *t) { struct rad_req_t *req = container_of(t, typeof(*req), timeout); struct rad_server_t *s = req->serv; log_switch(triton_context_self(), NULL); if (req->try++ == conf_max_try) { rad_req_free(req); if (s->starting) s->starting = 0; else __free_server(s); return; } __rad_req_send(req, 0); } static void send_acct_on(struct rad_server_t *s) { struct rad_req_t *req = rad_req_alloc_empty(); log_switch(triton_context_self(), NULL); memset(req, 0, sizeof(*req)); req->hnd.fd = -1; req->type = RAD_SERV_ACCT; req->server_addr = s->addr; req->server_port = s->acct_port; req->serv = s; req->sent = acct_on_sent; req->recv = acct_on_recv; req->hnd.read = rad_req_read; req->timeout.expire = acct_on_timeout; req->timeout.period = conf_timeout * 1000; req->try = 1; __sync_add_and_fetch(&s->client_cnt[req->type], 1); if (conf_verbose) req->log = log_info1; req->pack = rad_packet_alloc(CODE_ACCOUNTING_REQUEST); if (!req->pack) goto out_err; if (rad_packet_add_val(req->pack, NULL, "Acct-Status-Type", s->starting ? "Accounting-On" : "Accounting-Off")) goto out_err; if (conf_nas_identifier) if (rad_packet_add_str(req->pack, NULL, "NAS-Identifier", conf_nas_identifier)) goto out_err; if (conf_nas_ip_address) if (rad_packet_add_ipaddr(req->pack, NULL, "NAS-IP-Address", conf_nas_ip_address)) goto out_err; if (req_set_RA(req, s->secret)) goto out_err; __rad_req_send(req, 0); triton_timer_add(&s->ctx, &req->timeout, 0); return; out_err: rad_req_free(req); } static void serv_ctx_close(struct triton_context_t *ctx) { struct rad_server_t *s = container_of(ctx, typeof(*s), ctx); if (s->timer.tpd) triton_timer_del(&s->timer); s->need_close = 1; if (!s->client_cnt[0] && !s->client_cnt[1]) { if (s->acct_on) { s->acct_on = 0; s->starting = 0; s->need_close = 0; send_acct_on(s); } else triton_context_unregister(ctx); } } static void show_stat(struct rad_server_t *s, void *client) { char addr[17]; struct timespec ts; u_inet_ntoa(s->addr, addr); clock_gettime(CLOCK_MONOTONIC, &ts); cli_sendv(client, "radius(%i, %s):\r\n", s->id, addr); if (ts.tv_sec < s->fail_time) cli_send(client, " state: failed\r\n"); else cli_send(client, " state: active\r\n"); cli_sendv(client, " fail count: %lu\r\n", s->stat_fail_cnt); cli_sendv(client, " request count: %i\r\n", s->req_cnt); cli_sendv(client, " queue length: %i\r\n", s->queue_cnt); if (s->auth_port) { cli_sendv(client, " auth sent: %lu\r\n", s->stat_auth_sent); cli_sendv(client, " auth lost(total/5m/1m): %lu/%lu/%lu\r\n", s->stat_auth_lost, stat_accm_get_cnt(s->stat_auth_lost_5m), stat_accm_get_cnt(s->stat_auth_lost_1m)); cli_sendv(client, " auth avg query time(5m/1m): %lu/%lu ms\r\n", stat_accm_get_avg(s->stat_auth_query_5m), stat_accm_get_avg(s->stat_auth_query_1m)); } if (s->acct_port) { cli_sendv(client, " acct sent: %lu\r\n", s->stat_acct_sent); cli_sendv(client, " acct lost(total/5m/1m): %lu/%lu/%lu\r\n", s->stat_acct_lost, stat_accm_get_cnt(s->stat_acct_lost_5m), stat_accm_get_cnt(s->stat_acct_lost_1m)); cli_sendv(client, " acct avg query time(5m/1m): %lu/%lu ms\r\n", stat_accm_get_avg(s->stat_acct_query_5m), stat_accm_get_avg(s->stat_acct_query_1m)); cli_sendv(client, " interim sent: %lu\r\n", s->stat_interim_sent); cli_sendv(client, " interim lost(total/5m/1m): %lu/%lu/%lu\r\n", s->stat_interim_lost, stat_accm_get_cnt(s->stat_interim_lost_5m), stat_accm_get_cnt(s->stat_interim_lost_1m)); cli_sendv(client, " interim avg query time(5m/1m): %lu/%lu ms\r\n", stat_accm_get_avg(s->stat_interim_query_5m), stat_accm_get_avg(s->stat_interim_query_1m)); } } static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { struct rad_server_t *s; list_for_each_entry(s, &serv_list, entry) show_stat(s, client); return CLI_CMD_OK; } static void __add_server(struct rad_server_t *s) { struct rad_server_t *s1; list_for_each_entry(s1, &serv_list, entry) { if (s1->addr == s->addr && s1->auth_port == s->auth_port && s1->acct_port == s->acct_port) { s1->fail_timeout = s->fail_timeout; s1->req_limit = s->req_limit; s1->max_fail = s->max_fail; s1->need_free = 0; _free(s); return; } } s->id = ++num; INIT_LIST_HEAD(&s->req_queue[0]); INIT_LIST_HEAD(&s->req_queue[1]); pthread_mutex_init(&s->lock, NULL); list_add_tail(&s->entry, &serv_list); s->starting = conf_acct_on; s->stat_auth_lost_1m = stat_accm_create(60); s->stat_auth_lost_5m = stat_accm_create(5 * 60); s->stat_auth_query_1m = stat_accm_create(60); s->stat_auth_query_5m = stat_accm_create(5 * 60); s->stat_acct_lost_1m = stat_accm_create(60); s->stat_acct_lost_5m = stat_accm_create(5 * 60); s->stat_acct_query_1m = stat_accm_create(60); s->stat_acct_query_5m = stat_accm_create(5 * 60); s->stat_interim_lost_1m = stat_accm_create(60); s->stat_interim_lost_5m = stat_accm_create(5 * 60); s->stat_interim_query_1m = stat_accm_create(60); s->stat_interim_query_5m = stat_accm_create(5 * 60); s->ctx.close = serv_ctx_close; triton_context_register(&s->ctx, NULL); triton_context_set_priority(&s->ctx, 0); if (conf_acct_on) triton_context_call(&s->ctx, (triton_event_func)send_acct_on, s); triton_context_wakeup(&s->ctx); } static void __free_server(struct rad_server_t *s) { log_debug("radius: free(%i)\n", s->id); stat_accm_free(s->stat_auth_lost_1m); stat_accm_free(s->stat_auth_lost_5m); stat_accm_free(s->stat_auth_query_1m); stat_accm_free(s->stat_auth_query_5m); stat_accm_free(s->stat_acct_lost_1m); stat_accm_free(s->stat_acct_lost_5m); stat_accm_free(s->stat_acct_query_1m); stat_accm_free(s->stat_acct_query_5m); stat_accm_free(s->stat_interim_lost_1m); stat_accm_free(s->stat_interim_lost_5m); stat_accm_free(s->stat_interim_query_1m); stat_accm_free(s->stat_interim_query_5m); triton_context_unregister(&s->ctx); _free(s); } static int parse_server_old(const char *opt, in_addr_t *addr, int *port, char **secret) { char *str = _strdup(opt); char *p1, *p2; p1 = strstr(str, ":"); p2 = strstr(str, ","); if (p1) *p1 = 0; if (p2) *p2 = 0; else return -1; *addr = inet_addr(str); if (p1) { *port = atoi(p1 + 1); if (*port <=0 ) return -1; } *secret = _strdup(p2 + 1); _free(str); return 0; } static void add_server_old(void) { const char *opt; in_addr_t auth_addr = 0; int auth_port = 0; char *auth_secret = NULL; in_addr_t acct_addr = 0; int acct_port = 0; char *acct_secret = NULL; struct rad_server_t *s; opt = conf_get_opt("radius", "auth-server"); if (opt) { if (parse_server_old(opt, &auth_addr, &auth_port, &auth_secret)) { log_emerg("radius: failed to parse 'auth-server'\n"); return; } } else return; opt = conf_get_opt("radius", "acct-server"); if (opt) { if (parse_server_old(opt, &acct_addr, &acct_port, &acct_secret)) { log_emerg("radius: failed to parse 'acct-server'\n"); return; } conf_accounting = 1; } s = _malloc(sizeof(*s)); memset(s, 0, sizeof(*s)); s->addr = auth_addr; s->secret = auth_secret; s->auth_port = auth_port; s->fail_timeout = conf_fail_timeout; s->req_limit = conf_req_limit; s->max_fail = conf_max_fail; if (auth_addr == acct_addr && !strcmp(auth_secret, acct_secret)) { s->acct_port = acct_port; __add_server(s); return; } __add_server(s); if (acct_addr) { s = _malloc(sizeof(*s)); memset(s, 0, sizeof(*s)); s->addr = acct_addr; s->secret = acct_secret; s->acct_port = acct_port; s->fail_timeout = conf_fail_timeout; s->req_limit = conf_req_limit; s->max_fail = conf_max_fail; __add_server(s); } } static int parse_server1(const char *_opt, struct rad_server_t *s) { char *opt = _strdup(_opt); char *ptr1, *ptr2, *ptr3, *endptr; ptr1 = strchr(opt, ','); if (!ptr1) goto out; ptr2 = strchr(ptr1 + 1, ','); if (ptr2) ptr3 = strchr(ptr2 + 1, ','); else ptr3 = NULL; *ptr1 = 0; if (ptr2) *ptr2 = 0; if (ptr3) *ptr3 = 0; s->addr = inet_addr(opt); if (ptr2) { if (ptr2[1]) { s->auth_port = strtol(ptr2 + 1, &endptr, 10); if (*endptr) goto out; } } else s->auth_port = 1812; if (ptr3) { if (ptr3[1]) { s->acct_port = strtol(ptr3 + 1, &endptr, 10); if (*endptr) goto out; } } else s->acct_port = 1813; s->secret = _strdup(ptr1 + 1); s->fail_timeout = conf_fail_timeout; s->req_limit = conf_req_limit; s->max_fail = conf_max_fail; return 0; out: _free(opt); return -1; } static int parse_server2(const char *_opt, struct rad_server_t *s) { char *opt = _strdup(_opt); char *ptr1, *ptr2, *ptr3, *endptr; ptr1 = strchr(opt, ','); if (!ptr1) goto out; ptr2 = strchr(ptr1 + 1, ','); if (!ptr2) goto out; *ptr1 = 0; s->addr = inet_addr(opt); ptr3 = strstr(ptr2, ",auth-port="); if (ptr3) { s->auth_port = strtol(ptr3 + 11, &endptr, 10); if (*endptr != ',' && *endptr != 0) goto out; } else s->auth_port = 1812; ptr3 = strstr(ptr2, ",acct-port="); if (ptr3) { s->acct_port = strtol(ptr3 + 11, &endptr, 10); if (*endptr != ',' && *endptr != 0) goto out; } else s->acct_port = 1813; ptr3 = strstr(ptr2, ",req-limit="); if (ptr3) { s->req_limit = strtol(ptr3 + 11, &endptr, 10); if (*endptr != ',' && *endptr != 0) goto out; } else s->req_limit = conf_req_limit; ptr3 = strstr(ptr2, ",fail-timeout="); if (ptr3) { s->fail_timeout = strtol(ptr3 + 14, &endptr, 10); if (*endptr != ',' && *endptr != 0) goto out; } else { ptr3 = strstr(ptr2, ",fail-time="); if (ptr3) { s->fail_timeout = strtol(ptr3 + 11, &endptr, 10); if (*endptr != ',' && *endptr != 0) goto out; } else s->fail_timeout = conf_fail_timeout; } ptr3 = strstr(ptr2, ",max-fail="); if (ptr3) { s->max_fail = strtol(ptr3 + 10, &endptr, 10); if (*endptr != ',' && *endptr != 0) goto out; } else s->max_fail = conf_max_fail; ptr3 = strstr(ptr2, ",weight="); if (ptr3) { s->weight = atoi(ptr3 + 8); if (s->weight <= 0) { log_error("radius: %s: invalid weight (forced to 1)\n", _opt); s->weight = 1; } } else s->weight = 1; ptr3 = strstr(ptr2, ",backup"); if (ptr3) s->backup = 1; else s->backup = 0; *ptr2 = 0; s->secret = _strdup(ptr1 + 1); _free(opt); return 0; out: _free(opt); return -1; } static void add_server(const char *opt) { struct rad_server_t *s = _malloc(sizeof(*s)); memset(s, 0, sizeof(*s)); if (!parse_server1(opt,s)) goto add; if (!parse_server2(opt,s)) goto add; log_emerg("radius: failed to parse '%s'\n", opt); _free(s); return; add: __add_server(s); } static void load_config(void) { struct conf_sect_t *sect = conf_get_section("radius"); struct conf_option_t *opt; struct rad_server_t *s; struct rad_req_t *r; struct list_head *pos, *n; const char *opt1; list_for_each_entry(s, &serv_list, entry) s->need_free = 1; opt1 = conf_get_opt("radius", "acct-on"); if (opt1) conf_acct_on = atoi(opt1); else conf_acct_on = 0; opt1 = conf_get_opt("radius", "fail-timeout"); if (!opt1) opt1 = conf_get_opt("radius", "fail-time"); if (opt1) conf_fail_timeout = atoi(opt1); else conf_fail_timeout = 0; opt1 = conf_get_opt("radius", "req-limit"); if (opt1) conf_req_limit = atoi(opt1); else conf_req_limit = 0; opt1 = conf_get_opt("radius", "max-fail"); if (opt1) conf_max_fail = atoi(opt1); else conf_max_fail = conf_req_limit + conf_max_try; list_for_each_entry(opt, §->items, entry) { if (strcmp(opt->name, "server")) continue; add_server(opt->val); } list_for_each_safe(pos, n, &serv_list) { s = list_entry(pos, typeof(*s), entry); if (s->need_free) { list_del(&s->entry); while (!list_empty(&s->req_queue[0])) { r = list_entry(s->req_queue[0].next, typeof(*r), entry); list_del(&r->entry); triton_context_call(r->rpd->ses->ctrl->ctx, (triton_event_func)req_wakeup, r); } while (!list_empty(&s->req_queue[1])) { r = list_entry(s->req_queue[1].next, typeof(*r), entry); list_del(&r->entry); triton_context_call(r->rpd->ses->ctrl->ctx, (triton_event_func)req_wakeup, r); } if (!s->client_cnt[0] && !s->client_cnt[1]) { if (s->acct_on) triton_context_call(&s->ctx, (triton_event_func)serv_ctx_close, &s->ctx); else __free_server(s); } } } add_server_old(); conf_accounting = 0; list_for_each_entry(s, &serv_list, entry) { if (s->acct_port) { conf_accounting = 1; break; } } list_for_each_entry(s, &serv_list, entry) { if (s->starting) { if (!conf_accounting || !s->auth_port) s->starting = 0; } } } static void init(void) { load_config(); triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); cli_register_simple_cmd2(show_stat_exec, NULL, 2, "show", "stat"); } DEFINE_INIT(52, init);