blob: ece0784ffaf5233024f8c26ce76347ea31b9ece7 [file] [log] [blame]
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#include "pool.h"
#include "buffer.h"
#include "error.h"
#include "socket.h"
#include "otime.h"
#include "memdbg.h"
#if P2MP
static void
ifconfig_pool_entry_free(struct ifconfig_pool_entry *ipe, bool hard)
{
ipe->in_use = false;
if (hard && ipe->common_name)
{
free(ipe->common_name);
ipe->common_name = NULL;
}
if (hard)
{
ipe->last_release = 0;
}
else
{
ipe->last_release = now;
}
}
static int
ifconfig_pool_find(struct ifconfig_pool *pool, const char *common_name)
{
int i;
time_t earliest_release = 0;
int previous_usage = -1;
int new_usage = -1;
for (i = 0; i < pool->size; ++i)
{
struct ifconfig_pool_entry *ipe = &pool->list[i];
if (!ipe->in_use)
{
/*
* If duplicate_cn mode, take first available IP address
*/
if (pool->duplicate_cn)
{
new_usage = i;
break;
}
/*
* Keep track of the unused IP address entry which
* was released earliest.
*/
if ((new_usage == -1 || ipe->last_release < earliest_release) && !ipe->fixed)
{
earliest_release = ipe->last_release;
new_usage = i;
}
/*
* Keep track of a possible allocation to us
* from an earlier session.
*/
if (previous_usage < 0
&& common_name
&& ipe->common_name
&& !strcmp(common_name, ipe->common_name))
{
previous_usage = i;
}
}
}
if (previous_usage >= 0)
{
return previous_usage;
}
if (new_usage >= 0)
{
return new_usage;
}
return -1;
}
/*
* Verify start/end range
*/
bool
ifconfig_pool_verify_range(const int msglevel, const in_addr_t start, const in_addr_t end)
{
struct gc_arena gc = gc_new();
bool ret = true;
if (start > end)
{
msg(msglevel, "--ifconfig-pool start IP [%s] is greater than end IP [%s]",
print_in_addr_t(start, 0, &gc),
print_in_addr_t(end, 0, &gc));
ret = false;
}
if (end - start >= IFCONFIG_POOL_MAX)
{
msg(msglevel, "--ifconfig-pool address range is too large [%s -> %s]. Current maximum is %d addresses, as defined by IFCONFIG_POOL_MAX variable.",
print_in_addr_t(start, 0, &gc),
print_in_addr_t(end, 0, &gc),
IFCONFIG_POOL_MAX);
ret = false;
}
gc_free(&gc);
return ret;
}
struct ifconfig_pool *
ifconfig_pool_init(const bool ipv4_pool, enum pool_type type, in_addr_t start,
in_addr_t end, const bool duplicate_cn,
const bool ipv6_pool, const struct in6_addr ipv6_base,
const int ipv6_netbits )
{
struct gc_arena gc = gc_new();
struct ifconfig_pool *pool = NULL;
int pool_ipv4_size = -1, pool_ipv6_size = -1;
ASSERT(start <= end && end - start < IFCONFIG_POOL_MAX);
ALLOC_OBJ_CLEAR(pool, struct ifconfig_pool);
pool->duplicate_cn = duplicate_cn;
pool->ipv4.enabled = ipv4_pool;
if (pool->ipv4.enabled)
{
pool->ipv4.type = type;
switch (pool->ipv4.type)
{
case IFCONFIG_POOL_30NET:
pool->ipv4.base = start & ~3;
pool_ipv4_size = (((end | 3) + 1) - pool->ipv4.base) >> 2;
break;
case IFCONFIG_POOL_INDIV:
pool->ipv4.base = start;
pool_ipv4_size = end - start + 1;
break;
default:
ASSERT(0);
}
if (pool_ipv4_size < 2)
{
msg(M_FATAL, "IPv4 pool size is too small (%d), must be at least 2",
pool_ipv4_size);
}
msg(D_IFCONFIG_POOL, "IFCONFIG POOL IPv4: base=%s size=%d",
print_in_addr_t(pool->ipv4.base, 0, &gc), pool_ipv4_size);
pool->size = pool_ipv4_size;
}
/* IPv6 pools are always "INDIV" type */
pool->ipv6.enabled = ipv6_pool;
if (pool->ipv6.enabled)
{
/* the host portion of the address will always be contained in the last
* 4 bytes, therefore we can just extract that and use it as base in
* integer form
*/
uint32_t base = (ipv6_base.s6_addr[12] << 24)
| (ipv6_base.s6_addr[13] << 16)
| (ipv6_base.s6_addr[14] << 8)
| ipv6_base.s6_addr[15];
/* some bits of the last 4 bytes may still be part of the network
* portion of the address, therefore we need to set them to 0
*/
if ((128 - ipv6_netbits) < 32)
{
/* extract only the bits that are really part of the host portion of
* the address.
*
* Example: if we have netbits=31, the first bit has to be zero'd,
* the following operation first computes mask=0x3fffff and then
* uses mask to extract the wanted bits from base
*/
uint32_t mask = (1 << (128 - ipv6_netbits) ) - 1;
base &= mask;
}
pool->ipv6.base = ipv6_base;
/* if a pool starts at a base address that has all-zero in the
* host part, that first IPv6 address must not be assigned to
* clients because it is not usable (subnet anycast address).
* Start with 1, then.
*
* NOTE: this will also (mis-)fire for something like
* ifconfig-ipv6-pool 2001:db8:0:1:1234::0/64
* as we only check the rightmost 32 bits of the host part. So be it.
*/
if (base == 0)
{
msg(D_IFCONFIG_POOL, "IFCONFIG POOL IPv6: incrementing pool start "
"to avoid ::0 assignment");
base++;
pool->ipv6.base.s6_addr[15]++;
}
pool_ipv6_size = ipv6_netbits >= 112
? (1 << (128 - ipv6_netbits)) - base
: IFCONFIG_POOL_MAX;
if (pool_ipv6_size < 2)
{
msg(M_FATAL, "IPv6 pool size is too small (%d), must be at least 2",
pool_ipv6_size);
}
msg(D_IFCONFIG_POOL, "IFCONFIG POOL IPv6: base=%s size=%d netbits=%d",
print_in6_addr(pool->ipv6.base, 0, &gc), pool_ipv6_size,
ipv6_netbits);
/* if there is no v4 pool, or the v6 pool is smaller, use
* v6 pool size as "unified pool size"
*/
if (pool->size <= 0 || pool_ipv6_size < pool->size)
{
pool->size = pool_ipv6_size;
}
}
if (pool->ipv4.enabled && pool->ipv6.enabled)
{
if (pool_ipv4_size < pool_ipv6_size)
{
msg(M_INFO, "NOTE: IPv4 pool size is %d, IPv6 pool size is %d. "
"IPv4 pool size limits the number of clients that can be "
"served from the pool", pool_ipv4_size, pool_ipv6_size);
}
else if (pool_ipv4_size > pool_ipv6_size)
{
msg(M_WARN, "WARNING: IPv4 pool size is %d, IPv6 pool size is %d. "
"IPv6 pool size limits the number of clients that can be "
"served from the pool. This is likely a MISTAKE - please check "
"your configuration", pool_ipv4_size, pool_ipv6_size);
}
}
ASSERT(pool->size > 0);
ALLOC_ARRAY_CLEAR(pool->list, struct ifconfig_pool_entry, pool->size);
gc_free(&gc);
return pool;
}
void
ifconfig_pool_free(struct ifconfig_pool *pool)
{
if (pool)
{
int i;
for (i = 0; i < pool->size; ++i)
{
ifconfig_pool_entry_free(&pool->list[i], true);
}
free(pool->list);
free(pool);
}
}
ifconfig_pool_handle
ifconfig_pool_acquire(struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote, struct in6_addr *remote_ipv6, const char *common_name)
{
int i;
i = ifconfig_pool_find(pool, common_name);
if (i >= 0)
{
struct ifconfig_pool_entry *ipe = &pool->list[i];
ASSERT(!ipe->in_use);
ifconfig_pool_entry_free(ipe, true);
ipe->in_use = true;
if (common_name)
{
ipe->common_name = string_alloc(common_name, NULL);
}
if (pool->ipv4.enabled && local && remote)
{
switch (pool->ipv4.type)
{
case IFCONFIG_POOL_30NET:
{
in_addr_t b = pool->ipv4.base + (i << 2);
*local = b + 1;
*remote = b + 2;
break;
}
case IFCONFIG_POOL_INDIV:
{
in_addr_t b = pool->ipv4.base + i;
*local = 0;
*remote = b;
break;
}
default:
ASSERT(0);
}
}
/* IPv6 pools are always INDIV (--linear) */
if (pool->ipv6.enabled && remote_ipv6)
{
*remote_ipv6 = add_in6_addr(pool->ipv6.base, i);
}
}
return i;
}
bool
ifconfig_pool_release(struct ifconfig_pool *pool, ifconfig_pool_handle hand, const bool hard)
{
bool ret = false;
if (pool && hand >= 0 && hand < pool->size)
{
ifconfig_pool_entry_free(&pool->list[hand], hard);
ret = true;
}
return ret;
}
/*
* private access functions
*/
/* currently handling IPv4 logic only */
static ifconfig_pool_handle
ifconfig_pool_ip_base_to_handle(const struct ifconfig_pool *pool, const in_addr_t addr)
{
ifconfig_pool_handle ret = -1;
switch (pool->ipv4.type)
{
case IFCONFIG_POOL_30NET:
{
ret = (addr - pool->ipv4.base) >> 2;
break;
}
case IFCONFIG_POOL_INDIV:
{
ret = (addr - pool->ipv4.base);
break;
}
default:
ASSERT(0);
}
if (ret < 0 || ret >= pool->size)
{
ret = -1;
}
return ret;
}
static ifconfig_pool_handle
ifconfig_pool_ipv6_base_to_handle(const struct ifconfig_pool *pool,
const struct in6_addr *in_addr)
{
ifconfig_pool_handle ret;
uint32_t base, addr;
/* IPv6 pool is always IFCONFIG_POOL_INDIV.
*
* We assume the offset can't be larger than 2^32-1, therefore we compute
* the difference only among the last 4 bytes like if they were two 32bit
* long integers. The rest of the address must match.
*/
for (int i = 0; i < (12); i++)
{
if (pool->ipv6.base.s6_addr[i] != in_addr->s6_addr[i])
{
return -1;
}
}
base = (pool->ipv6.base.s6_addr[12] << 24)
| (pool->ipv6.base.s6_addr[13] << 16)
| (pool->ipv6.base.s6_addr[14] << 8)
| pool->ipv6.base.s6_addr[15];
addr = (in_addr->s6_addr[12] << 24)
| (in_addr->s6_addr[13] << 16)
| (in_addr->s6_addr[14] << 8)
| in_addr->s6_addr[15];
ret = addr - base;
if (ret < 0 || ret >= pool->size)
{
ret = -1;
}
return ret;
}
static in_addr_t
ifconfig_pool_handle_to_ip_base(const struct ifconfig_pool *pool, ifconfig_pool_handle hand)
{
in_addr_t ret = 0;
if (pool->ipv4.enabled && hand >= 0 && hand < pool->size)
{
switch (pool->ipv4.type)
{
case IFCONFIG_POOL_30NET:
{
ret = pool->ipv4.base + (hand << 2);
break;
}
case IFCONFIG_POOL_INDIV:
{
ret = pool->ipv4.base + hand;
break;
}
default:
ASSERT(0);
}
}
return ret;
}
static struct in6_addr
ifconfig_pool_handle_to_ipv6_base(const struct ifconfig_pool *pool, ifconfig_pool_handle hand)
{
struct in6_addr ret = IN6ADDR_ANY_INIT;
/* IPv6 pools are always INDIV (--linear) */
if (pool->ipv6.enabled && hand >= 0 && hand < pool->size)
{
ret = add_in6_addr( pool->ipv6.base, hand );
}
return ret;
}
static void
ifconfig_pool_set(struct ifconfig_pool *pool, const char *cn,
ifconfig_pool_handle h, const bool fixed)
{
struct ifconfig_pool_entry *e = &pool->list[h];
ifconfig_pool_entry_free(e, true);
e->in_use = false;
e->common_name = string_alloc(cn, NULL);
e->last_release = now;
e->fixed = fixed;
}
static void
ifconfig_pool_list(const struct ifconfig_pool *pool, struct status_output *out)
{
if (pool && out)
{
struct gc_arena gc = gc_new();
int i;
for (i = 0; i < pool->size; ++i)
{
const struct ifconfig_pool_entry *e = &pool->list[i];
struct in6_addr ip6;
in_addr_t ip;
const char *ip6_str = "";
const char *ip_str = "";
if (e->common_name)
{
if (pool->ipv4.enabled)
{
ip = ifconfig_pool_handle_to_ip_base(pool, i);
ip_str = print_in_addr_t(ip, 0, &gc);
}
if (pool->ipv6.enabled)
{
ip6 = ifconfig_pool_handle_to_ipv6_base(pool, i);
ip6_str = print_in6_addr(ip6, 0, &gc);
}
status_printf(out, "%s,%s,%s", e->common_name, ip_str, ip6_str);
}
}
gc_free(&gc);
}
}
static void
ifconfig_pool_msg(const struct ifconfig_pool *pool, int msglevel)
{
struct status_output *so = status_open(NULL, 0, msglevel, NULL, 0);
ASSERT(so);
status_printf(so, "IFCONFIG POOL LIST");
ifconfig_pool_list(pool, so);
status_close(so);
}
/*
* Deal with reading/writing the ifconfig pool database to a file
*/
struct ifconfig_pool_persist *
ifconfig_pool_persist_init(const char *filename, int refresh_freq)
{
struct ifconfig_pool_persist *ret;
ASSERT(filename);
ALLOC_OBJ_CLEAR(ret, struct ifconfig_pool_persist);
if (refresh_freq > 0)
{
ret->fixed = false;
ret->file = status_open(filename, refresh_freq, -1, NULL, STATUS_OUTPUT_READ|STATUS_OUTPUT_WRITE);
}
else
{
ret->fixed = true;
ret->file = status_open(filename, 0, -1, NULL, STATUS_OUTPUT_READ);
}
return ret;
}
void
ifconfig_pool_persist_close(struct ifconfig_pool_persist *persist)
{
if (persist)
{
if (persist->file)
{
status_close(persist->file);
}
free(persist);
}
}
bool
ifconfig_pool_write_trigger(struct ifconfig_pool_persist *persist)
{
if (persist->file)
{
return status_trigger(persist->file);
}
else
{
return false;
}
}
void
ifconfig_pool_read(struct ifconfig_pool_persist *persist, struct ifconfig_pool *pool)
{
const int buf_size = 128;
update_time();
if (persist && persist->file && pool)
{
struct gc_arena gc = gc_new();
struct buffer in = alloc_buf_gc(256, &gc);
char *cn_buf, *ip_buf, *ip6_buf;
int line = 0;
ALLOC_ARRAY_CLEAR_GC(cn_buf, char, buf_size, &gc);
ALLOC_ARRAY_CLEAR_GC(ip_buf, char, buf_size, &gc);
ALLOC_ARRAY_CLEAR_GC(ip6_buf, char, buf_size, &gc);
while (true)
{
ASSERT(buf_init(&in, 0));
if (!status_read(persist->file, &in))
{
break;
}
++line;
if (!BLEN(&in))
{
continue;
}
int c = *BSTR(&in);
if (c == '#' || c == ';')
{
continue;
}
msg(M_INFO, "ifconfig_pool_read(), in='%s'", BSTR(&in));
/* The expected format of a line is: "CN,IP4,IP6".
*
* IP4 or IP6 may be empty when respectively no v4 or v6 pool
* was previously specified.
*
* This means that accepted strings can be:
* - CN,IP4,IP6
* - CN,IP4
* - CN,,IP6
*/
if (!buf_parse(&in, ',', cn_buf, buf_size)
|| !buf_parse(&in, ',', ip_buf, buf_size))
{
continue;
}
ifconfig_pool_handle h = -1, h6 = -1;
if (strlen(ip_buf) > 0)
{
bool v4_ok = true;
in_addr_t addr = getaddr(GETADDR_HOST_ORDER, ip_buf, 0, &v4_ok,
NULL);
if (!v4_ok)
{
msg(M_WARN, "pool: invalid IPv4 (%s) for CN=%s", ip_buf,
cn_buf);
}
else
{
h = ifconfig_pool_ip_base_to_handle(pool, addr);
if (h < 0)
{
msg(M_WARN,
"pool: IPv4 (%s) out of pool range for CN=%s",
ip_buf, cn_buf);
}
}
}
if (buf_parse(&in, ',', ip6_buf, buf_size) && strlen(ip6_buf) > 0)
{
struct in6_addr addr6;
if (!get_ipv6_addr(ip6_buf, &addr6, NULL, M_WARN))
{
msg(M_WARN, "pool: invalid IPv6 (%s) for CN=%s", ip6_buf,
cn_buf);
}
else
{
h6 = ifconfig_pool_ipv6_base_to_handle(pool, &addr6);
if (h6 < 0)
{
msg(M_WARN,
"pool: IPv6 (%s) out of pool range for CN=%s",
ip6_buf, cn_buf);
}
/* Rely on IPv6 if no IPv4 was provided or the one provided
* was not valid
*/
if (h < 0)
{
h = h6;
}
}
}
/* 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.
*/
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)
{
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);
gc_free(&gc);
}
}
void
ifconfig_pool_write(struct ifconfig_pool_persist *persist, const struct ifconfig_pool *pool)
{
if (persist && persist->file && (status_rw_flags(persist->file) & STATUS_OUTPUT_WRITE) && pool)
{
status_reset(persist->file);
ifconfig_pool_list(pool, persist->file);
status_flush(persist->file);
}
}
/*
* TESTING ONLY
*/
#ifdef IFCONFIG_POOL_TEST
#define DUP_CN
void
ifconfig_pool_test(in_addr_t start, in_addr_t end)
{
struct gc_arena gc = gc_new();
struct ifconfig_pool *p = ifconfig_pool_init(IFCONFIG_POOL_30NET, start, end);
/*struct ifconfig_pool *p = ifconfig_pool_init (IFCONFIG_POOL_INDIV, start, end);*/
ifconfig_pool_handle array[256];
int i;
CLEAR(array);
msg(M_INFO | M_NOPREFIX, "************ 1");
for (i = 0; i < (int) SIZE(array); ++i)
{
char *cn;
ifconfig_pool_handle h;
in_addr_t local, remote;
char buf[256];
openvpn_snprintf(buf, sizeof(buf), "common-name-%d", i);
#ifdef DUP_CN
cn = NULL;
#else
cn = buf;
#endif
h = ifconfig_pool_acquire(p, &local, &remote, NULL, cn);
if (h < 0)
{
break;
}
msg(M_INFO | M_NOPREFIX, "IFCONFIG_POOL TEST pass 1: l=%s r=%s cn=%s",
print_in_addr_t(local, 0, &gc),
print_in_addr_t(remote, 0, &gc),
cn);
array[i] = h;
}
msg(M_INFO | M_NOPREFIX, "************* 2");
for (i = (int) SIZE(array) / 16; i < (int) SIZE(array) / 8; ++i)
{
msg(M_INFO, "Attempt to release %d cn=%s", array[i], p->list[i].common_name);
if (!ifconfig_pool_release(p, array[i]))
{
break;
}
msg(M_INFO, "Succeeded");
}
CLEAR(array);
msg(M_INFO | M_NOPREFIX, "**************** 3");
for (i = 0; i < (int) SIZE(array); ++i)
{
char *cn;
ifconfig_pool_handle h;
in_addr_t local, remote;
char buf[256];
snprintf(buf, sizeof(buf), "common-name-%d", i+24);
#ifdef DUP_CN
cn = NULL;
#else
cn = buf;
#endif
h = ifconfig_pool_acquire(p, &local, &remote, NULL, cn);
if (h < 0)
{
break;
}
msg(M_INFO | M_NOPREFIX, "IFCONFIG_POOL TEST pass 3: l=%s r=%s cn=%s",
print_in_addr_t(local, 0, &gc),
print_in_addr_t(remote, 0, &gc),
cn);
array[i] = h;
}
ifconfig_pool_free(p);
gc_free(&gc);
}
#endif /* ifdef IFCONFIG_POOL_TEST */
#endif /* if P2MP */