diff options
| author | Denys Fedoryshchenko <denys.f@collabora.com> | 2026-06-03 02:23:41 +0300 |
|---|---|---|
| committer | Denys Fedoryshchenko <denys.f@collabora.com> | 2026-06-03 14:57:45 +0300 |
| commit | 8afec90643a6e6d923032ae5c50d6c18e7a8b0c3 (patch) | |
| tree | 3d97ec841068a6d5a32fd3c2ac9a3555e61c2e7a | |
| parent | 8494d63943e69e2f7217ca401f6b9ba46b44cca8 (diff) | |
| download | accel-ppp-8afec90643a6e6d923032ae5c50d6c18e7a8b0c3.tar.gz accel-ppp-8afec90643a6e6d923032ae5c50d6c18e7a8b0c3.zip | |
ippool/ipv6pool: bitmap allocator + online reconfiguration
Replace the pre-generated free-list (one heap node per allocatable
address/prefix) with a per-pool bitmap. Each pool holds a list of
contiguous ranges, each range owning one bitmap (1 bit per unit);
a lease is a small per-session malloc wrapper around the ipdb item,
so pool memory is no longer shared or mutated by sessions.
Memory now scales with capacity at ~1 bit/unit instead of ~96B (v4) /
~128B (v6) per unit; startup, `show ippool`, and backup-restore are
O(1) instead of O(N)/O(N*M). Oversized IPv6 ranges (prefix_len-mask
> 24) are rejected at parse time instead of OOMing in the malloc loop.
The ipdb_t vtable, the owner-based put dispatch, ipdb.h structs, and
struct ap_session are unchanged; RADIUS and chap-secrets backends are
untouched (reconcile filters by owner).
Preserved behavior: p2p/net30 allocators (via a step/gw_offset/
peer_offset geometry triple), shuffle (randomized scan start), named
pools, next-chains, gw-ip-address, RADIUS pool-name attrs, and the
USE_BACKUP save/restore path. `gw=` is now accepted-and-ignored (its
per-address local gateway was already overridden at allocation time).
New: online reconfiguration. An EV_CONFIG_RELOAD handler rebuilds the
pool set and reconciles live sessions (sessions are the source of
truth, the bitmap is rebuilt from them) under
pool_set_rwlock(write) -> ses_lock(read) -> per-pool spinlock.
Sessions whose address left the pools are handled per a new
`reload-orphan = keep|disconnect` knob (default keep); foreign
in-range addresses are reserved to avoid duplicate assignment.
Adds extra/bitpool.h (shared bit-array helpers) and a standalone
extra/bitpool_test.c (not wired into cmake) covering the bitmap and
the v4/v6 address<->bit math, cross-checked against an __int128
reference.
Signed-off-by: Denys Fedoryshchenko <denys.f@collabora.com>
| -rw-r--r-- | accel-pppd/accel-ppp.conf | 4 | ||||
| -rw-r--r-- | accel-pppd/accel-ppp.conf.5 | 34 | ||||
| -rw-r--r-- | accel-pppd/extra/bitpool.h | 88 | ||||
| -rw-r--r-- | accel-pppd/extra/bitpool_test.c | 256 | ||||
| -rw-r--r-- | accel-pppd/extra/ippool.c | 1167 | ||||
| -rw-r--r-- | accel-pppd/extra/ipv6pool.c | 1101 |
6 files changed, 1880 insertions, 770 deletions
diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf index f9c5245b..591a8ca7 100644 --- a/accel-pppd/accel-ppp.conf +++ b/accel-pppd/accel-ppp.conf @@ -241,6 +241,9 @@ gw-ip-address=192.168.0.1 #vendor=Cisco #attr=Cisco-AVPair attr=Framed-Pool +#shuffle=0 +# on reload, sessions whose address left the pools: keep (default) or disconnect +#reload-orphan=keep 192.168.0.2-255 192.168.1.1-255,name=pool1 192.168.2.1-255,name=pool2 @@ -330,6 +333,7 @@ timeout=60 #vendor= #attr-prefix=Delegated-IPv6-Prefix-Pool #attr-address=Stateful-IPv6-Address-Pool +#reload-orphan=keep fc00:0:1::/48,64 fc00:0:2::/48,64,name=pool1 fc00:0:3::/48,64,name=pool2,next=pool1 diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5 index 076f7137..1e9140fc 100644 --- a/accel-pppd/accel-ppp.conf.5 +++ b/accel-pppd/accel-ppp.conf.5 @@ -1226,16 +1226,24 @@ Configuration of ippool module. Specifies single IP address to be used as local address of ppp interfaces. .TP .BI "shuffle=" 1|0 -Specifies whether to shuffle initial address list. +Specifies whether to assign addresses in randomized order instead of sequentially. +.TP +.BI "reload-orphan=" keep|disconnect +Controls what happens on configuration reload to sessions whose assigned address +no longer belongs to any configured pool (a pool was shrunk or removed). With +.B keep +(default) such sessions keep running and their address is simply no longer tracked; +with +.B disconnect +those sessions are terminated. Reload reconciles all live sessions against the new +pools, so growing or renumbering pools no longer requires a restart. .TP .BI "gw=" range -Specifies range of local address of ppp interfaces if form: -.br -.B x.x.x.x/mask[,name=pool_name][,next=next_pool_name] -(for example 10.0.0.0/8) -.br -.B x.x.x.x-y[,name=pool_name][,next=next_pool_name] -(for example 10.0.0.1-254) +Deprecated and ignored. The per-address local gateway has long been overridden at +allocation time by +.B gw-ip-address +(or 0 for IPoE), so this option no longer has any effect. It is still accepted to +avoid breaking existing configuration files. .TP .BI "tunnel=" range Specifies range of remote address of ppp interfaces if form: @@ -1293,6 +1301,16 @@ Specifies which Radius attribute contains stateful address pool name. .TP .BI "vendor=" vendor If attribute is vendor-specific then specify vendor name in this option. +.TP +.BI "reload-orphan=" keep|disconnect +Same meaning as in the +.B [ip-pool] +section, applied to IPv6 address and delegated-prefix leases on configuration reload. +.PP +Note: each pool range is materialized as a bitmap with one bit per allocatable +prefix, so the number of prefixes in a range (2^(prefix_len-mask)) is capped. Ranges +that would exceed roughly 16.7 million prefixes (prefix_len-mask > 24) are rejected at +parse time with an error rather than allocated. .SH [metrics] .br Exposes daemon statistics over HTTP. Requires the diff --git a/accel-pppd/extra/bitpool.h b/accel-pppd/extra/bitpool.h new file mode 100644 index 00000000..fa739a28 --- /dev/null +++ b/accel-pppd/extra/bitpool.h @@ -0,0 +1,88 @@ +#ifndef __BITPOOL_H +#define __BITPOOL_H + +#include <stdint.h> +#include <stddef.h> + +/* + * Fixed-size bit array shared by the IPv4/IPv6 address pool allocators. + * + * Semantics: bit == 1 => allocated/unavailable, bit == 0 => free. A freshly + * calloc'd map is therefore all-free. The caller is responsible for pre-setting + * to 1 any bit it never wants handed out: + * - "remainder" bits in the final word (indices >= count), and + * - reserved in-range bits (e.g. the p2p gateway collision), + * so the word-at-a-time scan can never return a non-existent unit. + */ + +typedef unsigned long bm_word_t; +#define BM_WORD_BITS (8 * sizeof(bm_word_t)) +#define BM_NWORDS(n) (((n) + BM_WORD_BITS - 1) / BM_WORD_BITS) +#define BM_INVALID ((uint64_t)-1) + +static inline void bm_set(bm_word_t *bm, uint64_t i) +{ + bm[i / BM_WORD_BITS] |= (bm_word_t)1 << (i % BM_WORD_BITS); +} + +static inline void bm_clear(bm_word_t *bm, uint64_t i) +{ + bm[i / BM_WORD_BITS] &= ~((bm_word_t)1 << (i % BM_WORD_BITS)); +} + +static inline int bm_test(const bm_word_t *bm, uint64_t i) +{ + return (bm[i / BM_WORD_BITS] >> (i % BM_WORD_BITS)) & 1; +} + +/* + * Find the first free (0) bit at or after `from`, wrapping once back to 0. + * `count` is the number of valid bits; remainder bits in the final word must + * already be set to 1 by the caller so they are never returned. Returns the + * bit index in [0,count), or BM_INVALID if every bit is used. + * + * Word-at-a-time: a fully-used word (~w == 0) is skipped in one branch, so the + * scan is O(1) amortized for a sparse pool and O(count/word) worst case. + */ +static inline uint64_t bm_find_free(const bm_word_t *bm, uint64_t count, uint64_t from) +{ + uint64_t nwords, fw, i; + unsigned fb; + + if (!count) + return BM_INVALID; + if (from >= count) + from = 0; + + nwords = BM_NWORDS(count); + fw = from / BM_WORD_BITS; + fb = from % BM_WORD_BITS; + + /* Probe nwords+1 times: the start word is examined first with its low bits + * (< from) masked off, and once more at the end with only those low bits, + * so every bit is considered exactly once across the wrap. */ + for (i = 0; i <= nwords; i++) { + uint64_t word = fw + i; + bm_word_t inv; + + if (word >= nwords) + word -= nwords; + + inv = ~bm[word]; + + if (i == 0) + inv &= ~(((bm_word_t)1 << fb) - 1); /* skip bits below `from` */ + else if (i == nwords) + inv &= ((bm_word_t)1 << fb) - 1; /* wrapped: only those skipped bits */ + + if (inv) { + uint64_t bit = word * BM_WORD_BITS + __builtin_ctzl(inv); + if (bit < count) + return bit; + } + } + + return BM_INVALID; +} + +#endif diff --git a/accel-pppd/extra/bitpool_test.c b/accel-pppd/extra/bitpool_test.c new file mode 100644 index 00000000..347f1770 --- /dev/null +++ b/accel-pppd/extra/bitpool_test.c @@ -0,0 +1,256 @@ +/* + * Standalone sanity test for the bitmap IP-pool allocator core. + * + * Not part of the cmake build. Compile and run with: + * gcc -O2 -Wall -o /tmp/bitpool_test accel-pppd/extra/bitpool_test.c && /tmp/bitpool_test + * + * It validates: + * - bitpool.h: bm_set/clear/test, bm_find_free wrap + remainder bits + + * exhaustion, and that every freed bit is re-handed exactly once. + * - IPv4 addr<->bit round-trip for p2p and net30 geometry, incl. alignment + * and out-of-range rejection. + * - IPv6 prefix<->bit round-trip (the exact helpers used by ipv6pool.c), + * cross-checked against __int128 reference math, incl. prefix_len==128, + * mask<=64 / mask>64, and an unaligned base. + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> + +#include "bitpool.h" + +static int failures; +#define CHECK(cond) do { if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0) + +/* ---- mimic a finalized range bitmap: count bits free, remainder bits set ---- */ +static bm_word_t *make_map(uint64_t count) +{ + uint64_t nw = BM_NWORDS(count), b; + bm_word_t *bm = calloc(nw, sizeof(bm_word_t)); + for (b = count; b < nw * BM_WORD_BITS; b++) + bm_set(bm, b); + return bm; +} + +static void test_bitmap_basic(void) +{ + uint64_t count = 200; /* not a multiple of 64 -> exercises remainder */ + bm_word_t *bm = make_map(count); + uint64_t i, bit, n = 0; + char *seen = calloc(count, 1); + + /* allocate the whole pool via rolling cursor; each bit unique and < count */ + uint64_t cursor = 0; + while ((bit = bm_find_free(bm, count, cursor)) != BM_INVALID) { + CHECK(bit < count); + CHECK(!seen[bit]); + seen[bit] = 1; + bm_set(bm, bit); + cursor = bit + 1; + n++; + CHECK(n <= count); + } + CHECK(n == count); /* exactly count handed out */ + for (i = 0; i < count; i++) + CHECK(seen[i]); /* every unit handed out once */ + CHECK(bm_find_free(bm, count, 0) == BM_INVALID); /* exhausted */ + + /* free three, confirm exactly those three come back */ + bm_clear(bm, 5); bm_clear(bm, 130); bm_clear(bm, 199); + memset(seen, 0, count); + for (n = 0; (bit = bm_find_free(bm, count, 0)) != BM_INVALID; n++) { + CHECK(bit == 5 || bit == 130 || bit == 199); + seen[bit] = 1; bm_set(bm, bit); + } + CHECK(n == 3 && seen[5] && seen[130] && seen[199]); + + /* wrap-around: only bit 1 free, search starting past it must still find it */ + memset(bm, 0xff, BM_NWORDS(count) * sizeof(bm_word_t)); + bm_clear(bm, 1); + CHECK(bm_find_free(bm, count, 50) == 1); + CHECK(bm_find_free(bm, count, 0) == 1); + + /* zero-size pool */ + CHECK(bm_find_free(bm, 0, 0) == BM_INVALID); + + free(seen); + free(bm); + printf("ok bitmap basic/wrap/remainder/exhaustion\n"); +} + +/* ---- IPv4 geometry (mirrors ippool.c range_addr_to_bit / bit->addr) ---- */ +static uint64_t v4_addr_to_bit(uint32_t start, uint32_t step, int peer_off, + uint64_t count, uint32_t peer) +{ + uint32_t base, delta; + uint64_t bit; + if (peer < (uint32_t)peer_off) return BM_INVALID; + base = peer - peer_off; + if (base < start) return BM_INVALID; + delta = base - start; + if (delta % step) return BM_INVALID; + bit = delta / step; + if (bit >= count) return BM_INVALID; + return bit; +} + +static void test_v4(void) +{ + /* p2p: 10.0.0.0 - 10.0.1.255 (512 addrs), step 1, peer_off 0 */ + uint32_t start = (10u << 24); + uint32_t end = (10u << 24) | 0x1ff; + uint64_t count = (uint64_t)(end - start) + 1; + uint64_t i; + CHECK(count == 512); + for (i = 0; i < count; i++) { + uint32_t peer = start + (uint32_t)i * 1 + 0; + CHECK(v4_addr_to_bit(start, 1, 0, count, peer) == i); + } + CHECK(v4_addr_to_bit(start, 1, 0, count, start - 1) == BM_INVALID); + CHECK(v4_addr_to_bit(start, 1, 0, count, end + 1) == BM_INVALID); + printf("ok v4 p2p round-trip (512 addrs)\n"); + + /* net30: same range, step 4, gw_off 1, peer_off 2 -> 128 blocks */ + count = ((uint64_t)(end - start) + 1) / 4; + CHECK(count == 128); + for (i = 0; i < count; i++) { + uint32_t base = start + (uint32_t)i * 4; + uint32_t peer = base + 2; /* .2 client */ + CHECK(v4_addr_to_bit(start, 4, 2, count, peer) == i); + /* .1 router and .0/.3 are NOT valid leases */ + CHECK(v4_addr_to_bit(start, 4, 2, count, base + 1) == BM_INVALID); + CHECK(v4_addr_to_bit(start, 4, 2, count, base + 0) == BM_INVALID); + CHECK(v4_addr_to_bit(start, 4, 2, count, base + 3) == BM_INVALID); + } + printf("ok v4 net30 round-trip + alignment (128 blocks)\n"); +} + +/* ---- IPv6 helpers: EXACT copies of the ones in ipv6pool.c ---- */ +static void in6_sub(uint8_t *o, const uint8_t *a, const uint8_t *b) +{ + int i, borrow = 0; + for (i = 15; i >= 0; i--) { + int v = (int)a[i] - b[i] - borrow; + if (v < 0) { v += 256; borrow = 1; } else borrow = 0; + o[i] = v; + } +} +static int in6_shr_u64(const uint8_t *d, int shift, uint64_t *out) +{ + int p; uint64_t v = 0; + for (p = 0; p < 64; p++) { + int sp = shift + p; + if (sp < 128 && (d[15 - (sp >> 3)] & (1 << (sp & 7)))) v |= (uint64_t)1 << p; + } + for (p = shift + 64; p < 128; p++) + if (d[15 - (p >> 3)] & (1 << (p & 7))) return -1; + *out = v; return 0; +} +static void in6_index_to_addr(uint8_t *out, const uint8_t *start, uint64_t i, int shift) +{ + uint8_t add[16] = { 0 }; int p, b, carry = 0; + for (p = 0; p < 64; p++) + if (i & ((uint64_t)1 << p)) { int sp = shift + p; if (sp < 128) add[15 - (sp >> 3)] |= (1 << (sp & 7)); } + memcpy(out, start, 16); + for (b = 15; b >= 0; b--) { int s = out[b] + add[b] + carry; out[b] = s & 0xff; carry = s >> 8; } +} +static int in6_cmp(const uint8_t *a, const uint8_t *b) +{ + int i; for (i = 0; i < 16; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return 0; +} +static uint64_t v6_addr_to_bit(const uint8_t *start, int shift, uint64_t count, const uint8_t *addr) +{ + uint8_t delta[16]; uint64_t i; int p; + if (in6_cmp(addr, start) < 0) return BM_INVALID; + in6_sub(delta, addr, start); + for (p = 0; p < shift; p++) + if (delta[15 - (p >> 3)] & (1 << (p & 7))) return BM_INVALID; + if (in6_shr_u64(delta, shift, &i)) return BM_INVALID; + if (i >= count) return BM_INVALID; + return i; +} + +/* ---- __int128 reference ---- */ +static __uint128_t to_u128(const uint8_t a[16]) { __uint128_t v = 0; int i; for (i = 0; i < 16; i++) v = (v << 8) | a[i]; return v; } +static void from_u128(uint8_t a[16], __uint128_t v) { int i; for (i = 15; i >= 0; i--) { a[i] = v & 0xff; v >>= 8; } } + +static void test_v6_one(const char *label, const uint8_t start[16], int mask, int prefix_len) +{ + int shift = 128 - prefix_len; + __uint128_t s = to_u128(start); + __uint128_t hostmask = (mask == 0) ? ~(__uint128_t)0 : (((__uint128_t)1 << (128 - mask)) - 1); + __uint128_t end = s | hostmask; + uint8_t endb[16]; uint8_t delta[16]; uint64_t span, count, i, step_lo; + __uint128_t step = (__uint128_t)1 << shift; + + from_u128(endb, end); + in6_sub(delta, endb, start); + CHECK(in6_shr_u64(delta, shift, &span) == 0); + count = span + 1; + CHECK(count == (uint64_t)(((end - s) >> shift) + 1)); /* matches reference */ + + /* round-trip boundary + sampled indices */ + uint64_t samples[] = { 0, 1, count / 2, count - 1 }; + for (size_t k = 0; k < sizeof(samples) / sizeof(samples[0]); k++) { + i = samples[k]; + if (i >= count) continue; + uint8_t addr[16]; in6_index_to_addr(addr, start, i, shift); + __uint128_t ref = s + (__uint128_t)i * step; + uint8_t refb[16]; from_u128(refb, ref); + CHECK(memcmp(addr, refb, 16) == 0); /* bit->addr matches ref */ + CHECK(v6_addr_to_bit(start, shift, count, addr) == i); /* addr->bit round-trip */ + /* misaligned address (only when there is room below a prefix) */ + if (shift > 0) { + uint8_t bad[16]; memcpy(bad, addr, 16); bad[15] |= 1; + CHECK(v6_addr_to_bit(start, shift, count, bad) == BM_INVALID); + } + } + /* below start and past end reject */ + if (s > 0) { uint8_t below[16]; from_u128(below, s - 1); CHECK(v6_addr_to_bit(start, shift, count, below) == BM_INVALID); } + { uint8_t past[16]; from_u128(past, s + (__uint128_t)count * step); CHECK(v6_addr_to_bit(start, shift, count, past) == BM_INVALID); } + + (void)step_lo; + printf("ok v6 %s (mask=%d plen=%d shift=%d count=%llu)\n", + label, mask, prefix_len, shift, (unsigned long long)count); +} + +static void test_v6(void) +{ + uint8_t a[16]; + + /* 2001:db8::/48 carved into /64s -> 65536 prefixes (mask<=64) */ + memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; + test_v6_one("/48->/64", a, 48, 64); + + /* mask>64: 2001:db8:0:0:8000::/72 -> /80 (mask>64 path) */ + memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; a[8] = 0x80; + test_v6_one("/72->/80", a, 72, 80); + + /* prefix_len == 128: single addresses, step 1 */ + memset(a, 0, 16); a[0] = 0xfc; a[15] = 0x00; + test_v6_one("/120->/128", a, 120, 128); + + /* unaligned base within the mask host bits (start not on a /64 boundary) */ + memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; a[7] = 0x05; + test_v6_one("/48->/64 unaligned base", a, 48, 64); + + /* big delegation: /32 -> /56 = 2^24 prefixes (the cap boundary) */ + memset(a, 0, 16); a[0] = 0x2a; a[1] = 0x00; + test_v6_one("/32->/56 (cap)", a, 32, 56); +} + +int main(void) +{ + test_bitmap_basic(); + test_v4(); + test_v6(); + if (failures) { + printf("\n%d CHECK(s) FAILED\n", failures); + return 1; + } + printf("\nALL TESTS PASSED\n"); + return 0; +} diff --git a/accel-pppd/extra/ippool.c b/accel-pppd/extra/ippool.c index f839a376..045e3bbf 100644 --- a/accel-pppd/extra/ippool.c +++ b/accel-pppd/extra/ippool.c @@ -3,13 +3,16 @@ #include <stdint.h> #include <unistd.h> #include <string.h> +#include <pthread.h> #include <arpa/inet.h> +#include "triton.h" #include "events.h" #include "log.h" #include "list.h" #include "spinlock.h" #include "backup.h" +#include "ap_session.h" #include "ap_session_backup.h" #include "ipdb.h" @@ -19,123 +22,142 @@ #include "radius.h" #endif +#include "bitpool.h" #include "memdebug.h" -struct ippool_t; - -typedef void (*generate_func)(struct ippool_t *); +/* + * Bitmap IPv4 address pool. + * + * Each pool holds a list of contiguous ranges; each range owns one bitmap + * (1 bit per allocatable block). A lease is a small per-session malloc wrapper + * around the ipv4db_item_t returned to the session, so pool memory is never + * shared/mutated by sessions. The whole pool set is rebuilt and swapped on + * EV_CONFIG_RELOAD, reconciling live sessions against the new ranges. + * + * TODO: a sparse/hierarchical allocator would lift IPPOOL_MAX_BITS; the dense + * bitmap is fine for realistic IPv4 ranges (a /8 sits right at the cap). + */ + +#define IPPOOL_MAX_BITS (1u << 24) /* ~16.7M units, ~2MB bitmap */ + +enum { + ORPHAN_KEEP = 0, /* keep the session, no-op its later put */ + ORPHAN_DISCONNECT, /* terminate sessions whose address left the pools */ +}; -struct ippool_t -{ +/* one contiguous range = one bitmap. step/offsets encode the allocator: + * p2p: step=1, gw_offset=-1 (local addr from gw-ip-address/0), peer_offset=0 + * net30: step=4, gw_offset=1 (.1 router), peer_offset=2 (.2 client) */ +struct ip_range { struct list_head entry; - char *name; - struct list_head gw_list; - struct list_head tunnel_list; - struct list_head items; - uint32_t startip; - uint32_t endip; - struct ippool_t *next; - generate_func generate; - spinlock_t lock; + uint32_t start; /* host order, first block base */ + uint32_t end; /* host order, last address in range */ + uint32_t step; + int gw_offset; + int peer_offset; + uint64_t count; /* number of blocks = bits in bitmap */ + uint64_t cursor; /* round-robin search hint */ + uint64_t used; + bm_word_t *bitmap; }; -struct ippool_item_t -{ - struct list_head entry; - struct ippool_t *pool; - struct ipv4db_item_t it; +struct ip_pool { + struct list_head entry; /* in set->pools; named pools only */ + char *name; /* NULL for the default pool */ + struct ip_pool *next; /* overflow chain */ + struct list_head ranges; + spinlock_t lock; /* guards every range's bitmap/cursor/used */ }; -struct ipaddr_t -{ +struct pool_set { + struct list_head pools; /* named pools */ + struct ip_pool *def_pool; /* unnamed default (not on `pools`) */ + in_addr_t gw_ip_address; /* prevents the configured gateway address from being handed out as a peer address */ + int shuffle; + int orphan_policy; +}; + +struct ip_lease { + struct ip_pool *pool; /* NULL once orphaned by a reload */ + struct ip_range *range; /*lease->range points to the struct ip_range that supplied this session’s allocated peer address, also NULL if orphaned */ + struct ipv4db_item_t it; /* ses->ipv4 = &it */ +}; + +/* collected during reconcile, acted on after locks are dropped */ +struct disc_node { struct list_head entry; - in_addr_t addr; + struct ap_session *ses; }; static struct ipdb_t ipdb; -static in_addr_t conf_gw_ip_address; -static int conf_shuffle; +static pthread_rwlock_t pool_set_rwlock = PTHREAD_RWLOCK_INITIALIZER; +static struct pool_set *cur_set; #ifdef RADIUS static int conf_vendor = 0; static int conf_attr = 88; // Framed-Pool -#endif - -static int cnt; -static LIST_HEAD(pool_list); -static struct ippool_t *def_pool; -struct ippool_t *create_pool(char *name) +static int parse_attr_opt(const char *opt) { - struct ippool_t *p = malloc(sizeof(*p)); + struct rad_dict_attr_t *attr; + struct rad_dict_vendor_t *vendor; - memset(p, 0, sizeof(*p)); - p->name = name; + if (conf_vendor) + vendor = rad_dict_find_vendor_id(conf_vendor); + else + vendor = NULL; - INIT_LIST_HEAD(&p->gw_list); - INIT_LIST_HEAD(&p->tunnel_list); - INIT_LIST_HEAD(&p->items); - spinlock_init(&p->lock); + if (conf_vendor) { + if (vendor) + attr = rad_dict_find_vendor_attr(vendor, opt); + else + attr = NULL; + } else + attr = rad_dict_find_attr(opt); - if (name) - list_add_tail(&p->entry, &pool_list); + if (attr) + return attr->id; - return p; + return atoi(opt); } -struct ippool_t *find_pool(char *name, int create) +static int parse_vendor_opt(const char *opt) { - struct ippool_t *p; - - list_for_each_entry(p, &pool_list, entry) { - if (!strcmp(p->name, name)) - return p; - } + struct rad_dict_vendor_t *vendor; - if (create) - return create_pool(name); + vendor = rad_dict_find_vendor_name(opt); + if (vendor) + return vendor->id; - return NULL; + return atoi(opt); } +#endif -static void parse_gw_ip_address(const char *val) +/* ===== randomness for shuffle ===== */ + +static uint64_t rand_u64(void) { - char addr[17]; - char *ptr; + uint64_t r = 0; - if (!val) - return; + if (read(urandom_fd, &r, sizeof(r)) != sizeof(r)) + r = 0; - ptr = strchr(val, '/'); - if (ptr) { - // safeguard, don't crash on oversized or undersized strings - if (ptr - val > 15 || ptr - val < 7) - return; - memcpy(addr, val, ptr - val); - addr[ptr - val] = 0; - conf_gw_ip_address = inet_addr(addr); - } - else - conf_gw_ip_address = inet_addr(val); + return r; } +/* ===== range arithmetic ===== */ + //parses ranges like x.x.x.x/mask static int parse1(const char *str, uint32_t *begin, uint32_t *end) { int n; unsigned int f1, f2, f3, f4, m; - n = sscanf(str, "%u.%u.%u.%u/%u",&f1, &f2, &f3, &f4, &m); + n = sscanf(str, "%u.%u.%u.%u/%u", &f1, &f2, &f3, &f4, &m); if (n != 5) return -1; - if (f1 > 255) - return -1; - if (f2 > 255) - return -1; - if (f3 > 255) - return -1; - if (f4 > 255) + if (f1 > 255 || f2 > 255 || f3 > 255 || f4 > 255) return -1; if (m == 0 || m > 32) return -1; @@ -154,16 +176,10 @@ static int parse2(const char *str, uint32_t *begin, uint32_t *end) int n; unsigned int f1, f2, f3, f4, f5; - n = sscanf(str, "%u.%u.%u.%u-%u",&f1, &f2, &f3, &f4, &f5); + n = sscanf(str, "%u.%u.%u.%u-%u", &f1, &f2, &f3, &f4, &f5); if (n != 5) return -1; - if (f1 > 255) - return -1; - if (f2 > 255) - return -1; - if (f3 > 255) - return -1; - if (f4 > 255) + if (f1 > 255 || f2 > 255 || f3 > 255 || f4 > 255) return -1; if (f5 < f4 || f5 > 255) return -1; @@ -174,222 +190,514 @@ static int parse2(const char *str, uint32_t *begin, uint32_t *end) return 0; } -static void add_range(struct ippool_t *p, struct list_head *list, const char *name, void (*generate)(struct ippool_t *)) +/* bit index of a leased peer address within a range, or BM_INVALID */ +static uint64_t range_addr_to_bit(const struct ip_range *r, uint32_t peer_host) { - uint32_t i,startip, endip; - struct ipaddr_t *ip; + uint32_t base, delta; + uint64_t bit; + + if (peer_host < (uint32_t)r->peer_offset) + return BM_INVALID; + base = peer_host - r->peer_offset; + if (base < r->start) + return BM_INVALID; + delta = base - r->start; + if (delta % r->step) + return BM_INVALID; + bit = delta / r->step; + if (bit >= r->count) + return BM_INVALID; + + return bit; +} - if (parse1(name, &startip, &endip)) { - if (parse2(name, &startip, &endip)) { - fprintf(stderr, "ippool: cann't parse '%s'\n", name); - _exit(EXIT_FAILURE); - } +/* ===== pool set construction ===== */ + +static struct ip_pool *create_pool(struct pool_set *set, char *name) +{ + struct ip_pool *p = _malloc(sizeof(*p)); + + if (!p) + return NULL; + + memset(p, 0, sizeof(*p)); + p->name = name; + INIT_LIST_HEAD(&p->ranges); + spinlock_init(&p->lock); + + if (name) + list_add_tail(&p->entry, &set->pools); + + return p; +} + +static struct ip_pool *find_pool(struct pool_set *set, const char *name, int create) +{ + struct ip_pool *p; + + list_for_each_entry(p, &set->pools, entry) { + if (p->name && !strcmp(p->name, name)) + return p; } - for (i = startip; i <= endip; i++) { - ip = malloc(sizeof(*ip)); - ip->addr = htonl(i); - list_add_tail(&ip->entry, list); - cnt++; + if (create) { + char *dup = _strdup(name); + if (!dup) + return NULL; + return create_pool(set, dup); } - p->startip = startip; - p->endip = endip; - p->generate = generate; + return NULL; } -static uint8_t get_random() +static int add_range_to_pool(struct ip_pool *p, const char *str, + uint32_t step, int gw_offset, int peer_offset) { - static uint8_t buf[128]; - static int pos = 0; - int r; + uint32_t start, end; + uint64_t count; + struct ip_range *r; - if (pos == 0) - read(urandom_fd, buf, 128); + if (parse1(str, &start, &end)) { + if (parse2(str, &start, &end)) { + log_error("ippool: can't parse range '%s'\n", str); + return -1; + } + } - r = buf[pos++]; + if (end < start) { + log_error("ippool: range '%s' ends before it starts\n", str); + return -1; + } - if (pos == 128) - pos = 0; + count = ((uint64_t)(end - start) + 1) / step; /* floor: net30 tail dropped */ + if (count == 0) { + log_warn("ippool: range '%s' is empty for this allocator\n", str); + return 0; + } + if (count > IPPOOL_MAX_BITS) { + log_error("ippool: range '%s' has %llu units, exceeds cap %u; skipping\n", + str, (unsigned long long)count, IPPOOL_MAX_BITS); + return -1; + } - return r; + r = _malloc(sizeof(*r)); + if (!r) + return -1; + memset(r, 0, sizeof(*r)); + r->start = start; + r->end = end; + r->step = step; + r->gw_offset = gw_offset; + r->peer_offset = peer_offset; + r->count = count; + list_add_tail(&r->entry, &p->ranges); + + return 0; } -static void generate_pool_p2p(struct ippool_t *p) +/* allocate + finalize bitmaps once the whole section (incl. gw-ip-address) is known */ +static int finalize_ranges(struct pool_set *set, struct ip_pool *p) { - struct ippool_item_t *it; - struct ipaddr_t *addr = NULL; - struct ipaddr_t *peer_addr; - struct list_head *pos = NULL, *pos1 = p->tunnel_list.next, *pos2 = p->tunnel_list.prev; - uint8_t r, t = 0; - - while (1) { - if (list_empty(&p->tunnel_list)) - break; - else { - if (conf_shuffle) { - if (pos1 == &p->tunnel_list) - pos1 = pos1->next; - - if (pos2 == &p->tunnel_list) - pos2 = pos2->prev; - - if (t++ < 10) - r = get_random(); - else - r = get_random()%64; - - if (r < 32) - pos = pos1; - else if (r < 64) - pos = pos2; - - pos1 = pos1->next; - pos2 = pos2->prev; - - if (r >= 64) - continue; - - peer_addr = list_entry(pos, typeof(*peer_addr), entry); - if (pos == pos1) - pos1 = pos1->next; - - if (pos == pos2) - pos2 = pos2->prev; - - list_del(&peer_addr->entry); - t = 0; - } else { - peer_addr = list_entry(p->tunnel_list.next, typeof(*peer_addr), entry); - list_del(&peer_addr->entry); + struct ip_range *r; + + list_for_each_entry(r, &p->ranges, entry) { + uint64_t nw = BM_NWORDS(r->count); + uint64_t b; + + r->bitmap = _malloc(nw * sizeof(bm_word_t)); + if (!r->bitmap) + return -1; + memset(r->bitmap, 0, nw * sizeof(bm_word_t)); + + /* remainder bits past count must never be handed out */ + for (b = r->count; b < nw * BM_WORD_BITS; b++) + bm_set(r->bitmap, b); + + /* p2p: reserve the bit colliding with the configured gateway, + * reproducing the old generate_pool_p2p skip */ + if (r->step == 1 && r->peer_offset == 0 && set->gw_ip_address) { + uint32_t gw = ntohl(set->gw_ip_address); + if (gw >= r->start && gw <= r->end) { + uint64_t bit = gw - r->start; + if (bit < r->count && !bm_test(r->bitmap, bit)) { + bm_set(r->bitmap, bit); + r->used++; + } } } + } - if (!conf_gw_ip_address) { - if (list_empty(&p->gw_list)) - break; - else { - addr = list_entry(p->gw_list.next, typeof(*addr), entry); - list_del(&addr->entry); - } - } else if (conf_gw_ip_address == peer_addr->addr) - continue; + return 0; +} - it = malloc(sizeof(*it)); - if (!it) { - fprintf(stderr, "ippool: out of memory\n"); - break; +static void parse_gw_ip_address(const char *val, in_addr_t *out) +{ + char addr[17]; + char *ptr; + + if (!val) + return; + + ptr = strchr(val, '/'); + if (ptr) { + if (ptr - val > 15 || ptr - val < 7) + return; + memcpy(addr, val, ptr - val); + addr[ptr - val] = 0; + *out = inet_addr(addr); + } else + *out = inet_addr(val); +} + +/* parse ,name= / ,allocator= / ,next= from a raw option line */ +static int parse_line_opts(struct pool_set *set, const char *opt, struct ip_pool **pool, + uint32_t *step, int *gw_offset, int *peer_offset) +{ + char *name, *ptr; + + name = strstr(opt, ",name="); + if (name) { + name += sizeof(",name=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + *pool = find_pool(set, name, 1); + _free(name); + } else if ((name = strchr(opt, ',')) && !strchr(name + 1, '=')) { + name = _strndup(name + 1, strchrnul(name + 1, ',') - (name + 1)); + if (!name) + return -1; + *pool = find_pool(set, name, 1); + _free(name); + } else + *pool = set->def_pool; + + if (!*pool) + return -1; + + /* defaults: p2p */ + *step = 1; + *gw_offset = -1; + *peer_offset = 0; + + name = strstr(opt, ",allocator="); + if (name) { + name += sizeof(",allocator=") - 1; + ptr = strchrnul(name, ','); + if (!strncmp(name, "p2p", ptr - name) && (size_t)(ptr - name) == 3) { + *step = 1; *gw_offset = -1; *peer_offset = 0; + } else if (!strncmp(name, "net30", ptr - name) && (size_t)(ptr - name) == 5) { + *step = 4; *gw_offset = 1; *peer_offset = 2; + } else { + log_error("ippool: '%s': unknown allocator\n", opt); + return -1; } + } - memset(it, 0, sizeof(*it)); - it->pool = p; - it->it.owner = &ipdb; - if (conf_gw_ip_address) - it->it.addr = conf_gw_ip_address; - else - it->it.addr = addr->addr; + name = strstr(opt, ",next="); + if (name) { + struct ip_pool *next; + name += sizeof(",next=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + next = find_pool(set, name, 1); + _free(name); + if (next) + (*pool)->next = next; + } - it->it.peer_addr = peer_addr->addr; + return 0; +} - list_add_tail(&it->entry, &p->items); +static void free_pool(struct ip_pool *p) +{ + struct ip_range *r; + + while (!list_empty(&p->ranges)) { + r = list_first_entry(&p->ranges, typeof(*r), entry); + list_del(&r->entry); + if (r->bitmap) + _free(r->bitmap); + _free(r); } + if (p->name) + _free(p->name); + _free(p); } -static void generate_pool_net30(struct ippool_t *p) +static void free_pool_set(struct pool_set *set) { - struct ippool_item_t *it; - struct ipaddr_t *addr[4]; - int i; + struct ip_pool *p; - while (1) { - memset(addr, 0, sizeof(addr)); + if (!set) + return; - for (i = 0; i < 4; i++) { - if (list_empty(&p->tunnel_list)) - break; + while (!list_empty(&set->pools)) { + p = list_first_entry(&set->pools, typeof(*p), entry); + list_del(&p->entry); + free_pool(p); + } + if (set->def_pool) + free_pool(set->def_pool); + _free(set); +} + +static struct pool_set *build_pool_set(void) +{ + struct conf_sect_t *s = conf_get_section("ip-pool"); + struct conf_option_t *opt; + struct pool_set *set; + struct ip_pool *p; + + set = _malloc(sizeof(*set)); + if (!set) + return NULL; + memset(set, 0, sizeof(*set)); + INIT_LIST_HEAD(&set->pools); + set->orphan_policy = ORPHAN_KEEP; + +#ifdef RADIUS + /* statics persist across reloads; reset to defaults so a removed + * vendor/attr line doesn't leave stale values behind */ + conf_vendor = 0; + conf_attr = 88; // Framed-Pool +#endif + + if (!s) + return set; /* no section: an empty (inert) set */ - addr[i] = list_entry(p->tunnel_list.next, typeof(*addr[i]), entry); - list_del(&addr[i]->entry); + set->def_pool = create_pool(set, NULL); + if (!set->def_pool) + goto err; + + list_for_each_entry(opt, &s->items, entry) { + const char *range_str; + struct ip_pool *pool; + uint32_t step; + int gw_offset, peer_offset; + +#ifdef RADIUS + if (triton_module_loaded("radius")) { + if (!strcmp(opt->name, "vendor")) { + if (opt->val) + conf_vendor = parse_vendor_opt(opt->val); + continue; + } else if (!strcmp(opt->name, "attr")) { + if (opt->val) + conf_attr = parse_attr_opt(opt->val); + continue; + } + } +#endif + if (!strcmp(opt->name, "gw-ip-address")) { + parse_gw_ip_address(opt->val, &set->gw_ip_address); + continue; + } + if (!strcmp(opt->name, "shuffle")) { + set->shuffle = opt->val ? atoi(opt->val) : 0; + continue; + } + if (!strcmp(opt->name, "reload-orphan")) { + if (opt->val && !strcmp(opt->val, "disconnect")) + set->orphan_policy = ORPHAN_DISCONNECT; + else + set->orphan_policy = ORPHAN_KEEP; + continue; + } + if (!strcmp(opt->name, "gw")) { + /* deprecated/no-op: the per-block local address has long been + * overwritten at get time by gw-ip-address/0. Accept and ignore. */ + log_warn("ippool: 'gw=' is deprecated and ignored\n"); + continue; } - if (!addr[2]) - break; + if (!strcmp(opt->name, "tunnel")) + range_str = opt->val; + else if (!opt->val || strchr(opt->name, ',')) + range_str = opt->name; + else + continue; /* unrecognized option */ + if (!range_str) + continue; - it = malloc(sizeof(*it)); - if (!it) { - log_emerg("ippool: out of memory\n"); - break; + if (parse_line_opts(set, opt->raw, &pool, &step, &gw_offset, &peer_offset)) { + log_error("ippool: failed to parse '%s'\n", opt->raw); + continue; } - memset(it, 0, sizeof(*it)); - it->pool = p; - it->it.owner = &ipdb; - it->it.addr = addr[1]->addr; - it->it.peer_addr = addr[2]->addr; + add_range_to_pool(pool, range_str, step, gw_offset, peer_offset); + } + + if (finalize_ranges(set, set->def_pool)) + goto err; + list_for_each_entry(p, &set->pools, entry) { + if (finalize_ranges(set, p)) + goto err; + if (list_empty(&p->ranges)) + log_warn("ippool: pool '%s' is empty or not defined\n", p->name); + } + + return set; + +err: + free_pool_set(set); + return NULL; +} + +/* ===== address lookup across a set ===== */ + +static int pool_contains(struct ip_pool *p, uint32_t peer_host, + struct ip_range **out_r, uint64_t *out_bit) +{ + struct ip_range *r; + + list_for_each_entry(r, &p->ranges, entry) { + uint64_t bit = range_addr_to_bit(r, peer_host); + if (bit != BM_INVALID) { + *out_r = r; + *out_bit = bit; + return 1; + } + } + return 0; +} + +/* find the pool+range+bit owning `peer_host`, preferring `pref_name` on overlap */ +static int find_target(struct pool_set *set, uint32_t peer_host, const char *pref_name, + struct ip_pool **op, struct ip_range **orr, uint64_t *obit) +{ + struct ip_pool *p; - list_add_tail(&it->entry, &p->items); + if (!set) + return 0; - for (i = 0; i < 4; i++) { - if (addr[i]) - free(addr[i]); + if (pref_name) { + p = find_pool(set, pref_name, 0); + if (p && pool_contains(p, peer_host, orr, obit)) { + *op = p; + return 1; } } - for (i = 0; i < 4; i++) { - if (addr[i]) - free(addr[i]); + if (set->def_pool && pool_contains(set->def_pool, peer_host, orr, obit)) { + *op = set->def_pool; + return 1; } + list_for_each_entry(p, &set->pools, entry) { + if (pool_contains(p, peer_host, orr, obit)) { + *op = p; + return 1; + } + } + + return 0; } +/* ===== ipdb get/put ===== */ static struct ipv4db_item_t *get_ip(struct ap_session *ses) { - struct ippool_item_t *it; - struct ippool_t *pool, *start; + struct pool_set *set; + struct ip_pool *pool, *start, *found_p = NULL; + struct ip_range *r, *found_r = NULL; + struct ip_lease *lease; + uint64_t bit = BM_INVALID; + uint64_t rnd; + uint32_t base; + + pthread_rwlock_rdlock(&pool_set_rwlock); + set = cur_set; + if (!set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } if (ses->ipv4_pool_name) - pool = find_pool(ses->ipv4_pool_name, 0); + pool = find_pool(set, ses->ipv4_pool_name, 0); else - pool = def_pool; + pool = set->def_pool; - if (!pool) + if (!pool) { + pthread_rwlock_unlock(&pool_set_rwlock); return NULL; + } + + /* draw randomness once, outside the lock: read(urandom_fd) is a + * blocking syscall and must not run while holding pool->lock */ + rnd = set->shuffle ? rand_u64() : 0; start = pool; do { 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; + list_for_each_entry(r, &pool->ranges, entry) { + uint64_t from = set->shuffle ? (r->count ? rnd % r->count : 0) : r->cursor; + bit = bm_find_free(r->bitmap, r->count, from); + if (bit != BM_INVALID) { + bm_set(r->bitmap, bit); + r->used++; + r->cursor = bit + 1; + found_r = r; + found_p = pool; + break; + } + } spin_unlock(&pool->lock); + if (found_r) + break; + pool = pool->next; + } while (pool && pool != start); - if (it) { - if (ses->ctrl->ppp) - it->it.addr = conf_gw_ip_address; - else - it->it.addr = 0; + if (!found_r) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } - it->it.mask = 0; + lease = _malloc(sizeof(*lease)); + if (!lease) { + spin_lock(&found_p->lock); + bm_clear(found_r->bitmap, bit); + found_r->used--; + spin_unlock(&found_p->lock); + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } - return &it->it; - } + memset(lease, 0, sizeof(*lease)); + lease->pool = found_p; + lease->range = found_r; + lease->it.owner = &ipdb; + base = found_r->start + (uint32_t)bit * found_r->step; + lease->it.peer_addr = htonl(base + found_r->peer_offset); + lease->it.addr = ses->ctrl->ppp ? set->gw_ip_address : 0; + lease->it.mask = 0; - pool = pool->next; - } while (pool && pool != start); + pthread_rwlock_unlock(&pool_set_rwlock); - return NULL; + return &lease->it; } static void put_ip(struct ap_session *ses, struct ipv4db_item_t *it) { - struct ippool_item_t *pit = container_of(it, typeof(*pit), it); + struct ip_lease *lease = container_of(it, typeof(*lease), it); + + pthread_rwlock_rdlock(&pool_set_rwlock); + if (lease->pool && lease->range) { + uint64_t bit = range_addr_to_bit(lease->range, ntohl(it->peer_addr)); + spin_lock(&lease->pool->lock); + if (bit != BM_INVALID && bm_test(lease->range->bitmap, bit)) { + bm_clear(lease->range->bitmap, bit); + lease->range->used--; + } + spin_unlock(&lease->pool->lock); + } + /* else: orphaned by a reload - nothing to release, just free the wrapper */ + pthread_rwlock_unlock(&pool_set_rwlock); - spin_lock(&pit->pool->lock); - list_add_tail(&pit->entry, &pit->pool->items); - spin_unlock(&pit->pool->lock); + _free(lease); } static struct ipdb_t ipdb = { @@ -418,9 +726,10 @@ static int session_save(struct ap_session *ses, struct backup_mod *m) static int session_restore(struct ap_session *ses, struct backup_mod *m) { struct backup_tag *tag; - in_addr_t addr = 0, peer_addr; - struct ippool_t *p; - struct ippool_item_t *it, *it0 = NULL; + in_addr_t addr = 0, peer_addr = 0; + struct ip_pool *np; + struct ip_range *nr; + uint64_t bit; m = backup_find_mod(m->data, MODID_COMMON); @@ -435,40 +744,35 @@ static int session_restore(struct ap_session *ses, struct backup_mod *m) } } - spin_lock(&def_pool->lock); - list_for_each_entry(it, &def_pool->items, entry) { - if (peer_addr == it->it.peer_addr && addr == it->it.addr) { - list_del(&it->entry); - it0 = it; - break; - } - } - spin_unlock(&def_pool->lock); - - if (!it0) { - list_for_each_entry(p, &pool_list, entry) { - spin_lock(&p->lock); - list_for_each_entry(it, &p->items, entry) { - if (peer_addr == it->it.peer_addr && addr == it->it.addr) { - list_del(&it->entry); - it0 = it; - break; - } + pthread_rwlock_rdlock(&pool_set_rwlock); + if (find_target(cur_set, ntohl(peer_addr), NULL, &np, &nr, &bit)) { + struct ip_lease *lease = _malloc(sizeof(*lease)); + if (lease) { + memset(lease, 0, sizeof(*lease)); + lease->pool = np; + lease->range = nr; + lease->it.owner = &ipdb; + lease->it.addr = addr; + lease->it.peer_addr = peer_addr; + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; } - spin_unlock(&p->lock); - if (it0) - break; + spin_unlock(&np->lock); + ses->ipv4 = &lease->it; } } + pthread_rwlock_unlock(&pool_set_rwlock); - if (it0) - ses->ipv4 = &it0->it; - else { + if (!ses->ipv4) { ses->ipv4 = _malloc(sizeof(*ses->ipv4)); - memset(ses->ipv4, 0, sizeof(*ses->ipv4)); - ses->ipv4->addr = addr; - ses->ipv4->peer_addr = peer_addr; - ses->ipv4->owner = &ipdb_b; + if (ses->ipv4) { + memset(ses->ipv4, 0, sizeof(*ses->ipv4)); + ses->ipv4->addr = addr; + ses->ipv4->peer_addr = peer_addr; + ses->ipv4->owner = &ipdb_b; + } } return 0; @@ -481,6 +785,101 @@ static struct backup_module backup_mod = { }; #endif +/* ===== reconcile on reload ===== */ + +static void reconcile_v4(struct pool_set *new_set, struct ap_session *ses, + int policy, struct list_head *disc) +{ + struct ipv4db_item_t *it = ses->ipv4; + uint32_t peer_host; + struct ip_pool *np; + struct ip_range *nr; + uint64_t bit; + + if (!it || !it->owner) + return; + + peer_host = ntohl(it->peer_addr); + + if (it->owner == &ipdb) { + struct ip_lease *lease = container_of(it, typeof(*lease), it); + if (find_target(new_set, peer_host, ses->ipv4_pool_name, &np, &nr, &bit)) { + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; + } + spin_unlock(&np->lock); + lease->pool = np; + lease->range = nr; + } else { + lease->pool = NULL; + lease->range = NULL; + if (policy == ORPHAN_DISCONNECT) { + struct disc_node *d = _malloc(sizeof(*d)); + if (d) { + d->ses = ses; + list_add_tail(&d->entry, disc); + } + } + } + } else { + /* foreign owner (radius/chap-secrets/static): reserve the bit so we + * never hand out a live address. We do not own or free it. */ + if (find_target(new_set, peer_host, NULL, &np, &nr, &bit)) { + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; + } + spin_unlock(&np->lock); + } + } +} + +static void terminate_orphan(void *arg) +{ + struct ap_session *ses = arg; + ap_session_terminate(ses, TERM_NAS_REBOOT, 0); +} + +static void load_config(void *data) +{ + struct pool_set *new_set, *old_set; + struct ap_session *ses; + struct disc_node *d; + LIST_HEAD(disc_list); + int policy; + + new_set = build_pool_set(); + if (!new_set) { + log_error("ippool: reload failed, keeping current pools\n"); + return; + } + policy = new_set->orphan_policy; + + pthread_rwlock_wrlock(&pool_set_rwlock); + pthread_rwlock_rdlock(&ses_lock); + + list_for_each_entry(ses, &ses_list, entry) + reconcile_v4(new_set, ses, policy, &disc_list); + + old_set = cur_set; + cur_set = new_set; + + pthread_rwlock_unlock(&ses_lock); + pthread_rwlock_unlock(&pool_set_rwlock); + + free_pool_set(old_set); + + while (!list_empty(&disc_list)) { + d = list_first_entry(&disc_list, typeof(*d), entry); + list_del(&d->entry); + triton_context_call(d->ses->ctrl->ctx, terminate_orphan, d->ses); + _free(d); + } +} + #ifdef RADIUS static int parse_attr(struct ap_session *ses, struct rad_attr_t *attr) { @@ -517,169 +916,60 @@ static void ev_radius_access_accept(struct ev_radius_t *ev) parse_attr(ev->ses, attr); } } - -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 int parse_options(const char *opt, struct ippool_t **pool, generate_func *generate, struct ippool_t **next) -{ - char *name, *ptr; - - name = strstr(opt, ",name="); - if (name) { - name += sizeof(",name=") - 1; - ptr = strchrnul(name, ','); - name = _strndup(name, ptr - name); - if (!name) - return -1; - *pool = find_pool(name, 1); - } else if ((name = strchr(opt, ',')) && !strchr(name + 1, '=')) { - name = _strdup(name + 1); - if (!name) - return -1; - *pool = find_pool(name, 1); - } else - *pool = def_pool; - - name = strstr(opt, ",allocator="); - if (name) { - name += sizeof(",allocator=") - 1; - ptr = strchrnul(name, ','); - name = strncpy(alloca(ptr - name + 1), name, ptr - name + 1); - if (strcmp(name, "p2p") == 0) - *generate = generate_pool_p2p; - else if (strcmp(name, "net30") == 0) - *generate = generate_pool_net30; - else { - log_error("ipool: '%s': unknown allocator\n", opt); - return -1; - } - } else - *generate = generate_pool_p2p; - - name = strstr(opt, ",next="); - if (name) { - name += sizeof(",next=") - 1; - ptr = strchrnul(name, ','); - name = strncpy(alloca(ptr - name + 1), name, ptr - name + 1); - *next = find_pool(name, 0); - if (!*next) { - name = _strdup(name); - if (!name) - return -1; - *next = find_pool(name, 1); - } - } else - *next = NULL; - - return 0; -} - -static void ippool_init1(void) -{ - ipdb_register(&ipdb); -} +/* ===== cli ===== */ static int show_ippool_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { - struct ippool_t *pool; - uint32_t total, available, used; - struct ippool_item_t *it; + struct ip_pool *pool; + struct ip_range *r; + uint64_t total, used; cli_send(client, "IP Pool Usage Report\r\n"); cli_send(client, "====================\r\n"); - // Show default pool first - if (def_pool) { - spin_lock(&def_pool->lock); - - total = 0; - available = 0; - - if (def_pool->startip && def_pool->endip) { - total = def_pool->endip - def_pool->startip + 1; - } - - list_for_each_entry(it, &def_pool->items, entry) { - available++; - } - - used = total - available; - spin_unlock(&def_pool->lock); - - if (total > 0) { - cli_sendv(client, "<default>\r\n total: %u\r\n used: %u\r\n available: %u\r\n usage: %u%%\r\n", - total, used, available, - total ? (used * 100 / total) : 0); - } + pthread_rwlock_rdlock(&pool_set_rwlock); + if (!cur_set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return CLI_CMD_OK; } - // Show named pools - list_for_each_entry(pool, &pool_list, entry) { - if (!pool->name) continue; - + if (cur_set->def_pool) { + pool = cur_set->def_pool; + total = used = 0; spin_lock(&pool->lock); - - total = 0; - available = 0; - - if (pool->startip && pool->endip) { - total = pool->endip - pool->startip + 1; - } - - list_for_each_entry(it, &pool->items, entry) { - available++; + list_for_each_entry(r, &pool->ranges, entry) { + total += r->count; + used += r->used; } - - used = total - available; spin_unlock(&pool->lock); + if (total > 0) + cli_sendv(client, "<default>\r\n total: %llu\r\n used: %llu\r\n available: %llu\r\n usage: %llu%%\r\n", + (unsigned long long)total, (unsigned long long)used, + (unsigned long long)(total - used), + (unsigned long long)(used * 100 / total)); + } - if (total > 0) { - if (used * 1000 / total % 10 > 5) - cli_sendv(client, "%s\r\n total: %u\r\n used: %u\r\n available: %u\r\n usage: %u.%u%%\r\n", - pool->name, total, used, available, - total ? (used * 100 / total) : 0, - total ? (used * 1000 / total % 10) : 0); - else - cli_sendv(client, "%s\r\n total: %u\r\n used: %u\r\n available: %u\r\n usage: %u%%\r\n", - pool->name, total, used, available, - total ? (used * 100 / total) : 0); + list_for_each_entry(pool, &cur_set->pools, entry) { + if (!pool->name) + continue; + total = used = 0; + spin_lock(&pool->lock); + list_for_each_entry(r, &pool->ranges, entry) { + total += r->count; + used += r->used; } + spin_unlock(&pool->lock); + if (total > 0) + cli_sendv(client, "%s\r\n total: %llu\r\n used: %llu\r\n available: %llu\r\n usage: %llu%%\r\n", + pool->name, (unsigned long long)total, (unsigned long long)used, + (unsigned long long)(total - used), + (unsigned long long)(used * 100 / total)); } + pthread_rwlock_unlock(&pool_set_rwlock); + return CLI_CMD_OK; } @@ -688,61 +978,20 @@ static void show_ippool_help(char * const *fields, int fields_cnt, void *client) cli_send(client, "show ippool - shows IP pool statistics\r\n"); } -static void ippool_init2(void) -{ - struct conf_sect_t *s = conf_get_section("ip-pool"); - struct conf_option_t *opt; - struct ippool_t *pool, *next; - generate_func generate; - - if (!s) - return; - - def_pool = create_pool(NULL); +/* ===== init ===== */ - 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; - } else if (!strcmp(opt->name, "attr")) { - conf_attr = parse_attr_opt(opt->val); - continue; - } - } -#endif - if (!strcmp(opt->name, "gw-ip-address")) - parse_gw_ip_address(opt->val); - else if (!strcmp(opt->name, "shuffle")) - conf_shuffle = atoi(opt->val); - else { - if (parse_options(opt->raw, &pool, &generate, &next)) { - log_error("ippool: failed to parse '%s'\n", opt->raw); - continue; - } - - if (!strcmp(opt->name, "gw")) - add_range(pool, &pool->gw_list, opt->val, generate); - else if (!strcmp(opt->name, "tunnel")) - add_range(pool, &pool->tunnel_list, opt->val, generate); - else if (!opt->val || strchr(opt->name, ',')) - add_range(pool, &pool->tunnel_list, opt->name, generate); - - if (next) - pool->next = next; - } - } +static void ippool_init1(void) +{ + ipdb_register(&ipdb); +} - if (def_pool->generate) - def_pool->generate(def_pool); +static void ippool_init2(void) +{ + load_config(NULL); - list_for_each_entry(pool, &pool_list, entry) { - if (pool->generate) - pool->generate(pool); - else - log_warn("ippool: pool '%s' is empty or not defined\n", pool->name); - } + if (triton_event_register_handler(EV_CONFIG_RELOAD, load_config) < 0) + log_error("ippool: registration of CONFIG_RELOAD event failed," + " pools will not reload\n"); #ifdef USE_BACKUP backup_register_module(&backup_mod); diff --git a/accel-pppd/extra/ipv6pool.c b/accel-pppd/extra/ipv6pool.c index c5cabef5..9a763445 100644 --- a/accel-pppd/extra/ipv6pool.c +++ b/accel-pppd/extra/ipv6pool.c @@ -3,76 +3,112 @@ #include <stdint.h> #include <unistd.h> #include <string.h> -#include <unistd.h> #include <fcntl.h> +#include <pthread.h> #include <arpa/inet.h> +#include <netinet/in.h> #include <endian.h> +#include "triton.h" #include "events.h" #include "ipdb.h" #include "list.h" #include "log.h" #include "spinlock.h" +#include "ap_session.h" #ifdef RADIUS #include "radius.h" #endif +#include "bitpool.h" #include "memdebug.h" -enum ippool_type -{ - IPPOOL_ADDRESS, - IPPOOL_PREFIX +/* + * Bitmap IPv6 address (IA_NA) and delegated-prefix (IA_PD) pools. + * + * Same model as the IPv4 pool: each pool holds a list of contiguous ranges, + * each range owns one bitmap indexing per-lease prefixes (bit i -> start + + * i*2^(128-prefix_len)). A lease is a per-session malloc wrapper that embeds + * the single ipv6db_addr_t node linked into the item's addr/prefix list. The + * whole set is rebuilt and swapped on EV_CONFIG_RELOAD with a reconcile pass. + * + * TODO: a sparse/hierarchical allocator would lift IPPOOL_MAX_BITS; the dense + * bitmap requires (prefix_len - mask) <= 24, i.e. <= ~16.7M prefixes per range. + */ + +#define IPPOOL_MAX_BITS (1u << 24) +#define IPPOOL_MAX_SHIFT 24 /* prefix_len - mask cap (2^24 entries) */ + +enum ippool_type { + IPPOOL_ADDRESS, + IPPOOL_PREFIX, }; -struct ippool_t -{ +enum { + ORPHAN_KEEP = 0, + ORPHAN_DISCONNECT, +}; + +struct ip6_range { struct list_head entry; - char *name; - struct list_head gw_list; - struct list_head items; - struct ippool_t *next; - spinlock_t lock; + struct in6_addr start; + int prefix_len; + int shift; /* 128 - prefix_len */ + uint64_t count; /* number of prefixes = bits */ + uint64_t cursor; + uint64_t used; + bm_word_t *bitmap; }; -struct ippool_item_t -{ +struct ip6_pool { struct list_head entry; - struct ippool_t *pool; - struct ipv6db_item_t it; + char *name; /* NULL for default pool */ + struct ip6_pool *next; + struct list_head ranges; + spinlock_t lock; }; -struct dppool_item_t -{ +struct pool_set { + struct list_head ippools; /* named NA pools */ + struct list_head dppools; /* named PD pools */ + struct ip6_pool *def_ippool; + struct ip6_pool *def_dppool; + struct in6_addr gw_addr; + int orphan_policy; +}; + +struct ip6_lease { + struct ip6_pool *pool; + struct ip6_range *range; + struct ipv6db_item_t it; /* ses->ipv6 = &it */ + struct ipv6db_addr_t node; /* linked into it.addr_list */ +}; + +struct dp_lease { + struct ip6_pool *pool; + struct ip6_range *range; + struct ipv6db_prefix_t it; /* ses->ipv6_dp = &it */ + struct ipv6db_addr_t node; /* linked into it.prefix_list */ +}; + +struct disc_node { struct list_head entry; - struct ippool_t *pool; - struct ipv6db_prefix_t it; + struct ap_session *ses; }; +static struct ipdb_t ipdb; + +static pthread_rwlock_t pool_set_rwlock = PTHREAD_RWLOCK_INITIALIZER; +static struct pool_set *cur_set; + #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; - } -} +/* ===== 128-bit helpers (big-endian s6_addr[16]) ===== */ static int in6_addr_cmp(const struct in6_addr *n1, const struct in6_addr *n2) { @@ -88,154 +124,205 @@ static int in6_addr_cmp(const struct in6_addr *n1, const struct in6_addr *n2) return 0; } -static struct ippool_t *create_pool(enum ippool_type type, char *name) +/* o = a - b (assumes a >= b) */ +static void in6_sub(uint8_t *o, const uint8_t *a, const uint8_t *b) { - struct ippool_t *pool = malloc(sizeof(*pool)); - struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &dppool_list : &ippool_list; + int i, borrow = 0; - memset(pool, 0, sizeof(*pool)); - pool->name = name; + for (i = 15; i >= 0; i--) { + int v = (int)a[i] - b[i] - borrow; + if (v < 0) { + v += 256; + borrow = 1; + } else + borrow = 0; + o[i] = v; + } +} - INIT_LIST_HEAD(&pool->items); - spinlock_init(&pool->lock); +/* floor(delta >> shift) into *out; returns -1 if it doesn't fit in uint64 */ +static int in6_shr_u64(const uint8_t *d, int shift, uint64_t *out) +{ + int p; + uint64_t v = 0; - if (name) - list_add_tail(&pool->entry, pool_list); + for (p = 0; p < 64; p++) { + int sp = shift + p; + if (sp < 128 && (d[15 - (sp >> 3)] & (1 << (sp & 7)))) + v |= (uint64_t)1 << p; + } + for (p = shift + 64; p < 128; p++) { + if (d[15 - (p >> 3)] & (1 << (p & 7))) + return -1; + } - return pool; + *out = v; + return 0; } -static struct ippool_t *find_pool(enum ippool_type type, char *name, int create) +/* out = start + (i << shift) */ +static void in6_index_to_addr(struct in6_addr *out, const struct in6_addr *start, + uint64_t i, int shift) { - 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; + uint8_t add[16] = { 0 }; + int p, b, carry = 0; + + for (p = 0; p < 64; p++) { + if (i & ((uint64_t)1 << p)) { + int sp = shift + p; + if (sp < 128) + add[15 - (sp >> 3)] |= (1 << (sp & 7)); + } } - if (create) - return create_pool(type, name); - - return NULL; + memcpy(out, start, 16); + for (b = 15; b >= 0; b--) { + int s = out->s6_addr[b] + add[b] + carry; + out->s6_addr[b] = s & 0xff; + carry = s >> 8; + } } -static void generate_ippool(struct ippool_t *pool, struct in6_addr *addr, int mask, int prefix_len) +/* bit index of a leased prefix within a range, or BM_INVALID */ +static uint64_t range_addr_to_bit(const struct ip6_range *r, const struct in6_addr *addr) { - struct ippool_item_t *it; - struct ipv6db_addr_t *a; - struct in6_addr ip, end, step; + uint8_t delta[16]; + uint64_t i; + int p; - memcpy(&ip, addr, sizeof(ip)); + if (in6_addr_cmp(addr, &r->start) < 0) + return BM_INVALID; - 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)); + in6_sub(delta, addr->s6_addr, r->start.s6_addr); + + /* must sit on a prefix boundary: low `shift` bits zero */ + for (p = 0; p < r->shift; p++) { + if (delta[15 - (p >> 3)] & (1 << (p & 7))) + return BM_INVALID; } - 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)); + if (in6_shr_u64(delta, r->shift, &i)) + return BM_INVALID; + if (i >= r->count) + return BM_INVALID; - 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); - } + return i; } -static void generate_dppool(struct ippool_t *pool, struct in6_addr *addr, int mask, int prefix_len) +/* ===== pool set construction ===== */ + +static struct ip6_pool *create_pool(struct list_head *pool_list, char *name) { - struct dppool_item_t *it; - struct in6_addr ip, end, step; - struct ipv6db_addr_t *a; + struct ip6_pool *p = _malloc(sizeof(*p)); - memcpy(&ip, addr, sizeof(ip)); + if (!p) + return NULL; - 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(p, 0, sizeof(*p)); + p->name = name; + INIT_LIST_HEAD(&p->ranges); + spinlock_init(&p->lock); - 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)); + if (name) + list_add_tail(&p->entry, pool_list); - 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); + return p; +} + +static struct ip6_pool *find_pool(struct list_head *pool_list, const char *name, int create) +{ + struct ip6_pool *p; + + list_for_each_entry(p, pool_list, entry) { + if (p->name && !strcmp(p->name, name)) + return p; } + + if (create) { + char *dup = _strdup(name); + if (!dup) + return NULL; + return create_pool(pool_list, dup); + } + + return NULL; } -static void add_prefix(enum ippool_type type, struct ippool_t *pool, const char *_val) +/* parse "<addr>/<mask>,<prefix_len>" and append a range to `pool` */ +static void add_prefix(struct ip6_pool *pool, const char *_val) { char *val = _strdup(_val); char *ptr1, *ptr2; - struct in6_addr addr; - int prefix_len; - int mask; + struct in6_addr start, end; + int prefix_len, mask, shift; + uint64_t count; + struct ip6_range *r; + + if (!val) + return; 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) + if (inet_pton(AF_INET6, val, &start) == 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) + 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); + if (prefix_len - mask > IPPOOL_MAX_SHIFT) { + log_error("ipv6_pool: range '%s' has 2^%d prefixes, exceeds cap 2^%d; skipping\n", + _val, prefix_len - mask, IPPOOL_MAX_SHIFT); + _free(val); + return; + } + + shift = 128 - prefix_len; + + /* end = start | hostmask(mask) (matches the original generator) */ + memcpy(&end, &start, 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)); + } + + { + uint8_t delta[16]; + uint64_t span; + in6_sub(delta, end.s6_addr, start.s6_addr); + if (in6_shr_u64(delta, shift, &span) || span >= IPPOOL_MAX_BITS) { + log_error("ipv6_pool: range '%s' exceeds cap; skipping\n", _val); + _free(val); + return; + } + count = span + 1; + } + + r = _malloc(sizeof(*r)); + if (!r) { + _free(val); + return; + } + memset(r, 0, sizeof(*r)); + memcpy(&r->start, &start, sizeof(start)); + r->prefix_len = prefix_len; + r->shift = shift; + r->count = count; + list_add_tail(&r->entry, &pool->ranges); _free(val); return; @@ -245,133 +332,130 @@ err: _free(val); } -static struct ipv6db_item_t *get_ip(struct ap_session *ses) +static int finalize_ranges(struct ip6_pool *p) { - struct ippool_item_t *it; - struct ipv6db_addr_t *a; - struct ippool_t *pool, *start; + struct ip6_range *r; - if (ses->ipv6_pool_name) - pool = find_pool(IPPOOL_ADDRESS, ses->ipv6_pool_name, 0); - else - pool = def_ippool; + list_for_each_entry(r, &p->ranges, entry) { + uint64_t nw = BM_NWORDS(r->count); + uint64_t b; - if (!pool) - return NULL; - - start = pool; - do { - 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; - } - - pool = pool->next; - } while (pool && pool != start); - - return NULL; -} + r->bitmap = _malloc(nw * sizeof(bm_word_t)); + if (!r->bitmap) + return -1; + memset(r->bitmap, 0, nw * sizeof(bm_word_t)); -static void put_ip(struct ap_session *ses, struct ipv6db_item_t *it) -{ - struct ippool_item_t *pit = container_of(it, typeof(*pit), it); + for (b = r->count; b < nw * BM_WORD_BITS; b++) + bm_set(r->bitmap, b); + } - spin_lock(&pit->pool->lock); - list_add_tail(&pit->entry, &pit->pool->items); - spin_unlock(&pit->pool->lock); + return 0; } -static struct ipv6db_prefix_t *get_dp(struct ap_session *ses) +static int parse_line_opts(struct pool_set *set, enum ippool_type type, const char *opt, + struct ip6_pool **pool) { - struct dppool_item_t *it; - struct ippool_t *pool, *start; + struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &set->dppools : &set->ippools; + char *name, *ptr; - if (ses->dpv6_pool_name) - pool = find_pool(IPPOOL_PREFIX, ses->dpv6_pool_name, 0); - else - pool = def_dppool; + name = strstr(opt, ",name="); + if (name) { + name += sizeof(",name=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + *pool = find_pool(pool_list, name, 1); + _free(name); + } else + *pool = (type == IPPOOL_PREFIX) ? set->def_dppool : set->def_ippool; - if (!pool) - return NULL; + if (!*pool) + return -1; - start = pool; - do { - 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; + name = strstr(opt, ",next="); + if (name) { + struct ip6_pool *next; + name += sizeof(",next=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + next = find_pool(pool_list, name, 1); + _free(name); + if (next) + (*pool)->next = next; + } - pool = pool->next; - } while (pool && pool != start); + return 0; +} - return NULL; +static void free_pool(struct ip6_pool *p) +{ + struct ip6_range *r; + + while (!list_empty(&p->ranges)) { + r = list_first_entry(&p->ranges, typeof(*r), entry); + list_del(&r->entry); + if (r->bitmap) + _free(r->bitmap); + _free(r); + } + if (p->name) + _free(p->name); + _free(p); } -static void put_dp(struct ap_session *ses, struct ipv6db_prefix_t *it) +static void free_pool_list(struct list_head *l) { - struct dppool_item_t *pit = container_of(it, typeof(*pit), it); + struct ip6_pool *p; - spin_lock(&pit->pool->lock); - list_add_tail(&pit->entry, &pit->pool->items); - spin_unlock(&pit->pool->lock); + while (!list_empty(l)) { + p = list_first_entry(l, typeof(*p), entry); + list_del(&p->entry); + free_pool(p); + } } -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) +static void free_pool_set(struct pool_set *set) { - struct rad_attr_t *attr; - struct ap_session *ses = ev->ses; + if (!set) + return; - 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; + free_pool_list(&set->ippools); + free_pool_list(&set->dppools); + if (set->def_ippool) + free_pool(set->def_ippool); + if (set->def_dppool) + free_pool(set->def_dppool); + _free(set); +} - if (conf_dppool_attr && conf_dppool_attr == attr->attr->id) { - if (ses->dpv6_pool_name) - _free(ses->dpv6_pool_name); - ses->dpv6_pool_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 finalize_set(struct pool_set *set) +{ + struct ip6_pool *p; + + if (set->def_ippool && finalize_ranges(set->def_ippool)) + return -1; + if (set->def_dppool && finalize_ranges(set->def_dppool)) + return -1; + list_for_each_entry(p, &set->ippools, entry) { + if (finalize_ranges(p)) + return -1; + if (list_empty(&p->ranges)) + log_warn("ipv6_pool: pool '%s' is empty or not defined\n", p->name); + } + list_for_each_entry(p, &set->dppools, entry) { + if (finalize_ranges(p)) + return -1; + if (list_empty(&p->ranges)) + log_warn("ipv6_pool: delegate pool '%s' is empty or not defined\n", p->name); } + + return 0; } +#ifdef RADIUS static int parse_attr_opt(const char *opt) { struct rad_dict_attr_t *attr; @@ -408,83 +492,77 @@ static int parse_vendor_opt(const char *opt) } #endif -static int parse_options(enum ippool_type type, const char *opt, struct ippool_t **pool, struct ippool_t **next) -{ - char *name, *ptr; - - name = strstr(opt, ",name="); - if (name) { - name += sizeof(",name=") - 1; - ptr = strchrnul(name, ','); - name = _strndup(name, ptr - name); - if (!name) - return -1; - *pool = find_pool(type, name, 1); - } else if (type == IPPOOL_PREFIX) - *pool = def_dppool; - else - *pool = def_ippool; - - name = strstr(opt, ",next="); - if (name) { - name += sizeof(",next=") - 1; - ptr = strchrnul(name, ','); - name = strncpy(alloca(ptr - name + 1), name, ptr - name + 1); - *next = find_pool(type, name, 0); - if (!*next) { - name = _strdup(name); - if (!name) - return -1; - *next = find_pool(type, name, 1); - } - } else - *next = NULL; - - return 0; -} - -static void ippool_init1(void) -{ - ipdb_register(&ipdb); -} - -static void ippool_init2(void) +static struct pool_set *build_pool_set(void) { struct conf_sect_t *s = conf_get_section("ipv6-pool"); struct conf_option_t *opt; - struct ippool_t *pool, *next; - char *val; - enum ippool_type type; + struct pool_set *set; #ifdef RADIUS int dppool_attr = 0, ippool_attr = 0; #endif + set = _malloc(sizeof(*set)); + if (!set) + return NULL; + memset(set, 0, sizeof(*set)); + INIT_LIST_HEAD(&set->ippools); + INIT_LIST_HEAD(&set->dppools); + set->orphan_policy = ORPHAN_KEEP; + +#ifdef RADIUS + /* statics persist across reloads; reset to defaults so a removed + * vendor/attr line doesn't leave stale values behind. A stale + * conf_vendor would otherwise force conf_dppool_attr/conf_ippool_attr + * to 0 below, silently disabling pool-name matching. */ + conf_vendor = 0; + conf_dppool_attr = 171; // Delegated-IPv6-Prefix-Pool + conf_ippool_attr = 172; // Stateful-IPv6-Address-Pool +#endif + if (!s) - return; + return set; - def_ippool = create_pool(IPPOOL_ADDRESS, NULL); - def_dppool = create_pool(IPPOOL_PREFIX, NULL); + set->def_ippool = create_pool(&set->ippools, NULL); + set->def_dppool = create_pool(&set->dppools, NULL); + if (!set->def_ippool || !set->def_dppool) + goto err; list_for_each_entry(opt, &s->items, entry) { + enum ippool_type type; + const char *val; + struct ip6_pool *pool; + #ifdef RADIUS if (triton_module_loaded("radius")) { if (!strcmp(opt->name, "vendor")) { - conf_vendor = parse_vendor_opt(opt->val); + if (opt->val) + conf_vendor = parse_vendor_opt(opt->val); continue; } else if (!strcmp(opt->name, "attr-prefix")) { - dppool_attr = parse_attr_opt(opt->val); + if (opt->val) + dppool_attr = parse_attr_opt(opt->val); continue; } else if (!strcmp(opt->name, "attr-address")) { - ippool_attr = parse_attr_opt(opt->val); + if (opt->val) + 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) + if (opt->val && inet_pton(AF_INET6, opt->val, &set->gw_addr) == 0) log_error("ipv6_pool: failed to parse '%s'\n", opt->raw); continue; - } else if (!strcmp(opt->name, "delegate")) { + } + if (!strcmp(opt->name, "reload-orphan")) { + if (opt->val && !strcmp(opt->val, "disconnect")) + set->orphan_policy = ORPHAN_DISCONNECT; + else + set->orphan_policy = ORPHAN_KEEP; + continue; + } + + if (!strcmp(opt->name, "delegate")) { type = IPPOOL_PREFIX; val = opt->val; } else { @@ -492,24 +570,15 @@ static void ippool_init2(void) val = opt->name; } - if (parse_options(type, opt->raw, &pool, &next)) { + if (!val) + continue; + + if (parse_line_opts(set, type, opt->raw, &pool)) { log_error("ipv6_pool: failed to parse '%s'\n", opt->raw); continue; } - add_prefix(type, pool, val); - - if (next) - pool->next = next; - } - - list_for_each_entry(pool, &ippool_list, entry) { - if (list_empty(&pool->items)) - log_warn("ipv6_pool: pool '%s' is empty or not defined\n", pool->name); - } - list_for_each_entry(pool, &dppool_list, entry) { - if (list_empty(&pool->items)) - log_warn("ipv6_pool: delegate pool '%s' is empty or not defined\n", pool->name); + add_prefix(pool, val); } #ifdef RADIUS @@ -518,9 +587,435 @@ static void ippool_init2(void) 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 + + if (finalize_set(set)) + goto err; + + return set; + +err: + free_pool_set(set); + return NULL; +} + +/* ===== address lookup ===== */ + +static int pool_contains(struct ip6_pool *p, const struct in6_addr *addr, + struct ip6_range **out_r, uint64_t *out_bit) +{ + struct ip6_range *r; + + list_for_each_entry(r, &p->ranges, entry) { + uint64_t bit = range_addr_to_bit(r, addr); + if (bit != BM_INVALID) { + *out_r = r; + *out_bit = bit; + return 1; + } + } + return 0; +} + +static int find_target(struct list_head *pool_list, struct ip6_pool *def_pool, + const struct in6_addr *addr, const char *pref_name, + struct ip6_pool **op, struct ip6_range **orr, uint64_t *obit) +{ + struct ip6_pool *p; + + if (pref_name) { + p = find_pool(pool_list, pref_name, 0); + if (p && pool_contains(p, addr, orr, obit)) { + *op = p; + return 1; + } + } + + if (def_pool && pool_contains(def_pool, addr, orr, obit)) { + *op = def_pool; + return 1; + } + list_for_each_entry(p, pool_list, entry) { + if (pool_contains(p, addr, orr, obit)) { + *op = p; + return 1; + } + } + + return 0; +} + +/* alloc a free bit from a pool's next-chain; returns range+bit+pool or NULL */ +static struct ip6_range *alloc_from(struct ip6_pool *pool, struct ip6_pool **found_p, uint64_t *found_bit) +{ + struct ip6_pool *start = pool; + struct ip6_range *r, *found_r = NULL; + uint64_t bit = BM_INVALID; + + do { + spin_lock(&pool->lock); + list_for_each_entry(r, &pool->ranges, entry) { + bit = bm_find_free(r->bitmap, r->count, r->cursor); + if (bit != BM_INVALID) { + bm_set(r->bitmap, bit); + r->used++; + r->cursor = bit + 1; + found_r = r; + *found_p = pool; + *found_bit = bit; + break; + } + } + spin_unlock(&pool->lock); + if (found_r) + return found_r; + pool = pool->next; + } while (pool && pool != start); + + return NULL; +} + +/* ===== ipdb get/put: NA ===== */ + +static struct ipv6db_item_t *get_ip(struct ap_session *ses) +{ + struct pool_set *set; + struct ip6_pool *pool, *found_p = NULL; + struct ip6_range *r; + struct ip6_lease *lease; + uint64_t bit; + + pthread_rwlock_rdlock(&pool_set_rwlock); + set = cur_set; + if (!set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + pool = ses->ipv6_pool_name ? find_pool(&set->ippools, ses->ipv6_pool_name, 0) : set->def_ippool; + if (!pool) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + r = alloc_from(pool, &found_p, &bit); + if (!r) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + lease = _malloc(sizeof(*lease)); + if (!lease) { + spin_lock(&found_p->lock); + bm_clear(r->bitmap, bit); + r->used--; + spin_unlock(&found_p->lock); + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + memset(lease, 0, sizeof(*lease)); + lease->pool = found_p; + lease->range = r; + lease->it.owner = &ipdb; + INIT_LIST_HEAD(&lease->it.addr_list); + in6_index_to_addr(&lease->node.addr, &r->start, bit, r->shift); + lease->node.prefix_len = r->prefix_len; + list_add_tail(&lease->node.entry, &lease->it.addr_list); + + if (r->prefix_len == 128) { + memcpy(&lease->it.intf_id, set->gw_addr.s6_addr + 8, 8); + memcpy(&lease->it.peer_intf_id, lease->node.addr.s6_addr + 8, 8); + } else { + lease->it.intf_id = 0; + lease->it.peer_intf_id = 0; + } + + pthread_rwlock_unlock(&pool_set_rwlock); + + return &lease->it; +} + +static void put_ip(struct ap_session *ses, struct ipv6db_item_t *it) +{ + struct ip6_lease *lease = container_of(it, typeof(*lease), it); + + pthread_rwlock_rdlock(&pool_set_rwlock); + if (lease->pool && lease->range) { + uint64_t bit = range_addr_to_bit(lease->range, &lease->node.addr); + spin_lock(&lease->pool->lock); + if (bit != BM_INVALID && bm_test(lease->range->bitmap, bit)) { + bm_clear(lease->range->bitmap, bit); + lease->range->used--; + } + spin_unlock(&lease->pool->lock); + } + pthread_rwlock_unlock(&pool_set_rwlock); + + _free(lease); +} + +/* ===== ipdb get/put: PD ===== */ + +static struct ipv6db_prefix_t *get_dp(struct ap_session *ses) +{ + struct pool_set *set; + struct ip6_pool *pool, *found_p = NULL; + struct ip6_range *r; + struct dp_lease *lease; + uint64_t bit; + + pthread_rwlock_rdlock(&pool_set_rwlock); + set = cur_set; + if (!set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + pool = ses->dpv6_pool_name ? find_pool(&set->dppools, ses->dpv6_pool_name, 0) : set->def_dppool; + if (!pool) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + r = alloc_from(pool, &found_p, &bit); + if (!r) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + lease = _malloc(sizeof(*lease)); + if (!lease) { + spin_lock(&found_p->lock); + bm_clear(r->bitmap, bit); + r->used--; + spin_unlock(&found_p->lock); + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + memset(lease, 0, sizeof(*lease)); + lease->pool = found_p; + lease->range = r; + lease->it.owner = &ipdb; + INIT_LIST_HEAD(&lease->it.prefix_list); + in6_index_to_addr(&lease->node.addr, &r->start, bit, r->shift); + lease->node.prefix_len = r->prefix_len; + list_add_tail(&lease->node.entry, &lease->it.prefix_list); + + pthread_rwlock_unlock(&pool_set_rwlock); + + return &lease->it; +} + +static void put_dp(struct ap_session *ses, struct ipv6db_prefix_t *it) +{ + struct dp_lease *lease = container_of(it, typeof(*lease), it); + + pthread_rwlock_rdlock(&pool_set_rwlock); + if (lease->pool && lease->range) { + uint64_t bit = range_addr_to_bit(lease->range, &lease->node.addr); + spin_lock(&lease->pool->lock); + if (bit != BM_INVALID && bm_test(lease->range->bitmap, bit)) { + bm_clear(lease->range->bitmap, bit); + lease->range->used--; + } + spin_unlock(&lease->pool->lock); + } + pthread_rwlock_unlock(&pool_set_rwlock); + + _free(lease); +} + +static struct ipdb_t ipdb = { + .get_ipv6 = get_ip, + .put_ipv6 = put_ip, + .get_ipv6_prefix = get_dp, + .put_ipv6_prefix = put_dp, +}; + +/* ===== reconcile on reload ===== */ + +static void reserve_bit(struct ip6_pool *np, struct ip6_range *nr, uint64_t bit) +{ + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; + } + spin_unlock(&np->lock); +} + +static void reconcile_na(struct pool_set *new_set, struct ap_session *ses, + int policy, struct list_head *disc) +{ + struct ipv6db_item_t *it = ses->ipv6; + struct ip6_pool *np; + struct ip6_range *nr; + uint64_t bit; + + if (!it || !it->owner) + return; + + if (it->owner == &ipdb) { + struct ip6_lease *lease = container_of(it, typeof(*lease), it); + if (find_target(&new_set->ippools, new_set->def_ippool, &lease->node.addr, + ses->ipv6_pool_name, &np, &nr, &bit)) { + reserve_bit(np, nr, bit); + lease->pool = np; + lease->range = nr; + } else { + lease->pool = NULL; + lease->range = NULL; + if (policy == ORPHAN_DISCONNECT) { + struct disc_node *d = _malloc(sizeof(*d)); + if (d) { + d->ses = ses; + list_add_tail(&d->entry, disc); + } + } + } + } else { + struct ipv6db_addr_t *a; + list_for_each_entry(a, &it->addr_list, entry) { + if (find_target(&new_set->ippools, new_set->def_ippool, &a->addr, + NULL, &np, &nr, &bit)) + reserve_bit(np, nr, bit); + } + } +} + +static void reconcile_pd(struct pool_set *new_set, struct ap_session *ses, + int policy, struct list_head *disc) +{ + struct ipv6db_prefix_t *it = ses->ipv6_dp; + struct ip6_pool *np; + struct ip6_range *nr; + uint64_t bit; + + if (!it || !it->owner) + return; + + if (it->owner == &ipdb) { + struct dp_lease *lease = container_of(it, typeof(*lease), it); + if (find_target(&new_set->dppools, new_set->def_dppool, &lease->node.addr, + ses->dpv6_pool_name, &np, &nr, &bit)) { + reserve_bit(np, nr, bit); + lease->pool = np; + lease->range = nr; + } else { + lease->pool = NULL; + lease->range = NULL; + if (policy == ORPHAN_DISCONNECT) { + struct disc_node *d = _malloc(sizeof(*d)); + if (d) { + d->ses = ses; + list_add_tail(&d->entry, disc); + } + } + } + } else { + struct ipv6db_addr_t *a; + list_for_each_entry(a, &it->prefix_list, entry) { + if (find_target(&new_set->dppools, new_set->def_dppool, &a->addr, + NULL, &np, &nr, &bit)) + reserve_bit(np, nr, bit); + } + } +} + +static void terminate_orphan(void *arg) +{ + struct ap_session *ses = arg; + ap_session_terminate(ses, TERM_NAS_REBOOT, 0); +} + +static void load_config(void *data) +{ + struct pool_set *new_set, *old_set; + struct ap_session *ses; + struct disc_node *d; + LIST_HEAD(disc_list); + int policy; + + new_set = build_pool_set(); + if (!new_set) { + log_error("ipv6_pool: reload failed, keeping current pools\n"); + return; + } + policy = new_set->orphan_policy; + + pthread_rwlock_wrlock(&pool_set_rwlock); + pthread_rwlock_rdlock(&ses_lock); + + list_for_each_entry(ses, &ses_list, entry) { + reconcile_na(new_set, ses, policy, &disc_list); + reconcile_pd(new_set, ses, policy, &disc_list); + } + + old_set = cur_set; + cur_set = new_set; + + pthread_rwlock_unlock(&ses_lock); + pthread_rwlock_unlock(&pool_set_rwlock); + + free_pool_set(old_set); + + while (!list_empty(&disc_list)) { + d = list_first_entry(&disc_list, typeof(*d), entry); + list_del(&d->entry); + triton_context_call(d->ses->ctrl->ctx, terminate_orphan, d->ses); + _free(d); + } +} + +#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->dpv6_pool_name) + _free(ses->dpv6_pool_name); + ses->dpv6_pool_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); + } + } +} +#endif + +/* ===== init ===== */ + +static void ippool_init1(void) +{ + ipdb_register(&ipdb); +} + +static void ippool_init2(void) +{ + load_config(NULL); + + if (triton_event_register_handler(EV_CONFIG_RELOAD, load_config) < 0) + log_error("ipv6_pool: registration of CONFIG_RELOAD event failed," + " pools will not reload\n"); + +#ifdef RADIUS + if (triton_module_loaded("radius")) + triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); +#endif } DEFINE_INIT(51, ippool_init1); |
