Attention is currently required from: flichtenheld, ordex, plaisthos.
Hello flichtenheld, plaisthos,
I'd like you to reexamine a change. Please visit
http://gerrit.openvpn.net/c/openvpn/+/1783?usp=email
to look at the new patch set (#3).
Change subject: multi: assign clients from a named, tagged subnet-pool
......................................................................
multi: assign clients from a named, tagged subnet-pool
Add two server directives:
subnet-pool <tag> <network> <netmask> [<gateway>] (main config)
subnet-pool-tag <tag> (per-client CCD)
so groups of clients can be given addresses from subnets other than the
--server network (per-group firewall rules under --topology subnet, and
thus DCO). A client's subnet-pool-tag is resolved to its pool and served
a virtual IP from that subnet: a static --ifconfig-push within it, or the
next free address from a dynamic per-group pool shared by tag and created
on first use. Such a client is validated against its own subnet instead
of the global tunnel-network constraint and never falls back to the
global pool; a tag matching no configured pool is declined with
AUTH_FAILED.
Change-Id: Ia1e7db95615d99ef9687de7c4e996dc8033e2631
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/multi.h
M src/openvpn/options.c
M src/openvpn/options.h
M src/openvpn/options_util.c
M src/openvpn/options_util.h
M tests/unit_tests/openvpn/Makefile.am
A tests/unit_tests/openvpn/test_subnet_pool.c
10 files changed, 323 insertions(+), 5 deletions(-)
git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/83/1783/3
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 23541a5d..3463b5a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -671,6 +671,7 @@
"test_provider"
"test_socket"
"test_ssl"
+ "test_subnet_pool"
"test_user_pass"
"test_push_update_msg"
)
@@ -829,6 +830,11 @@
src/openvpn/list.c
)
+ target_sources(test_subnet_pool PRIVATE
+ tests/unit_tests/openvpn/mock_get_random.c
+ src/openvpn/options_util.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 f420588..b6464af 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -330,6 +330,37 @@
by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local``
IP address.
+--subnet-pool args
+ Define a named pool of addresses from a subnet different from the
+ ``--server`` network, so groups of clients can be given IPs from separate
+ subnets for per-group firewall rules under ``--topology subnet`` (and
+ therefore DCO). This is a main-config directive and may be repeated to
+ define several pools.
+
+ Valid syntax:
+ ::
+
+ subnet-pool tag network netmask [gateway]
+
+ ``tag`` is a name a client refers to with ``--subnet-pool-tag``; ``gateway``
+ defaults to the first address of ``network``/``netmask``. A client assigned
+ to a pool receives (dynamically, or via a static ``--ifconfig-push`` that
+ must lie within the subnet) an address from it, plus the matching netmask
+ and a ``--route-gateway`` inside the subnet so that its pushed routes
+ install on-link.
+
+ A host route to the server's own VPN IP is not pushed, as it is not needed
+ to reach the networks behind the server. If your clients need to reach the
+ server's VPN address itself, add an explicit
+ ``push "route <server-VPN-IP> 255.255.255.255"`` (or the ``route-ipv6``
+ equivalent) to the server config.
+
+--subnet-pool-tag tag
+ Assign this client to the ``--subnet-pool`` named ``tag``. Must be
+ associated with a specific client instance via ``--client-config-dir`` or
+ ``--client-connect``. Several clients may reference the same tag and are
+ then served from the same shared pool.
+
--ifconfig-ipv6-push args
for ``--client-config-dir`` per-client static IPv6 interface
configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index c4293a7..da46cf4 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -42,6 +42,7 @@
#include "vlan.h"
#include "auth_token.h"
#include "route.h"
+#include "options_util.h"
#include <inttypes.h>
#include <string.h>
@@ -53,6 +54,17 @@
#include "dco.h"
#include "reflect_filter.h"
+/*
+ * Dynamic address pool serving the clients of one --subnet-pool tag. Shared
+ * by every client that references the tag, created on first use.
+ */
+struct subnet_pool
+{
+ struct subnet_pool *next;
+ const char *tag;
+ struct ifconfig_pool *pool;
+};
+
/*#define MULTI_DEBUG_EVENT_LOOP*/
#ifdef MULTI_DEBUG_EVENT_LOOP
@@ -612,7 +624,8 @@
schedule_remove_entry(m->schedule, (struct schedule_entry *)mi);
- ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, false);
+ ifconfig_pool_release(mi->vaddr_pool ? mi->vaddr_pool :
m->ifconfig_pool, mi->vaddr_handle,
+ false);
if (mi->did_iroutes)
{
@@ -687,6 +700,14 @@
schedule_free(m->schedule);
mbuf_free(m->mbuf);
ifconfig_pool_free(m->ifconfig_pool);
+ while (m->subnet_pools)
+ {
+ struct subnet_pool *sp = m->subnet_pools;
+
+ m->subnet_pools = sp->next;
+ ifconfig_pool_free(sp->pool);
+ free(sp);
+ }
frequency_limit_free(m->new_connection_limiter);
initial_rate_limit_free(m->initial_rate_limiter);
multi_reap_free(m->reaper);
@@ -1384,6 +1405,12 @@
const struct options *o = &c->options;
if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined)
{
+ /* a subnet-pool client is validated against its own subnet */
+ if (o->subnet_pool_defined)
+ {
+ return (o->subnet_pool_netmask & c->c2.push_ifconfig_local)
+ == o->subnet_pool_network;
+ }
return (o->push_ifconfig_constraint_netmask &
c->c2.push_ifconfig_local)
== o->push_ifconfig_constraint_network;
}
@@ -1394,6 +1421,34 @@
}
/*
+ * Return the dynamic pool serving the clients tagged with <tag>, creating it
+ * on first use from the matching --subnet-pool definition.
+ */
+static struct ifconfig_pool *
+multi_get_group_pool(struct multi_context *m, const char *tag)
+{
+ struct subnet_pool *sp;
+
+ for (sp = m->subnet_pools; sp; sp = sp->next)
+ {
+ if (!strcmp(sp->tag, tag))
+ {
+ return sp->pool;
+ }
+ }
+
+ const struct subnet_pool_def *d4 =
subnet_pool_by_tag(m->top.options.subnet_pools, tag);
+ ALLOC_OBJ_CLEAR(sp, struct subnet_pool);
+ sp->tag = d4->tag;
+ sp->pool = ifconfig_pool_init(true, IFCONFIG_POOL_INDIV, d4->network + 2,
+ (d4->network | ~d4->netmask) - 1,
m->top.options.duplicate_cn,
+ false, in6addr_any, 0);
+ sp->next = m->subnet_pools;
+ m->subnet_pools = sp;
+ return sp->pool;
+}
+
+/*
* Select a virtual address for a new client instance.
* Use an --ifconfig-push directive, if given (static IP).
* Otherwise use an --ifconfig-pool address (dynamic IP).
@@ -1403,6 +1458,21 @@
{
struct gc_arena gc = gc_new();
+ /* resolve a CCD --subnet-pool-tag against the main-config --subnet-pool
list
+ * (an unknown tag is rejected with AUTH_FAILED in multi_client_connect) */
+ if (mi->context.options.subnet_pool_tag &&
!mi->context.options.subnet_pool_defined)
+ {
+ const struct subnet_pool_def *d =
+ subnet_pool_by_tag(m->top.options.subnet_pools,
mi->context.options.subnet_pool_tag);
+ if (d)
+ {
+ mi->context.options.subnet_pool_defined = true;
+ mi->context.options.subnet_pool_network = d->network;
+ mi->context.options.subnet_pool_netmask = d->netmask;
+ mi->context.options.subnet_pool_gateway = d->gateway;
+ }
+ }
+
/*
* If ifconfig addresses were set by dynamic config file,
* release pool addresses, otherwise keep them.
@@ -1413,8 +1483,10 @@
* release dynamic allocation */
if (mi->vaddr_handle >= 0)
{
- ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, true);
+ ifconfig_pool_release(mi->vaddr_pool ? mi->vaddr_pool :
m->ifconfig_pool,
+ mi->vaddr_handle, true);
mi->vaddr_handle = -1;
+ mi->vaddr_pool = NULL;
}
mi->context.c2.push_ifconfig_defined = true;
@@ -1434,6 +1506,44 @@
"MULTI_sva: WARNING: if --ifconfig-push is used for IPv4,
automatic IPv6 assignment from --ifconfig-ipv6-pool does not work. Use
--ifconfig-ipv6-push for IPv6 then.");
}
}
+ else if (mi->context.options.subnet_pool_defined)
+ {
+ /* dynamic address from this client's --subnet-pool group */
+
+ /* the CCD is read after a global-pool address may already have been
+ * acquired; drop it so we can serve this client from its group */
+ if (mi->vaddr_handle >= 0 && !mi->vaddr_pool)
+ {
+ ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, true);
+ mi->vaddr_handle = -1;
+ }
+
+ if (mi->vaddr_handle < 0)
+ {
+ in_addr_t local = 0, remote = 0;
+ const char *cn = NULL;
+
+ if (!mi->context.options.duplicate_cn)
+ {
+ 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_handle = ifconfig_pool_acquire(mi->vaddr_pool, &local,
&remote, NULL, cn);
+ if (mi->vaddr_handle >= 0)
+ {
+ mi->context.c2.push_ifconfig_local = remote;
+ mi->context.c2.push_ifconfig_remote_netmask =
+ mi->context.options.subnet_pool_netmask;
+ mi->context.c2.push_ifconfig_defined = true;
+ }
+ else
+ {
+ msg(D_MULTI_ERRORS, "MULTI: no free --subnet-pool addresses
are available for %s",
+ multi_instance_string(mi, false, &gc));
+ }
+ }
+ }
else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a
pool address */
{
in_addr_t local = 0, remote = 0;
@@ -2339,10 +2449,9 @@
const char *ifconfig_constraint_netmask =
print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc);
- /* JYFIXME -- this should cause the connection to fail */
msg(D_MULTI_ERRORS,
- "MULTI ERROR: primary virtual IP for %s (%s) "
- "violates tunnel network/netmask constraint (%s/%s)",
+ "WARNING: primary virtual IP for %s (%s) "
+ "violates tunnel network/netmask constraint (%s/%s) and may not
work",
multi_instance_string(mi, false, &gc),
print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
ifconfig_constraint_network, ifconfig_constraint_netmask);
@@ -2450,6 +2559,7 @@
/* reset pool handle to null */
mi->vaddr_handle = -1;
+ mi->vaddr_pool = NULL;
/* do --client-connect setenvs */
multi_select_virtual_addr(m, mi);
@@ -2741,6 +2851,15 @@
cc_succeeded = false;
}
+ if (mi->context.options.subnet_pool_tag
+ && !subnet_pool_by_tag(m->top.options.subnet_pools,
mi->context.options.subnet_pool_tag))
+ {
+ msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to unknown
--subnet-pool-tag '%s'",
+ mi->context.options.subnet_pool_tag);
+ auth_set_client_reason(mi->context.c2.tls_multi, "unknown
subnet-pool-tag");
+ cc_succeeded = false;
+ }
+
if (cc_succeeded)
{
multi_client_connect_late_setup(m, mi, *option_types_found);
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 8b837fa..f6815f4 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -121,6 +121,8 @@
struct mroute_addr real; /**< External network address of the
* remote peer. */
ifconfig_pool_handle vaddr_handle;
+ /* pool vaddr_handle belongs to (NULL = the global one) */
+ struct ifconfig_pool *vaddr_pool;
char msg_prefix[MULTI_PREFIX_MAX_LENGTH];
/* queued outgoing data in Server/TCP mode */
@@ -176,6 +178,8 @@
* instances. */
struct multi_io *multi_io; /**< I/O state and events tracker */
struct ifconfig_pool *ifconfig_pool;
+ /* per-group --subnet-pool dynamic pools */
+ struct subnet_pool *subnet_pools;
struct frequency_limit *new_connection_limiter;
struct initial_packet_rate_limit *initial_rate_limiter;
struct mroute_helper *route_helper;
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 87218d4..3f9f7de 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -443,6 +443,11 @@
" If seconds=0, file will be treated as read-only.\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"
+ " addresses outside the --server network, selected per\n"
+ " client with --subnet-pool-tag.\n"
+ "--subnet-pool-tag tag : Assign this client to the --subnet-pool named
tag.\n"
+ " Only valid in a client-specific config file.\n"
"--ifconfig-push local remote-netmask : Push an ifconfig option to
remote,\n"
" overrides --ifconfig-pool dynamic allocation.\n"
" Only valid in a client-specific config file.\n"
@@ -7631,6 +7636,33 @@
goto err;
}
}
+ else if (streq(p[0], "subnet-pool") && p[1] && p[2] && p[3] && !p[5])
+ {
+ struct subnet_pool_def *sp;
+ in_addr_t network, netmask;
+
+ VERIFY_PERMISSION(OPT_P_GENERAL);
+ network = getaddr(GETADDR_HOST_ORDER | GETADDR_RESOLVE, p[2], 0, NULL,
NULL);
+ netmask = getaddr(GETADDR_HOST_ORDER, p[3], 0, NULL, NULL);
+ if (!network || !netmask)
+ {
+ msg(msglevel, "cannot parse --subnet-pool addresses");
+ goto err;
+ }
+ ALLOC_OBJ_GC(sp, struct subnet_pool_def, &options->gc);
+ sp->tag = p[1];
+ sp->network = network & netmask;
+ sp->netmask = netmask;
+ sp->gateway = p[4] ? getaddr(GETADDR_HOST_ORDER | GETADDR_RESOLVE,
p[4], 0, NULL, NULL)
+ : (network & netmask) + 1;
+ sp->next = options->subnet_pools;
+ options->subnet_pools = sp;
+ }
+ else if (streq(p[0], "subnet-pool-tag") && p[1] && !p[2])
+ {
+ VERIFY_PERMISSION(OPT_P_INSTANCE);
+ options->subnet_pool_tag = p[1];
+ }
else if (streq(p[0], "ifconfig-push-constraint") && p[1] && p[2] && !p[3])
{
in_addr_t network, netmask;
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index a111cf8..0c810b3 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -250,6 +250,17 @@
struct verify_hash_list *next;
};
+/* A named --subnet-pool: a subnet clients can be assigned from, referenced
+ * from a CCD file by its tag via --subnet-pool-tag. */
+struct subnet_pool_def
+{
+ struct subnet_pool_def *next;
+ const char *tag;
+ in_addr_t network;
+ in_addr_t netmask;
+ in_addr_t gateway;
+};
+
/* Command line options */
struct options
{
@@ -515,6 +526,12 @@
bool push_ifconfig_constraint_defined;
in_addr_t push_ifconfig_constraint_network;
in_addr_t push_ifconfig_constraint_netmask;
+ struct subnet_pool_def *subnet_pools; /* named pools (server config) */
+ const char *subnet_pool_tag; /* which pool this client uses (CCD)
*/
+ bool subnet_pool_defined; /* resolved from the tag at connect
*/
+ in_addr_t subnet_pool_network;
+ in_addr_t subnet_pool_netmask;
+ in_addr_t subnet_pool_gateway;
bool push_ifconfig_ipv4_blocked; /* IPv4 */
bool push_ifconfig_ipv6_defined; /* IPv6 */
struct in6_addr push_ifconfig_ipv6_local; /* IPv6 */
diff --git a/src/openvpn/options_util.c b/src/openvpn/options_util.c
index 47fe0bc..ba04917 100644
--- a/src/openvpn/options_util.c
+++ b/src/openvpn/options_util.c
@@ -31,6 +31,19 @@
#include "push.h"
+const struct subnet_pool_def *
+subnet_pool_by_tag(const struct subnet_pool_def *pools, const char *tag)
+{
+ for (const struct subnet_pool_def *d = pools; d; d = d->next)
+ {
+ if (!strcmp(d->tag, tag))
+ {
+ return d;
+ }
+ }
+ return NULL;
+}
+
const char *
parse_auth_failed_temp(struct options *o, const char *reason)
{
diff --git a/src/openvpn/options_util.h b/src/openvpn/options_util.h
index 1d4ea9b..c54357c 100644
--- a/src/openvpn/options_util.h
+++ b/src/openvpn/options_util.h
@@ -28,6 +28,16 @@
const char *parse_auth_failed_temp(struct options *o, const char *reason);
+/**
+ * Look up a named --subnet-pool in a list of pool definitions.
+ *
+ * @param pools head of the --subnet-pool list (may be NULL)
+ * @param tag the --subnet-pool-tag to match
+ * @return the matching pool definition, or NULL if none matches
+ */
+const struct subnet_pool_def *subnet_pool_by_tag(const struct subnet_pool_def
*pools,
+ const char *tag);
+
/** Checks if the string is a valid integer by checking if it can be
* converted to an integer */
diff --git a/tests/unit_tests/openvpn/Makefile.am
b/tests/unit_tests/openvpn/Makefile.am
index 1128eb4..bfb7284 100644
--- a/tests/unit_tests/openvpn/Makefile.am
+++ b/tests/unit_tests/openvpn/Makefile.am
@@ -21,6 +21,7 @@
push_update_msg_testdriver \
socket_testdriver \
ssl_testdriver \
+ subnet_pool_testdriver \
user_pass_testdriver
if HAVE_LD_WRAP_SUPPORT
@@ -361,6 +362,19 @@
$(top_srcdir)/src/openvpn/platform.c \
$(top_srcdir)/src/openvpn/mbuf.c
+subnet_pool_testdriver_CFLAGS = \
+ -I$(top_srcdir)/include -I$(top_srcdir)/src/compat
-I$(top_srcdir)/src/openvpn \
+ @TEST_CFLAGS@
+
+subnet_pool_testdriver_LDFLAGS = @TEST_LDFLAGS@
+
+subnet_pool_testdriver_SOURCES = test_subnet_pool.c \
+ mock_msg.c test_common.h \
+ mock_get_random.c \
+ $(top_srcdir)/src/openvpn/buffer.c \
+ $(top_srcdir)/src/openvpn/options_util.c \
+ $(top_srcdir)/src/openvpn/platform.c
+
misc_testdriver_CFLAGS = \
-I$(top_srcdir)/include -I$(top_srcdir)/src/compat
-I$(top_srcdir)/src/openvpn \
-DSOURCEDIR=\"$(top_srcdir)\" @TEST_CFLAGS@
diff --git a/tests/unit_tests/openvpn/test_subnet_pool.c
b/tests/unit_tests/openvpn/test_subnet_pool.c
new file mode 100644
index 0000000..8e73b64
--- /dev/null
+++ b/tests/unit_tests/openvpn/test_subnet_pool.c
@@ -0,0 +1,72 @@
+/*
+ * 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 "options.h"
+#include "options_util.h"
+#include "test_common.h"
+#include "mock_msg.h"
+
+/* --subnet-pool-tag resolves a client to its --subnet-pool; an unknown tag
+ * returns NULL, which is what makes the server reject that client. */
+static void
+test_subnet_pool_by_tag(void **state)
+{
+ struct subnet_pool_def c = { .next = NULL, .tag = "grpC", .network =
0x0a020300, .netmask = 0xffffff00, .gateway = 0x0a020301 };
+ struct subnet_pool_def b = { .next = &c, .tag = "grpB", .network =
0x0a020200, .netmask = 0xffffff00, .gateway = 0x0a020201 };
+ struct subnet_pool_def a = { .next = &b, .tag = "grpA", .network =
0x0a020100, .netmask = 0xffffff00, .gateway = 0x0a020101 };
+
+ /* every configured tag resolves to its own pool, wherever it sits */
+ assert_ptr_equal(subnet_pool_by_tag(&a, "grpA"), &a);
+ assert_ptr_equal(subnet_pool_by_tag(&a, "grpB"), &b);
+ assert_ptr_equal(subnet_pool_by_tag(&a, "grpC"), &c);
+
+ /* and it carries that pool's subnet/gateway */
+ const struct subnet_pool_def *r = subnet_pool_by_tag(&a, "grpB");
+ assert_int_equal(r->network, 0x0a020200);
+ assert_int_equal(r->netmask, 0xffffff00);
+ assert_int_equal(r->gateway, 0x0a020201);
+
+ /* unknown tag and empty list both miss */
+ assert_null(subnet_pool_by_tag(&a, "nope"));
+ assert_null(subnet_pool_by_tag(NULL, "grpA"));
+}
+
+const struct CMUnitTest subnet_pool_tests[] = {
+ cmocka_unit_test(test_subnet_pool_by_tag),
+};
+
+int
+main(void)
+{
+ openvpn_unit_test_setup();
+ return cmocka_run_group_tests(subnet_pool_tests, NULL, NULL);
+}
--
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1783?usp=email
To unsubscribe, or for help writing mail filters, visit
http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: Ia1e7db95615d99ef9687de7c4e996dc8033e2631
Gerrit-Change-Number: 1783
Gerrit-PatchSet: 3
Gerrit-Owner: ordex <[email protected]>
Gerrit-Reviewer: flichtenheld <[email protected]>
Gerrit-Reviewer: plaisthos <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: plaisthos <[email protected]>
Gerrit-Attention: flichtenheld <[email protected]>
Gerrit-Attention: ordex <[email protected]>
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel