/* * 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-2010 OpenVPN Technologies, Inc. * * 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 (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #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 (int type, in_addr_t start, in_addr_t end, const bool duplicate_cn) { struct gc_arena gc = gc_new (); struct ifconfig_pool *pool = NULL; ASSERT (start <= end && end - start < IFCONFIG_POOL_MAX); ALLOC_OBJ_CLEAR (pool, struct ifconfig_pool); pool->type = type; pool->duplicate_cn = duplicate_cn; switch (type) { case IFCONFIG_POOL_30NET: pool->base = start & ~3; pool->size = (((end | 3) + 1) - pool->base) >> 2; break; case IFCONFIG_POOL_INDIV: pool->base = start; pool->size = end - start + 1; break; default: ASSERT (0); } ALLOC_ARRAY_CLEAR (pool->list, struct ifconfig_pool_entry, pool->size); msg (D_IFCONFIG_POOL, "IFCONFIG POOL: base=%s size=%d", print_in_addr_t (pool->base, 0, &gc), 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, 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); switch (pool->type) { case IFCONFIG_POOL_30NET: { in_addr_t b = pool->base + (i << 2); *local = b + 1; *remote = b + 2; break; } case IFCONFIG_POOL_INDIV: { in_addr_t b = pool->base + i; *local = 0; *remote = b; break; } default: ASSERT (0); } } 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 */ 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->type) { case IFCONFIG_POOL_30NET: { ret = (addr - pool->base) >> 2; break; } case IFCONFIG_POOL_INDIV: { ret = (addr - pool->base); break; } default: ASSERT (0); } 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 (hand >= 0 && hand < pool->size) { switch (pool->type) { case IFCONFIG_POOL_30NET: { ret = pool->base + (hand << 2);; break; } case IFCONFIG_POOL_INDIV: { ret = pool->base + hand; break; } default: ASSERT (0); } } return ret; } static void ifconfig_pool_set (struct ifconfig_pool* pool, const char *cn, const in_addr_t addr, const bool fixed) { ifconfig_pool_handle h = ifconfig_pool_ip_base_to_handle (pool, addr); if (h >= 0) { 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]; if (e->common_name) { const in_addr_t ip = ifconfig_pool_handle_to_ip_base (pool, i); status_printf (out, "%s,%s", e->common_name, print_in_addr_t (ip, 0, &gc)); } } 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; char *ip_buf; int line = 0; ALLOC_ARRAY_CLEAR_GC (cn_buf, char, buf_size, &gc); ALLOC_ARRAY_CLEAR_GC (ip_buf, char, buf_size, &gc); while (true) { ASSERT (buf_init (&in, 0)); if (!status_read (persist->file, &in)) break; ++line; if (BLEN (&in)) { int c = *BSTR(&in); if (c == '#' || c == ';') continue; if (buf_parse (&in, ',', cn_buf, buf_size) && buf_parse (&in, ',', ip_buf, buf_size)) { bool succeeded; const in_addr_t addr = getaddr (GETADDR_HOST_ORDER, ip_buf, 0, &succeeded, NULL); if (succeeded) { ifconfig_pool_set (pool, cn_buf, addr, 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, 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, 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 #endif