#include #include #include #include #include #include #include #include #include #include #include #include #include #include "linux_ppp.h" #include "triton.h" #include "events.h" #include "ppp.h" #include "ipdb.h" #include "log.h" #include "utils.h" #include "sigchld.h" #include "iputils.h" #ifdef RADIUS #include "radius.h" #endif #include "memdebug.h" #define ENV_MEM 1024 #define ENV_MAX 16 static char *conf_ip_up; static char *conf_ip_pre_up; static char *conf_ip_down; static char *conf_ip_change; static char *conf_radattr_prefix; static int conf_verbose = 0; static int conf_fork_limit; static void *pd_key; static pthread_mutex_t queue_lock; static int fork_cnt; static LIST_HEAD(queue0); static LIST_HEAD(queue1); struct pppd_compat_pd { struct ap_private pd; struct ap_session *ses; struct list_head entry; struct sigchld_handler_t hnd; struct sigchld_handler_t ip_up_hnd; #ifdef RADIUS char *tmp_fname; int radattr_saved:1; #endif int started:1; int res; in_addr_t ipv4_addr; in_addr_t ipv4_peer_addr; }; static struct pppd_compat_pd *find_pd(struct ap_session *ses); static void fill_argv(char **argv, struct pppd_compat_pd *pd, char *path); static void fill_env(char **env, char *mem, struct pppd_compat_pd *pd); #ifdef RADIUS static void remove_radattr(struct pppd_compat_pd *); static void write_radattr(struct pppd_compat_pd *, struct rad_packet_t *pack); #endif static void fork_queue_wakeup() { struct pppd_compat_pd *pd; if (!conf_fork_limit) return; pthread_mutex_lock(&queue_lock); if (!list_empty(&queue0)) { pd = list_entry(queue0.next, typeof(*pd), entry); list_del(&pd->entry); pthread_mutex_unlock(&queue_lock); triton_context_wakeup(pd->ses->ctrl->ctx); return; } if (!list_empty(&queue1)) { pd = list_entry(queue1.next, typeof(*pd), entry); list_del(&pd->entry); pthread_mutex_unlock(&queue_lock); triton_context_wakeup(pd->ses->ctrl->ctx); return; } --fork_cnt; pthread_mutex_unlock(&queue_lock); } static void check_fork_limit(struct pppd_compat_pd *pd, struct list_head *queue) { if (!conf_fork_limit) return; pthread_mutex_lock(&queue_lock); if (fork_cnt >= conf_fork_limit) { log_ppp_debug("pppd_compat: sleep\n"); list_add_tail(&pd->entry, queue); pthread_mutex_unlock(&queue_lock); triton_context_schedule(); log_ppp_debug("pppd_compat: wakeup\n"); } else { ++fork_cnt; pthread_mutex_unlock(&queue_lock); } } static void ip_pre_up_handler(struct sigchld_handler_t *h, int status) { struct pppd_compat_pd *pd = container_of(h, typeof(*pd), hnd); fork_queue_wakeup(); if (conf_verbose) { log_switch(NULL, pd->ses); log_ppp_info2("pppd_compat: ip-pre-up finished (%i)\n", status); } pd->res = status; triton_context_wakeup(pd->ses->ctrl->ctx); } static void ses_ip_up_handler(long status) { log_ppp_info2("pppd_compat: ip-up finished (%li)\n", status); } static void ip_up_handler(struct sigchld_handler_t *h, int status) { struct pppd_compat_pd *pd = container_of(h, typeof(*pd), ip_up_hnd); fork_queue_wakeup(); if (conf_verbose) triton_context_call(pd->ses->ctrl->ctx, (triton_event_func)ses_ip_up_handler, (void *)(long)status); } static void ip_down_handler(struct sigchld_handler_t *h, int status) { struct pppd_compat_pd *pd = container_of(h, typeof(*pd), hnd); fork_queue_wakeup(); if (conf_verbose) { log_switch(NULL, pd->ses); log_ppp_info2("pppd_compat: ip-down finished (%i)\n", status); } triton_context_wakeup(pd->ses->ctrl->ctx); } static void ip_change_handler(struct sigchld_handler_t *h, int status) { struct pppd_compat_pd *pd = container_of(h, typeof(*pd), hnd); fork_queue_wakeup(); if (conf_verbose) { log_switch(NULL, pd->ses); log_ppp_info2("pppd_compat: ip-change finished (%i)\n", status); } pd->res = status; triton_context_wakeup(pd->ses->ctrl->ctx); } static void ev_ses_starting(struct ap_session *ses) { struct pppd_compat_pd *pd; pd = _malloc(sizeof(*pd)); if (!pd) { log_emerg("pppd_compat: out of memory\n"); return; } memset(pd, 0, sizeof(*pd)); pd->pd.key = &pd_key; pd->ses = ses; pd->ip_up_hnd.handler = ip_up_handler; list_add_tail(&pd->pd.entry, &ses->pd_list); } static void ev_ses_pre_up(struct ap_session *ses) { pid_t pid; char *argv[8]; char *env[ENV_MAX]; char env_mem[ENV_MEM]; char ipaddr[17]; char peer_ipaddr[17]; struct pppd_compat_pd *pd; pd = find_pd(ses); if (!pd) return; #ifdef RADIUS if (pd->tmp_fname) { char fname[PATH_MAX]; if (conf_radattr_prefix) { sprintf(fname, "%s.%s", conf_radattr_prefix, ses->ifname); rename(pd->tmp_fname, fname); } else unlink(pd->tmp_fname); _free(pd->tmp_fname); pd->tmp_fname = NULL; } #endif if (ses->ipv4) { pd->ipv4_addr = ses->ipv4->addr; pd->ipv4_peer_addr = ses->ipv4->peer_addr; } if (!conf_ip_pre_up) return; argv[4] = ipaddr; argv[5] = peer_ipaddr; fill_argv(argv, pd, conf_ip_pre_up); fill_env(env, env_mem, pd); check_fork_limit(pd, &queue0); sigchld_lock(); pid = fork(); if (pid > 0) { pd->hnd.pid = pid; pd->hnd.handler = ip_pre_up_handler; sigchld_register_handler(&pd->hnd); if (conf_verbose) log_ppp_info2("pppd_compat: ip-pre-up started (pid %i)\n", pid); sigchld_unlock(); triton_context_schedule(); pthread_mutex_lock(&pd->hnd.lock); pthread_mutex_unlock(&pd->hnd.lock); if (pd->res != 0) { ap_session_terminate(ses, pd->res > 127 ? TERM_NAS_ERROR : TERM_ADMIN_RESET, 0); return; } pd->started = 1; } else if (pid == 0) { sigset_t set; sigfillset(&set); pthread_sigmask(SIG_UNBLOCK, &set, NULL); net->enter_ns(); execve(conf_ip_pre_up, argv, env); net->exit_ns(); log_emerg("pppd_compat: exec '%s': %s\n", conf_ip_pre_up, strerror(errno)); _exit(EXIT_FAILURE); } else { sigchld_unlock(); fork_queue_wakeup(); log_error("pppd_compat: fork: %s\n", strerror(errno)); ap_session_terminate(ses, TERM_NAS_ERROR, 0); } } static void ev_ses_started(struct ap_session *ses) { pid_t pid; char *argv[8]; char *env[ENV_MAX]; char env_mem[ENV_MEM]; char ipaddr[17]; char peer_ipaddr[17]; struct pppd_compat_pd *pd; pd = find_pd(ses); if (!pd) return; pd->started = 1; if (!conf_ip_up) return; argv[4] = ipaddr; argv[5] = peer_ipaddr; fill_argv(argv, pd, conf_ip_up); fill_env(env, env_mem, pd); check_fork_limit(pd, &queue1); sigchld_lock(); pid = fork(); if (pid > 0) { pd->ip_up_hnd.pid = pid; sigchld_register_handler(&pd->ip_up_hnd); if (conf_verbose) log_ppp_info2("pppd_compat: ip-up started (pid %i)\n", pid); sigchld_unlock(); } else if (pid == 0) { sigset_t set; sigfillset(&set); pthread_sigmask(SIG_UNBLOCK, &set, NULL); net->enter_ns(); execve(conf_ip_up, argv, env); net->exit_ns(); log_emerg("pppd_compat: exec '%s': %s\n", conf_ip_up, strerror(errno)); _exit(EXIT_FAILURE); } else { sigchld_unlock(); fork_queue_wakeup(); log_error("pppd_compat: fork: %s\n", strerror(errno)); } } static void ev_ses_finished(struct ap_session *ses) { pid_t pid; char *argv[8]; char *env[ENV_MAX]; char env_mem[ENV_MEM]; char ipaddr[17]; char peer_ipaddr[17]; struct pppd_compat_pd *pd = find_pd(ses); if (!pd) return; if (pd->ip_up_hnd.pid) { pthread_mutex_lock(&pd->ip_up_hnd.lock); if (pd->ip_up_hnd.pid) { log_ppp_warn("pppd_compat: ip-up is not yet finished, terminating it ...\n"); kill(pd->ip_up_hnd.pid, SIGTERM); } pthread_mutex_unlock(&pd->ip_up_hnd.lock); } if (pd->started && conf_ip_down) { argv[4] = ipaddr; argv[5] = peer_ipaddr; fill_argv(argv, pd, conf_ip_down); fill_env(env, env_mem, pd); check_fork_limit(pd, &queue1); sigchld_lock(); pid = fork(); if (pid > 0) { pd->hnd.pid = pid; pd->hnd.handler = ip_down_handler; sigchld_register_handler(&pd->hnd); if (conf_verbose) log_ppp_info2("pppd_compat: ip-down started (pid %i)\n", pid); sigchld_unlock(); triton_context_schedule(); pthread_mutex_lock(&pd->hnd.lock); pthread_mutex_unlock(&pd->hnd.lock); } else if (pid == 0) { sigset_t set; sigfillset(&set); pthread_sigmask(SIG_UNBLOCK, &set, NULL); net->enter_ns(); execve(conf_ip_down, argv, env); net->exit_ns(); log_emerg("pppd_compat: exec '%s': %s\n", conf_ip_down, strerror(errno)); _exit(EXIT_FAILURE); } else { sigchld_unlock(); fork_queue_wakeup(); log_error("pppd_compat: fork: %s\n", strerror(errno)); } } if (pd->ip_up_hnd.pid) { pthread_mutex_lock(&pd->ip_up_hnd.lock); if (pd->ip_up_hnd.pid) { log_ppp_warn("pppd_compat: ip-up is not yet finished, killing it ...\n"); kill(pd->ip_up_hnd.pid, SIGKILL); pthread_mutex_unlock(&pd->ip_up_hnd.lock); if (sigchld_unregister_handler(&pd->ip_up_hnd)) fork_queue_wakeup(); } else pthread_mutex_unlock(&pd->ip_up_hnd.lock); } #ifdef RADIUS if (pd->radattr_saved) remove_radattr(pd); #endif list_del(&pd->pd.entry); _free(pd); } #ifdef RADIUS static void ev_radius_access_accept(struct ev_radius_t *ev) { struct pppd_compat_pd *pd = find_pd(ev->ses); if (!pd) return; if (!conf_radattr_prefix) return; write_radattr(pd, ev->reply); pd->radattr_saved = 1; } static void ev_radius_coa(struct ev_radius_t *ev) { pid_t pid; char *argv[8]; char *env[ENV_MAX]; char env_mem[ENV_MEM]; char ipaddr[17]; char peer_ipaddr[17]; struct pppd_compat_pd *pd = find_pd(ev->ses); if (!pd) return; if (!pd->radattr_saved) return; write_radattr(pd, ev->request); if (conf_ip_change) { argv[4] = ipaddr; argv[5] = peer_ipaddr; fill_argv(argv, pd, conf_ip_change); fill_env(env, env_mem, pd); check_fork_limit(pd, &queue1); sigchld_lock(); pid = fork(); if (pid > 0) { pd->hnd.pid = pid; pd->hnd.handler = ip_change_handler; sigchld_register_handler(&pd->hnd); sigchld_unlock(); if (conf_verbose) log_ppp_info2("pppd_compat: ip-change started (pid %i)\n", pid); triton_context_schedule(); if (!ev->res) ev->res = pd->res; } else if (pid == 0) { net->enter_ns(); execve(conf_ip_change, argv, env); net->exit_ns(); log_emerg("pppd_compat: exec '%s': %s\n", conf_ip_change, strerror(errno)); _exit(EXIT_FAILURE); } else { sigchld_unlock(); fork_queue_wakeup(); log_error("pppd_compat: fork: %s\n", strerror(errno)); } } } static void remove_radattr(struct pppd_compat_pd *pd) { char fname[PATH_MAX]; if (pd->tmp_fname) { unlink(pd->tmp_fname); _free(pd->tmp_fname); } else { sprintf(fname, "%s.%s", conf_radattr_prefix, pd->ses->ifname); unlink(fname); sprintf(fname, "%s_old.%s", conf_radattr_prefix, pd->ses->ifname); unlink(fname); } } static void write_radattr(struct pppd_compat_pd *pd, struct rad_packet_t *pack) { struct ap_session *ses = pd->ses; struct rad_attr_t *attr; struct rad_dict_value_t *val; FILE *f = NULL; char fname1[PATH_MAX], fname2[PATH_MAX]; int fd, i; in_addr_t addr; char ip_str[50]; if (ses->state == AP_STATE_ACTIVE) { sprintf(fname1, "%s.%s", conf_radattr_prefix, ses->ifname); sprintf(fname2, "%s_old.%s", conf_radattr_prefix, ses->ifname); if (rename(fname1, fname2)) log_ppp_warn("pppd_compat: rename: %s\n", strerror(errno)); f = fopen(fname1, "w"); } else { sprintf(fname1, "%s.XXXXXX", conf_radattr_prefix); fd = mkstemp(fname1); if (fd < 0) log_ppp_warn("pppd_compat: mkstemp: %s\n", strerror(errno)); else { fchmod(fd, 0644); f = fdopen(fd, "w"); } } if (f) { list_for_each_entry(attr, &pack->attrs, entry) { fprintf(f, "%s ", attr->attr->name); switch (attr->attr->type) { case ATTR_TYPE_INTEGER: val = rad_dict_find_val(attr->attr, attr->val); if (val) fprintf(f, "%s\n", val->name); else fprintf(f, "%i\n", attr->val.integer); break; case ATTR_TYPE_STRING: fprintf(f, "%s\n", attr->val.string); break; case ATTR_TYPE_OCTETS: for (i = 0; i < attr->len; i++) fprintf(f, "%02X", attr->val.octets[i]); fprintf(f, "\n"); break; case ATTR_TYPE_IPADDR: addr = ntohl(attr->val.ipaddr); fprintf(f, "%i.%i.%i.%i\n", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); break; case ATTR_TYPE_DATE: fprintf(f, "%lu\n", (unsigned long) attr->val.date); break; case ATTR_TYPE_IPV6PREFIX: inet_ntop(AF_INET6, &attr->val.ipv6prefix.prefix, ip_str, sizeof(ip_str)); fprintf(f, "%s/%i\n", ip_str, attr->val.ipv6prefix.len); break; case ATTR_TYPE_IPV6ADDR: inet_ntop(AF_INET6, &attr->val.ipv6addr, ip_str, sizeof(ip_str)); fprintf(f, "%s\n", ip_str); break; } } fclose(f); if (ses->state == AP_STATE_STARTING) pd->tmp_fname = _strdup(fname1); } else log_ppp_warn("pppd_compat: failed to create '%s': %s\n", fname1, strerror(errno)); } #endif static struct pppd_compat_pd *find_pd(struct ap_session *ses) { struct ap_private *pd; struct pppd_compat_pd *cpd; list_for_each_entry(pd, &ses->pd_list, entry) { if (pd->key == &pd_key) { cpd = container_of(pd, typeof(*cpd), pd); return cpd; } } //log_ppp_warn("pppd_compat: pd not found\n"); return NULL; } static void fill_argv(char **argv, struct pppd_compat_pd *pd, char *path) { argv[0] = path; argv[1] = pd->ses->ifname; argv[2] = "none"; argv[3] = "0"; u_inet_ntoa(pd->ipv4_addr, argv[4]); u_inet_ntoa(pd->ipv4_peer_addr, argv[5]); argv[6] = pd->ses->ctrl->calling_station_id; argv[7] = NULL; } static void fill_env(char **env, char *mem, struct pppd_compat_pd *pd) { struct ap_session *ses = pd->ses; size_t mem_sz = ENV_MEM; int write_sz; int n = 0; env[n] = mem; write_sz = snprintf(mem, mem_sz, "PEERNAME=%s", pd->ses->username); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; env[n] = mem; write_sz = snprintf(mem, mem_sz, "CALLING_SID=%s", pd->ses->ctrl->calling_station_id); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; env[n] = mem; write_sz = snprintf(mem, mem_sz, "CALLED_SID=%s", pd->ses->ctrl->called_station_id); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; if (ses->ipv6 && !list_empty(&ses->ipv6->addr_list)) { ///FIXME only first address is passed to env struct ipv6db_addr_t *a = list_first_entry(&ses->ipv6->addr_list, typeof(*a), entry); char ip6_buf[INET6_ADDRSTRLEN]; struct in6_addr addr; build_ip6_addr(a, ses->ipv6->peer_intf_id, &addr); env[n] = mem; write_sz = snprintf(mem, mem_sz, "IPV6_PREFIX=%s/%i", inet_ntop(AF_INET6, &addr, ip6_buf, sizeof(ip6_buf)), a->prefix_len); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; } if (ses->ipv6_dp) { ///FIXME only first prefix is passed to env struct ipv6db_addr_t *a = list_first_entry(&ses->ipv6_dp->prefix_list, typeof(*a), entry); char ip6_buf[INET6_ADDRSTRLEN]; env[n] = mem; write_sz = snprintf(mem, mem_sz, "IPV6_DELEGATED_PREFIX=%s/%i", inet_ntop(AF_INET6, &a->addr, ip6_buf, sizeof(ip6_buf)), a->prefix_len); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; } if (pd->ses->stop_time) { uint64_t gword_sz = (uint64_t)UINT32_MAX + 1; uint64_t tx_bytes; uint64_t rx_bytes; tx_bytes = ses->acct_tx_bytes + gword_sz * ses->acct_output_gigawords; rx_bytes = ses->acct_rx_bytes + gword_sz * ses->acct_input_gigawords; env[n] = mem; write_sz = snprintf(mem, mem_sz, "CONNECT_TIME=%lu", (unsigned long)(pd->ses->stop_time - pd->ses->start_time)); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; env[n] = mem; write_sz = snprintf(mem, mem_sz, "BYTES_SENT=%" PRIu64, tx_bytes); if (write_sz < 0 || write_sz >= mem_sz) goto out; mem_sz -= write_sz + 1; mem += write_sz + 1; ++n; env[n] = mem; write_sz = snprintf(mem, mem_sz, "BYTES_RCVD=%" PRIu64, rx_bytes); if (write_sz < 0 || write_sz >= mem_sz) goto out; ++n; } out: env[n] = NULL; } static void load_config() { const char *opt; conf_ip_pre_up = conf_get_opt("pppd-compat", "ip-pre-up"); if (conf_ip_pre_up && access(conf_ip_pre_up, R_OK | X_OK)) { log_error("pppd_compat: %s: %s\n", conf_ip_pre_up, strerror(errno)); conf_ip_pre_up = NULL; } conf_ip_up = conf_get_opt("pppd-compat", "ip-up"); if (conf_ip_up && access(conf_ip_up, R_OK | X_OK)) { log_error("pppd_compat: %s: %s\n", conf_ip_up, strerror(errno)); conf_ip_up = NULL; } conf_ip_down = conf_get_opt("pppd-compat", "ip-down"); if (conf_ip_down && access(conf_ip_down, R_OK | X_OK)) { log_error("pppd_compat: %s: %s\n", conf_ip_down, strerror(errno)); conf_ip_down = NULL; } conf_ip_change = conf_get_opt("pppd-compat", "ip-change"); if (conf_ip_change && access(conf_ip_change, R_OK | X_OK)) { log_error("pppd_compat: %s: %s\n", conf_ip_change, strerror(errno)); conf_ip_change = NULL; } conf_radattr_prefix = conf_get_opt("pppd-compat", "radattr-prefix"); opt = conf_get_opt("pppd-compat", "verbose"); if (opt) conf_verbose = atoi(opt); else conf_verbose = 0; opt = conf_get_opt("pppd-compat", "fork-limit"); if (opt) conf_fork_limit = atoi(opt); else conf_fork_limit = sysconf(_SC_NPROCESSORS_ONLN)*2; } static void init(void) { load_config(); triton_event_register_handler(EV_SES_STARTING, (triton_event_func)ev_ses_starting); triton_event_register_handler(EV_SES_PRE_UP, (triton_event_func)ev_ses_pre_up); triton_event_register_handler(EV_SES_STARTED, (triton_event_func)ev_ses_started); triton_event_register_handler(EV_SES_PRE_FINISHED, (triton_event_func)ev_ses_finished); triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); #ifdef RADIUS if (triton_module_loaded("radius")) { triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); triton_event_register_handler(EV_RADIUS_COA, (triton_event_func)ev_radius_coa); } #endif } DEFINE_INIT(100, init);