#include #include #include #include #include #include #include #include #include #include "events.h" #include "ipdb.h" #include "list.h" #include "log.h" #include "spinlock.h" #ifdef RADIUS #include "radius.h" #endif #include "memdebug.h" enum ippool_type { IPPOOL_ADDRESS, IPPOOL_PREFIX }; struct ippool_t { struct list_head entry; char *name; struct list_head gw_list; struct list_head items; struct ippool_t *next; spinlock_t lock; }; struct ippool_item_t { struct list_head entry; struct ippool_t *pool; struct ipv6db_item_t it; }; struct dppool_item_t { struct list_head entry; struct ippool_t *pool; struct ipv6db_prefix_t it; }; #ifdef RADIUS static int conf_vendor = 0; static int conf_dppool_attr = 171; // Delegated-IPv6-Prefix-Pool static int conf_ippool_attr = 172; // Stateful-IPv6-Address-Pool #endif static LIST_HEAD(ippool_list); static LIST_HEAD(dppool_list); static struct ipdb_t ipdb; static struct in6_addr conf_gw_addr; static struct ippool_t *def_ippool; static struct ippool_t *def_dppool; static void in6_addr_add(struct in6_addr *res, const struct in6_addr *arg) { uint16_t n = 0; int i; for (i = 15; i >= 0; i--) { n = (uint16_t)res->s6_addr[i] + arg->s6_addr[i] + (n >> 8); res->s6_addr[i] = n & 0xff; } } static int in6_addr_cmp(const struct in6_addr *n1, const struct in6_addr *n2) { int i; for (i = 0; i < 16; i++) { if (n1->s6_addr[i] < n2->s6_addr[i]) return -1; if (n1->s6_addr[i] > n2->s6_addr[i]) return 1; } return 0; } static struct ippool_t *create_pool(enum ippool_type type, char *name) { struct ippool_t *pool = malloc(sizeof(*pool)); struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &dppool_list : &ippool_list; memset(pool, 0, sizeof(*pool)); pool->name = name; INIT_LIST_HEAD(&pool->items); spinlock_init(&pool->lock); if (name) list_add_tail(&pool->entry, pool_list); return pool; } static struct ippool_t *find_pool(enum ippool_type type, char *name, int create) { struct ippool_t *pool; struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &dppool_list : &ippool_list; list_for_each_entry(pool, pool_list, entry) { if (!strcmp(pool->name, name)) return pool; } if (create) return create_pool(type, name); return NULL; } static void generate_ippool(struct ippool_t *pool, struct in6_addr *addr, int mask, int prefix_len) { struct ippool_item_t *it; struct ipv6db_addr_t *a; struct in6_addr ip, end, step; memcpy(&ip, addr, sizeof(ip)); memcpy(&end, addr, sizeof(end)); if (mask > 64) *(uint64_t *)(end.s6_addr + 8) = htobe64(be64toh(*(uint64_t *)(end.s6_addr + 8)) | ((1llu << (128 - mask)) - 1)); else { memset(end.s6_addr + 8, 0xff, 8); *(uint64_t *)end.s6_addr = htobe64(be64toh(*(uint64_t *)end.s6_addr) | ((1llu << (64 - mask)) - 1)); } memset(&step, 0, sizeof(step)); if (prefix_len > 64) *(uint64_t *)(step.s6_addr + 8) = htobe64(1llu << (128 - prefix_len)); else *(uint64_t *)step.s6_addr = htobe64(1llu << (64 - prefix_len)); while (in6_addr_cmp(&ip, &end) <= 0) { it = malloc(sizeof(*it)); memset(it, 0, sizeof(*it)); it->pool = pool; it->it.owner = &ipdb; INIT_LIST_HEAD(&it->it.addr_list); a = malloc(sizeof(*a)); memset(a, 0, sizeof(*a)); memcpy(&a->addr, &ip, sizeof(ip)); a->prefix_len = prefix_len; list_add_tail(&a->entry, &it->it.addr_list); list_add_tail(&it->entry, &pool->items); in6_addr_add(&ip, &step); } } static void generate_dppool(struct ippool_t *pool, struct in6_addr *addr, int mask, int prefix_len) { struct dppool_item_t *it; struct in6_addr ip, end, step; struct ipv6db_addr_t *a; memcpy(&ip, addr, sizeof(ip)); memcpy(&end, addr, sizeof(end)); if (mask > 64) *(uint64_t *)(end.s6_addr + 8) = htobe64(be64toh(*(uint64_t *)(end.s6_addr + 8)) | ((1llu << (128 - mask)) - 1)); else { memset(end.s6_addr + 8, 0xff, 8); *(uint64_t *)end.s6_addr = htobe64(be64toh(*(uint64_t *)end.s6_addr) | ((1llu << (64 - mask)) - 1)); } memset(&step, 0, sizeof(step)); if (prefix_len > 64) *(uint64_t *)(step.s6_addr + 8) = htobe64(1llu << (128 - prefix_len)); else *(uint64_t *)step.s6_addr = htobe64(1llu << (64 - prefix_len)); while (in6_addr_cmp(&ip, &end) <= 0) { it = malloc(sizeof(*it)); memset(it, 0, sizeof(*it)); it->pool = pool; it->it.owner = &ipdb; INIT_LIST_HEAD(&it->it.prefix_list); a = malloc(sizeof(*a)); memset(a, 0, sizeof(*a)); memcpy(&a->addr, &ip, sizeof(ip)); a->prefix_len = prefix_len; list_add_tail(&a->entry, &it->it.prefix_list); list_add_tail(&it->entry, &pool->items); in6_addr_add(&ip, &step); } } static void add_prefix(enum ippool_type type, struct ippool_t *pool, const char *_val) { char *val = _strdup(_val); char *ptr1, *ptr2; struct in6_addr addr; int prefix_len; int mask; ptr1 = strchr(val, '/'); if (!ptr1) goto err; *ptr1 = 0; ptr2 = strchr(ptr1 + 1, ','); if (!ptr2) goto err; *ptr2 = 0; if (inet_pton(AF_INET6, val, &addr) == 0) goto err; if (sscanf(ptr1 + 1, "%i", &mask) != 1) goto err; if (mask < 7 || mask > 127) goto err; if (sscanf(ptr2 + 1, "%i", &prefix_len) != 1) goto err; if (prefix_len > 128 || prefix_len < mask) goto err; if (type == IPPOOL_PREFIX) generate_dppool(pool, &addr, mask, prefix_len); else generate_ippool(pool, &addr, mask, prefix_len); _free(val); return; err: log_error("ipv6_pool: failed to parse '%s'\n", _val); _free(val); } static struct ipv6db_item_t *get_ip(struct ap_session *ses) { struct ippool_item_t *it; struct ipv6db_addr_t *a; struct ippool_t *pool; if (ses->ipv6_pool_name) pool = find_pool(IPPOOL_ADDRESS, ses->ipv6_pool_name, 0); else pool = def_ippool; if (!pool) return NULL; again: spin_lock(&pool->lock); if (!list_empty(&pool->items)) { it = list_entry(pool->items.next, typeof(*it), entry); list_del(&it->entry); } else it = NULL; spin_unlock(&pool->lock); if (it) { a = list_entry(it->it.addr_list.next, typeof(*a), entry); if (a->prefix_len == 128) { memcpy(&it->it.intf_id, conf_gw_addr.s6_addr + 8, 8); memcpy(&it->it.peer_intf_id, a->addr.s6_addr + 8, 8); } else { it->it.intf_id = 0; it->it.peer_intf_id = 0; } return &it->it; } else if (pool->next) { pool = pool->next; goto again; } return NULL; } static void put_ip(struct ap_session *ses, struct ipv6db_item_t *it) { struct ippool_item_t *pit = container_of(it, typeof(*pit), it); spin_lock(&pit->pool->lock); list_add_tail(&pit->entry, &pit->pool->items); spin_unlock(&pit->pool->lock); } static struct ipv6db_prefix_t *get_dp(struct ap_session *ses) { struct dppool_item_t *it; struct ippool_t *pool; if (ses->ipv6_pool_name) pool = find_pool(IPPOOL_PREFIX, ses->ipv6_dppool_name, 0); else pool = def_dppool; again: spin_lock(&pool->lock); if (!list_empty(&pool->items)) { it = list_entry(pool->items.next, typeof(*it), entry); list_del(&it->entry); } else it = NULL; spin_unlock(&pool->lock); if (it) return &it->it; else if (pool->next) { pool = pool->next; goto again; } return NULL; } static void put_dp(struct ap_session *ses, struct ipv6db_prefix_t *it) { struct dppool_item_t *pit = container_of(it, typeof(*pit), it); spin_lock(&pit->pool->lock); list_add_tail(&pit->entry, &pit->pool->items); spin_unlock(&pit->pool->lock); } static struct ipdb_t ipdb = { .get_ipv6 = get_ip, .put_ipv6 = put_ip, .get_ipv6_prefix = get_dp, .put_ipv6_prefix = put_dp, }; #ifdef RADIUS static void ev_radius_access_accept(struct ev_radius_t *ev) { struct rad_attr_t *attr; struct ap_session *ses = ev->ses; list_for_each_entry(attr, &ev->reply->attrs, entry) { if (attr->attr->type != ATTR_TYPE_STRING) continue; if (attr->vendor && attr->vendor->id != conf_vendor) continue; if (!attr->vendor && conf_vendor) continue; if (conf_dppool_attr && conf_dppool_attr == attr->attr->id) { if (ses->ipv6_dppool_name) _free(ses->ipv6_dppool_name); ses->ipv6_dppool_name = _strdup(attr->val.string); } else if (conf_ippool_attr && conf_ippool_attr == attr->attr->id) { if (ses->ipv6_pool_name) _free(ses->ipv6_pool_name); ses->ipv6_pool_name = _strdup(attr->val.string); } } } static int parse_attr_opt(const char *opt) { struct rad_dict_attr_t *attr; struct rad_dict_vendor_t *vendor; if (conf_vendor) vendor = rad_dict_find_vendor_id(conf_vendor); else vendor = NULL; if (conf_vendor) { if (vendor) attr = rad_dict_find_vendor_attr(vendor, opt); else attr = NULL; } else attr = rad_dict_find_attr(opt); if (attr) return attr->id; return atoi(opt); } static int parse_vendor_opt(const char *opt) { struct rad_dict_vendor_t *vendor; vendor = rad_dict_find_vendor_name(opt); if (vendor) return vendor->id; return atoi(opt); } #endif static void parse_options(enum ippool_type type, const char *opt, char **pool_name, struct ippool_t **next) { char *ptr1, *ptr2, *tmp; ptr1 = strstr(opt, ",name="); if (ptr1) { ptr1 += sizeof(",name=") - 1; ptr2 = strchrnul(ptr1, ','); *pool_name = _strndup(ptr1, ptr2 - ptr1); } ptr1 = strstr(opt, ",next="); if (ptr1) { ptr1 += sizeof(",next=") - 1; ptr2 = strchrnul(ptr1, ','); if (*ptr2 == ',') { tmp = alloca(ptr2 - ptr1 + 1); strncpy(tmp, ptr1, ptr2 - ptr1); ptr1 = tmp; } *next = find_pool(type, ptr1, 0); if (!(*next)) log_error("ipv6_pool: %s: next pool not found\n", opt); } } static void ippool_init1(void) { ipdb_register(&ipdb); } static void ippool_init2(void) { struct conf_sect_t *s = conf_get_section("ipv6-pool"); struct conf_option_t *opt; struct ippool_t *pool, *next; char *pool_name, *val; enum ippool_type type; #ifdef RADIUS int dppool_attr = 0, ippool_attr = 0; #endif if (!s) return; def_ippool = create_pool(IPPOOL_ADDRESS, NULL); def_dppool = create_pool(IPPOOL_PREFIX, NULL); list_for_each_entry(opt, &s->items, entry) { #ifdef RADIUS if (triton_module_loaded("radius")) { if (!strcmp(opt->name, "vendor")) { conf_vendor = parse_vendor_opt(opt->val); continue; } if (!strcmp(opt->name, "attr-prefix")) { dppool_attr = parse_attr_opt(opt->val); continue; } if (!strcmp(opt->name, "attr-address")) { ippool_attr = parse_attr_opt(opt->val); continue; } } #endif if (!strcmp(opt->name, "gw-ip6-address")) { if (inet_pton(AF_INET6, opt->val, &conf_gw_addr) == 0) log_error("ipv6_pool: failed to parse gw-ip6-address\n"); continue; } else if (!strcmp(opt->name, "delegate")) { type = IPPOOL_PREFIX; val = opt->val; pool = def_dppool; } else { type = IPPOOL_ADDRESS; val = opt->name; pool = def_ippool; } pool_name = NULL; parse_options(type, opt->raw, &pool_name, &next); if (pool_name) pool = find_pool(type, pool_name, 1); add_prefix(type, pool, val); pool->next = next; } #ifdef RADIUS if (triton_module_loaded("radius")) { if (conf_vendor || dppool_attr) conf_dppool_attr = dppool_attr; if (conf_vendor || ippool_attr) conf_ippool_attr = ippool_attr; triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); } #endif } DEFINE_INIT(51, ippool_init1); DEFINE_INIT2(52, ippool_init2);