summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Fedoryshchenko <denys.f@collabora.com>2026-06-03 02:23:41 +0300
committerDenys Fedoryshchenko <denys.f@collabora.com>2026-06-03 14:57:45 +0300
commit8afec90643a6e6d923032ae5c50d6c18e7a8b0c3 (patch)
tree3d97ec841068a6d5a32fd3c2ac9a3555e61c2e7a
parent8494d63943e69e2f7217ca401f6b9ba46b44cca8 (diff)
downloadaccel-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.conf4
-rw-r--r--accel-pppd/accel-ppp.conf.534
-rw-r--r--accel-pppd/extra/bitpool.h88
-rw-r--r--accel-pppd/extra/bitpool_test.c256
-rw-r--r--accel-pppd/extra/ippool.c1167
-rw-r--r--accel-pppd/extra/ipv6pool.c1101
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);