[L] Change in openvpn[master]: pool, multi: persist subnet pools via --ifconfig-pool-persist

"ordex \(Code Review\) via Openvpn-devel" <[email protected]>
Newsgroups gmane.network.openvpn.devel
Message-ID <[email protected]>
Attention is currently required from: plaisthos.

Hello plaisthos,

I'd like you to do a code review.
Please visit

    http://gerrit.openvpn.net/c/openvpn/+/1790?usp=email

to review the following change.


Change subject: pool, multi: persist subnet pools via --ifconfig-pool-persist
......................................................................

pool, multi: persist subnet pools via --ifconfig-pool-persist

Extend --ifconfig-pool-persist so its single file covers the global pool
and every --subnet-pool. ifconfig_pool_read()/ifconfig_pool_write() now
operate on the set of pools: each persisted entry is routed to the pool
whose address range owns it, and an entry matching no configured pool is
ignored on load (e.g. a --subnet-pool that was removed). When persist is
enabled the subnet pools are created eagerly at startup so their saved
allocations can be reloaded before the first client connects.

Change-Id: I0bff8d0a549a65b7da01ea0e785b7ec8bea17f05
GitHub: closes openvpn/OpenVPN#987
Signed-off-by: Antonio Quartulli <[email protected]>
---
M CMakeLists.txt
M doc/man-sections/server-options.rst
M src/openvpn/multi.c
M src/openvpn/options.c
M src/openvpn/pool.c
M src/openvpn/pool.h
M tests/unit_tests/openvpn/Makefile.am
A tests/unit_tests/openvpn/test_pool.c
8 files changed, 393 insertions(+), 39 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/90/1790/1

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3463b5a..0259234 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -668,6 +668,7 @@
         "test_options_parse"
         "test_packet_id"
         "test_pkt"
+        "test_pool"
         "test_provider"
         "test_socket"
         "test_ssl"
@@ -835,6 +836,15 @@
         src/openvpn/options_util.c
         )
 
+    target_sources(test_pool PRIVATE
+        tests/unit_tests/openvpn/mock_get_random.c
+        src/openvpn/pool.c
+        src/openvpn/status.c
+        src/openvpn/otime.c
+        src/openvpn/fdmisc.c
+        src/openvpn/interval.c
+        )
+
     target_sources(test_ncp PRIVATE
         src/openvpn/crypto_epoch.c
         src/openvpn/crypto_mbedtls.c
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index 0146594..910a414 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -275,6 +275,11 @@
   ``file`` is a comma-delimited ASCII file, formatted as
   :code:`<Common-Name>,<IP-address>`.
 
+  A single ``file`` covers the global pool and every ``--subnet-pool``: each
+  entry is associated with a pool by its address, and on load an entry whose
+  address falls in no configured pool (for example a ``--subnet-pool`` that is
+  no longer defined) is ignored.
+
   If ``seconds`` = :code:`0`, ``file`` will be treated as read-only. This
   is useful if you would like to treat ``file`` as a configuration file.
 
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 1cd84b6..a281764 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -65,6 +65,41 @@
     struct ifconfig_pool *pool;
 };
 
+static struct ifconfig_pool *multi_get_group_pool(struct multi_context *m,
+                                                  const struct options *o, const char *tag);
+
+/*
+ * Collect every address pool (the global one plus each --subnet-pool group)
+ * into a gc-allocated array, for the single combined --ifconfig-pool-persist
+ * file.  Returns the number of pools.
+ */
+static int
+multi_all_pools(struct multi_context *m, struct gc_arena *gc, struct ifconfig_pool ***pools)
+{
+    struct subnet_pool *sp;
+    int n = m->ifconfig_pool ? 1 : 0;
+
+    for (sp = m->subnet_pools; sp; sp = sp->next)
+    {
+        n++;
+    }
+
+    struct ifconfig_pool **arr = gc_malloc(n * sizeof(struct ifconfig_pool *), true, gc);
+    int i = 0;
+
+    if (m->ifconfig_pool)
+    {
+        arr[i++] = m->ifconfig_pool;
+    }
+    for (sp = m->subnet_pools; sp; sp = sp->next)
+    {
+        arr[i++] = sp->pool;
+    }
+
+    *pools = arr;
+    return n;
+}
+
 /*#define MULTI_DEBUG_EVENT_LOOP*/
 
 #ifdef MULTI_DEBUG_EVENT_LOOP
@@ -161,11 +196,16 @@
 void
 multi_ifconfig_pool_persist(struct multi_context *m, bool force)
 {
-    /* write pool data to file */
-    if (m->ifconfig_pool && m->top.c1.ifconfig_pool_persist
+    /* write the global pool and every --subnet-pool to the one persist file */
+    if (m->top.c1.ifconfig_pool_persist
         && (force || ifconfig_pool_write_trigger(m->top.c1.ifconfig_pool_persist)))
     {
-        ifconfig_pool_write(m->top.c1.ifconfig_pool_persist, m->ifconfig_pool);
+        struct gc_arena gc = gc_new();
+        struct ifconfig_pool **pools;
+        int n = multi_all_pools(m, &gc, &pools);
+
+        ifconfig_pool_write(m->top.c1.ifconfig_pool_persist, pools, n);
+        gc_free(&gc);
     }
 }
 
@@ -368,12 +408,29 @@
             t->options.ifconfig_pool_end, t->options.duplicate_cn,
             t->options.ifconfig_ipv6_pool_defined, t->options.ifconfig_ipv6_pool_base,
             t->options.ifconfig_ipv6_pool_netbits);
+    }
 
-        /* reload pool data from file */
-        if (t->c1.ifconfig_pool_persist)
+    /* With --ifconfig-pool-persist the single file covers the global pool and
+     * every configured --subnet-pool, so eagerly create the subnet pools and
+     * reload them all in one pass (entries for unknown pools are ignored). */
+    if (t->c1.ifconfig_pool_persist)
+    {
+        struct gc_arena gc = gc_new();
+        struct ifconfig_pool **pools;
+        int n;
+
+        for (const struct subnet_pool_def *d = t->options.subnet_pools; d; d = d->next)
         {
-            ifconfig_pool_read(t->c1.ifconfig_pool_persist, m->ifconfig_pool);
+            multi_get_group_pool(m, &t->options, d->tag);
         }
+        for (const struct subnet_pool6_def *d = t->options.subnet_pools_ipv6; d; d = d->next)
+        {
+            multi_get_group_pool(m, &t->options, d->tag);
+        }
+
+        n = multi_all_pools(m, &gc, &pools);
+        ifconfig_pool_read(t->c1.ifconfig_pool_persist, pools, n);
+        gc_free(&gc);
     }
 
     /*
@@ -1425,7 +1482,7 @@
  * on first use from the matching --subnet-pool definition.
  */
 static struct ifconfig_pool *
-multi_get_group_pool(struct multi_context *m, const char *tag)
+multi_get_group_pool(struct multi_context *m, const struct options *o, const char *tag)
 {
     struct subnet_pool *sp;
 
@@ -1437,14 +1494,13 @@
         }
     }
 
-    const struct subnet_pool_def *d4 = subnet_pool_by_tag(m->top.options.subnet_pools, tag);
-    const struct subnet_pool6_def *d6 = subnet_pool6_by_tag(m->top.options.subnet_pools_ipv6, tag);
+    const struct subnet_pool_def *d4 = subnet_pool_by_tag(o->subnet_pools, tag);
+    const struct subnet_pool6_def *d6 = subnet_pool6_by_tag(o->subnet_pools_ipv6, tag);
     ALLOC_OBJ_CLEAR(sp, struct subnet_pool);
     sp->tag = d4 ? d4->tag : d6->tag;
     sp->pool = ifconfig_pool_init(d4 != NULL, IFCONFIG_POOL_INDIV, d4 ? d4->network + 2 : 0,
-                                  d4 ? (d4->network | ~d4->netmask) - 1 : 0,
-                                  m->top.options.duplicate_cn, d6 != NULL,
-                                  d6 ? add_in6_addr(d6->network, 2) : in6addr_any,
+                                  d4 ? (d4->network | ~d4->netmask) - 1 : 0, o->duplicate_cn,
+                                  d6 != NULL, d6 ? add_in6_addr(d6->network, 2) : in6addr_any,
                                   d6 ? (int)d6->netbits : 0);
     sp->next = m->subnet_pools;
     m->subnet_pools = sp;
@@ -1549,7 +1605,8 @@
                 cn = tls_common_name(mi->context.c2.tls_multi, true);
             }
 
-            mi->vaddr_pool = multi_get_group_pool(m, mi->context.options.subnet_pool_tag);
+            mi->vaddr_pool =
+                multi_get_group_pool(m, &m->top.options, mi->context.options.subnet_pool_tag);
             mi->vaddr_handle =
                 ifconfig_pool_acquire(mi->vaddr_pool, &local, &remote, &remote_ipv6, cn);
             if (mi->vaddr_handle < 0)
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index b4485ca..40a195d 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -441,6 +441,7 @@
     "--ifconfig-pool-persist file [seconds] : Persist/unpersist ifconfig-pool\n"
     "                  data to file, at seconds intervals (default=600).\n"
     "                  If seconds=0, file will be treated as read-only.\n"
+    "                  Covers the global pool and every --subnet-pool.\n"
     "--ifconfig-ipv6-pool base-IP/bits : set aside an IPv6 network block\n"
     "                  to be dynamically allocated to connecting clients.\n"
     "--subnet-pool tag network netmask [gateway] : Define a named pool of\n"
@@ -2566,10 +2567,11 @@
         MUST_BE_UNDEF(route_delay_defined, "route-delay");
         MUST_BE_UNDEF(up_delay, "up-delay");
         if (!options->ifconfig_pool_defined && !options->ifconfig_ipv6_pool_defined
+            && !options->subnet_pools && !options->subnet_pools_ipv6
             && options->ifconfig_pool_persist_filename)
         {
-            msg(M_USAGE,
-                "--ifconfig-pool-persist must be used with --ifconfig-pool or --ifconfig-ipv6-pool");
+            msg(M_USAGE, "--ifconfig-pool-persist must be used with --ifconfig-pool, "
+                         "--ifconfig-ipv6-pool or --subnet-pool");
         }
         if (options->ifconfig_ipv6_pool_defined && !options->ifconfig_ipv6_local)
         {
diff --git a/src/openvpn/pool.c b/src/openvpn/pool.c
index ff74e7c..b260cc8 100644
--- a/src/openvpn/pool.c
+++ b/src/openvpn/pool.c
@@ -583,13 +583,14 @@
 }
 
 void
-ifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool *pool)
+ifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool **pools,
+                   int n_pools)
 {
     const int buf_size = 128;
 
     update_time();
 
-    if (persist && persist->file && pool)
+    if (persist && persist->file && n_pools > 0)
     {
         struct gc_arena gc = gc_new();
         struct buffer in = alloc_buf_gc(256, &gc);
@@ -634,6 +635,9 @@
                 continue;
             }
 
+            /* the address selects which pool this entry belongs to; an entry
+             * matching none of the configured pools is ignored */
+            struct ifconfig_pool *pool = NULL;
             ifconfig_pool_handle h = -1, h6 = -1;
 
             if (strlen(ip_buf) > 0)
@@ -647,10 +651,13 @@
                 }
                 else
                 {
-                    h = ifconfig_pool_ip_base_to_handle(pool, addr);
-                    if (h < 0)
+                    for (int i = 0; i < n_pools && !pool; ++i)
                     {
-                        msg(M_WARN, "pool: IPv4 (%s) out of pool range for CN=%s", ip_buf, cn_buf);
+                        h = ifconfig_pool_ip_base_to_handle(pools[i], addr);
+                        if (h >= 0)
+                        {
+                            pool = pools[i];
+                        }
                     }
                 }
             }
@@ -663,12 +670,20 @@
                 {
                     msg(M_WARN, "pool: invalid IPv6 (%s) for CN=%s", ip6_buf, cn_buf);
                 }
+                else if (pool)
+                {
+                    /* IPv4 already picked the pool; v4 and v6 share the handle */
+                    h6 = ifconfig_pool_ipv6_base_to_handle(pool, &addr6);
+                }
                 else
                 {
-                    h6 = ifconfig_pool_ipv6_base_to_handle(pool, &addr6);
-                    if (h6 < 0)
+                    for (int i = 0; i < n_pools && !pool; ++i)
                     {
-                        msg(M_WARN, "pool: IPv6 (%s) out of pool range for CN=%s", ip6_buf, cn_buf);
+                        h6 = ifconfig_pool_ipv6_base_to_handle(pools[i], &addr6);
+                        if (h6 >= 0)
+                        {
+                            pool = pools[i];
+                        }
                     }
 
                     /* Rely on IPv6 if no IPv4 was provided or the one provided
@@ -681,40 +696,44 @@
                 }
             }
 
-            /* at the moment IPv4 and IPv6 share the same pool, therefore offsets
-             * have to match for the same client.
-             *
-             * If offsets differ we use the IPv4, therefore warn the user about this.
-             */
+            /* IPv4 and IPv6 share the same pool handle, so their offsets have
+             * to match; if they differ we use the IPv4 and warn */
             if ((h6 >= 0) && (h != h6))
             {
                 msg(M_WARN, "pool: IPv4 (%s) and IPv6 (%s) have different offsets! Relying on IPv4",
                     ip_buf, ip6_buf);
             }
 
-            /* if at least one among v4 and v6 was properly parsed, attempt
-             * setting an handle for this client
-             */
-            if (h >= 0)
+            /* set the handle if the entry belongs to one of the pools */
+            if (pool && h >= 0)
             {
-                msg(M_INFO, "succeeded -> ifconfig_pool_set(hand=%d)", h);
                 ifconfig_pool_set(pool, cn_buf, h, persist->fixed);
             }
         }
 
-        ifconfig_pool_msg(pool, D_IFCONFIG_POOL);
+        for (int i = 0; i < n_pools; ++i)
+        {
+            ifconfig_pool_msg(pools[i], D_IFCONFIG_POOL);
+        }
 
         gc_free(&gc);
     }
 }
 
 void
-ifconfig_pool_write(struct ifconfig_pool_persist *persist, const struct ifconfig_pool *pool)
+ifconfig_pool_write(struct ifconfig_pool_persist *persist, struct ifconfig_pool *const *pools,
+                    int n_pools)
 {
-    if (persist && persist->file && (status_rw_flags(persist->file) & STATUS_OUTPUT_WRITE) && pool)
+    if (persist && persist->file && (status_rw_flags(persist->file) & STATUS_OUTPUT_WRITE))
     {
         status_reset(persist->file);
-        ifconfig_pool_list(pool, persist->file);
+        for (int i = 0; i < n_pools; ++i)
+        {
+            if (pools[i])
+            {
+                ifconfig_pool_list(pools[i], persist->file);
+            }
+        }
         status_flush(persist->file);
     }
 }
diff --git a/src/openvpn/pool.h b/src/openvpn/pool.h
index e2d3f82..79f3ac5 100644
--- a/src/openvpn/pool.h
+++ b/src/openvpn/pool.h
@@ -92,9 +92,15 @@
 
 bool ifconfig_pool_write_trigger(struct ifconfig_pool_persist *persist);
 
-void ifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool *pool);
+/* Read a persist file into a set of pools, routing each entry to the pool
+ * whose address range owns it.  Entries matching no pool (e.g. a --subnet-pool
+ * that is no longer configured) are ignored. */
+void ifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool **pools,
+                        int n_pools);
 
-void ifconfig_pool_write(struct ifconfig_pool_persist *persist, const struct ifconfig_pool *pool);
+/* Write a set of pools to a single persist file. */
+void ifconfig_pool_write(struct ifconfig_pool_persist *persist,
+                         struct ifconfig_pool *const *pools, int n_pools);
 
 #ifdef IFCONFIG_POOL_TEST
 void ifconfig_pool_test(in_addr_t start, in_addr_t end);
diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am
index bfb7284..d1d9574 100644
--- a/tests/unit_tests/openvpn/Makefile.am
+++ b/tests/unit_tests/openvpn/Makefile.am
@@ -17,6 +17,7 @@
 	options_parse_testdriver \
 	packet_id_testdriver \
 	pkt_testdriver \
+	pool_testdriver \
 	provider_testdriver \
 	push_update_msg_testdriver \
 	socket_testdriver \
@@ -362,6 +363,23 @@
 	$(top_srcdir)/src/openvpn/platform.c \
 	$(top_srcdir)/src/openvpn/mbuf.c
 
+pool_testdriver_CFLAGS  = \
+	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \
+	@TEST_CFLAGS@
+
+pool_testdriver_LDFLAGS = @TEST_LDFLAGS@
+
+pool_testdriver_SOURCES = test_pool.c \
+	mock_msg.c test_common.h \
+	mock_get_random.c \
+	$(top_srcdir)/src/openvpn/pool.c \
+	$(top_srcdir)/src/openvpn/status.c \
+	$(top_srcdir)/src/openvpn/buffer.c \
+	$(top_srcdir)/src/openvpn/otime.c \
+	$(top_srcdir)/src/openvpn/platform.c \
+	$(top_srcdir)/src/openvpn/fdmisc.c \
+	$(top_srcdir)/src/openvpn/interval.c
+
 subnet_pool_testdriver_CFLAGS  = \
 	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat -I$(top_srcdir)/src/openvpn \
 	@TEST_CFLAGS@
diff --git a/tests/unit_tests/openvpn/test_pool.c b/tests/unit_tests/openvpn/test_pool.c
new file mode 100644
index 0000000..daadb32
--- /dev/null
+++ b/tests/unit_tests/openvpn/test_pool.c
@@ -0,0 +1,237 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2026 OpenVPN Inc <[email protected]>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "syshead.h"
+
+#include <stdlib.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "pool.h"
+#include "buffer.h"
+#include "test_common.h"
+#include "mock_msg.h"
+
+/*
+ * pool.c pulls in a few socket.c/socket_util.c helpers for address parsing and
+ * formatting.  The pool logic under test does not need the full socket stack,
+ * so provide small IPv4 implementations here (the IPv6 ones are not reached by
+ * these IPv4-only pools, but must exist to link).
+ */
+struct signal_info;
+
+in_addr_t
+getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds, bool *succeeded,
+        struct signal_info *sig_info)
+{
+    struct in_addr a;
+    int ok = inet_pton(AF_INET, hostname, &a) == 1;
+
+    if (succeeded)
+    {
+        *succeeded = ok;
+    }
+    return ok ? ntohl(a.s_addr) : 0;
+}
+
+const char *
+print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena *gc)
+{
+    struct in_addr a = { .s_addr = htonl(addr) };
+    char buf[INET_ADDRSTRLEN] = { 0 };
+
+    inet_ntop(AF_INET, &a, buf, sizeof(buf));
+    return string_alloc(buf, gc);
+}
+
+bool
+get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,
+              msglvl_t msglevel)
+{
+    return false;
+}
+
+const char *
+print_in6_addr(struct in6_addr a6, unsigned int flags, struct gc_arena *gc)
+{
+    return "";
+}
+
+struct in6_addr
+add_in6_addr(struct in6_addr base, uint32_t add)
+{
+    return base;
+}
+
+#define IP(a, b, c, d) (((in_addr_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+
+/* three non-overlapping /24 pools: the global one and two --subnet-pool groups */
+#define GLOBAL_START IP(10, 1, 1, 2)
+#define GLOBAL_END   IP(10, 1, 1, 254)
+#define A_START      IP(10, 2, 1, 2)
+#define A_END        IP(10, 2, 1, 254)
+#define B_START      IP(10, 2, 2, 2)
+#define B_END        IP(10, 2, 2, 254)
+
+static struct ifconfig_pool *
+make_pool(in_addr_t start, in_addr_t end)
+{
+    struct in6_addr any = { 0 };
+
+    return ifconfig_pool_init(true, IFCONFIG_POOL_INDIV, start, end, false, false, any, 0);
+}
+
+static in_addr_t
+acquire(struct ifconfig_pool *pool, const char *cn)
+{
+    in_addr_t local = 0, remote = 0;
+
+    ifconfig_pool_acquire(pool, &local, &remote, NULL, cn);
+    return remote;
+}
+
+/* whether any entry of the pool is reserved for common name cn */
+static bool
+pool_has_cn(const struct ifconfig_pool *pool, const char *cn)
+{
+    for (int i = 0; i < pool->size; ++i)
+    {
+        if (pool->list[i].common_name && !strcmp(pool->list[i].common_name, cn))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+static const char *persist_file;
+
+/* Write {global, A, B} to one persist file, reload into a fresh set, and check
+ * that every entry was routed back to the pool that owns its address. */
+static void
+test_persist_roundtrip(void **state)
+{
+    struct ifconfig_pool *g = make_pool(GLOBAL_START, GLOBAL_END);
+    struct ifconfig_pool *a = make_pool(A_START, A_END);
+    struct ifconfig_pool *b = make_pool(B_START, B_END);
+
+    assert_int_equal(acquire(g, "alice"), GLOBAL_START);
+    assert_int_equal(acquire(a, "bob"), A_START);
+    assert_int_equal(acquire(b, "carol"), B_START);
+
+    struct ifconfig_pool *pools[] = { g, a, b };
+    struct ifconfig_pool_persist *wp = ifconfig_pool_persist_init(persist_file, 5);
+    ifconfig_pool_write(wp, pools, 3);
+    ifconfig_pool_persist_close(wp);
+
+    struct ifconfig_pool *g2 = make_pool(GLOBAL_START, GLOBAL_END);
+    struct ifconfig_pool *a2 = make_pool(A_START, A_END);
+    struct ifconfig_pool *b2 = make_pool(B_START, B_END);
+    struct ifconfig_pool *pools2[] = { g2, a2, b2 };
+    struct ifconfig_pool_persist *rp = ifconfig_pool_persist_init(persist_file, 0);
+    ifconfig_pool_read(rp, pools2, 3);
+    ifconfig_pool_persist_close(rp);
+
+    /* each client was routed to its own pool (handle 0 == the pool's base) */
+    assert_string_equal(g2->list[0].common_name, "alice");
+    assert_string_equal(a2->list[0].common_name, "bob");
+    assert_string_equal(b2->list[0].common_name, "carol");
+
+    /* and nowhere else */
+    assert_false(pool_has_cn(g2, "bob"));
+    assert_false(pool_has_cn(g2, "carol"));
+    assert_false(pool_has_cn(a2, "alice"));
+
+    /* the reservation is honoured: the same CN gets its persisted address back */
+    assert_int_equal(acquire(a2, "bob"), A_START);
+
+    ifconfig_pool_free(g);
+    ifconfig_pool_free(a);
+    ifconfig_pool_free(b);
+    ifconfig_pool_free(g2);
+    ifconfig_pool_free(a2);
+    ifconfig_pool_free(b2);
+}
+
+/* An entry whose address matches no configured pool (here: B's, when B is not
+ * in the set) is ignored on load and does not disturb the other pools. */
+static void
+test_persist_ignore_unknown(void **state)
+{
+    struct ifconfig_pool *g = make_pool(GLOBAL_START, GLOBAL_END);
+    struct ifconfig_pool *a = make_pool(A_START, A_END);
+    struct ifconfig_pool *b = make_pool(B_START, B_END);
+
+    acquire(g, "alice");
+    acquire(a, "bob");
+    acquire(b, "carol");
+
+    struct ifconfig_pool *pools[] = { g, a, b };
+    struct ifconfig_pool_persist *wp = ifconfig_pool_persist_init(persist_file, 5);
+    ifconfig_pool_write(wp, pools, 3);
+    ifconfig_pool_persist_close(wp);
+
+    /* reload into a set WITHOUT pool B: carol's entry now matches no pool */
+    struct ifconfig_pool *g2 = make_pool(GLOBAL_START, GLOBAL_END);
+    struct ifconfig_pool *a2 = make_pool(A_START, A_END);
+    struct ifconfig_pool *pools2[] = { g2, a2 };
+    struct ifconfig_pool_persist *rp = ifconfig_pool_persist_init(persist_file, 0);
+    ifconfig_pool_read(rp, pools2, 2);
+    ifconfig_pool_persist_close(rp);
+
+    assert_string_equal(g2->list[0].common_name, "alice");
+    assert_string_equal(a2->list[0].common_name, "bob");
+    assert_false(pool_has_cn(g2, "carol"));
+    assert_false(pool_has_cn(a2, "carol"));
+
+    ifconfig_pool_free(g);
+    ifconfig_pool_free(a);
+    ifconfig_pool_free(b);
+    ifconfig_pool_free(g2);
+    ifconfig_pool_free(a2);
+}
+
+const struct CMUnitTest pool_tests[] = {
+    cmocka_unit_test(test_persist_roundtrip),
+    cmocka_unit_test(test_persist_ignore_unknown),
+};
+
+int
+main(void)
+{
+    char template[] = "/tmp/ovpn_pool_persist_ut.XXXXXX";
+    int fd = mkstemp(template);
+
+    assert_true(fd >= 0);
+    close(fd);
+    persist_file = template;
+
+    openvpn_unit_test_setup();
+    int ret = cmocka_run_group_tests(pool_tests, NULL, NULL);
+
+    unlink(template);
+    return ret;
+}

-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1790?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I0bff8d0a549a65b7da01ea0e785b7ec8bea17f05
Gerrit-Change-Number: 1790
Gerrit-PatchSet: 1
Gerrit-Owner: ordex <[email protected]>
Gerrit-Reviewer: plaisthos <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: plaisthos <[email protected]>

_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.