mirror of
https://github.com/OpenVPN/openvpn.git
synced 2024-11-23 17:53:49 +08:00
ec4a500bb4
renegotiations -- this is similer to how the common name is also locked. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@5105 e7ae566f-a301-0410-adde-c780ea21d3b5
2660 lines
70 KiB
C
2660 lines
70 KiB
C
/*
|
|
* 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-2009 OpenVPN Technologies, 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 (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"
|
|
|
|
#if P2MP_SERVER
|
|
|
|
#include "multi.h"
|
|
#include "push.h"
|
|
#include "misc.h"
|
|
#include "otime.h"
|
|
#include "gremlin.h"
|
|
|
|
#include "memdbg.h"
|
|
|
|
#include "forward-inline.h"
|
|
#include "pf-inline.h"
|
|
|
|
/*#define MULTI_DEBUG_EVENT_LOOP*/
|
|
|
|
#ifdef MULTI_DEBUG_EVENT_LOOP
|
|
static const char *
|
|
id (struct multi_instance *mi)
|
|
{
|
|
if (mi)
|
|
return tls_common_name (mi->context.c2.tls_multi, false);
|
|
else
|
|
return "NULL";
|
|
}
|
|
#endif
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
static void
|
|
set_cc_config (struct multi_instance *mi, struct buffer_list *cc_config)
|
|
{
|
|
if (mi->cc_config)
|
|
buffer_list_free (mi->cc_config);
|
|
mi->cc_config = cc_config;
|
|
}
|
|
#endif
|
|
|
|
static bool
|
|
learn_address_script (const struct multi_context *m,
|
|
const struct multi_instance *mi,
|
|
const char *op,
|
|
const struct mroute_addr *addr)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
struct env_set *es;
|
|
bool ret = true;
|
|
struct plugin_list *plugins;
|
|
|
|
/* get environmental variable source */
|
|
if (mi && mi->context.c2.es)
|
|
es = mi->context.c2.es;
|
|
else
|
|
es = env_set_create (&gc);
|
|
|
|
/* get plugin source */
|
|
if (mi)
|
|
plugins = mi->context.plugins;
|
|
else
|
|
plugins = m->top.plugins;
|
|
|
|
if (plugin_defined (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS))
|
|
{
|
|
struct argv argv = argv_new ();
|
|
argv_printf (&argv, "%s %s",
|
|
op,
|
|
mroute_addr_print (addr, &gc));
|
|
if (mi)
|
|
argv_printf_cat (&argv, "%s", tls_common_name (mi->context.c2.tls_multi, false));
|
|
if (plugin_call (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
|
|
{
|
|
msg (M_WARN, "WARNING: learn-address plugin call failed");
|
|
ret = false;
|
|
}
|
|
argv_reset (&argv);
|
|
}
|
|
|
|
if (m->top.options.learn_address_script)
|
|
{
|
|
struct argv argv = argv_new ();
|
|
setenv_str (es, "script_type", "learn-address");
|
|
argv_printf (&argv, "%sc %s %s",
|
|
m->top.options.learn_address_script,
|
|
op,
|
|
mroute_addr_print (addr, &gc));
|
|
if (mi)
|
|
argv_printf_cat (&argv, "%s", tls_common_name (mi->context.c2.tls_multi, false));
|
|
if (!openvpn_execve_check (&argv, es, S_SCRIPT, "WARNING: learn-address command failed"))
|
|
ret = false;
|
|
argv_reset (&argv);
|
|
}
|
|
|
|
gc_free (&gc);
|
|
return ret;
|
|
}
|
|
|
|
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
|
|
&& (force || ifconfig_pool_write_trigger (m->top.c1.ifconfig_pool_persist)))
|
|
{
|
|
ifconfig_pool_write (m->top.c1.ifconfig_pool_persist, m->ifconfig_pool);
|
|
}
|
|
}
|
|
|
|
static void
|
|
multi_reap_range (const struct multi_context *m,
|
|
int start_bucket,
|
|
int end_bucket)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
struct hash_iterator hi;
|
|
struct hash_element *he;
|
|
|
|
if (start_bucket < 0)
|
|
{
|
|
start_bucket = 0;
|
|
end_bucket = hash_n_buckets (m->vhash);
|
|
}
|
|
|
|
dmsg (D_MULTI_DEBUG, "MULTI: REAP range %d -> %d", start_bucket, end_bucket);
|
|
hash_iterator_init_range (m->vhash, &hi, true, start_bucket, end_bucket);
|
|
while ((he = hash_iterator_next (&hi)) != NULL)
|
|
{
|
|
struct multi_route *r = (struct multi_route *) he->value;
|
|
if (!multi_route_defined (m, r))
|
|
{
|
|
dmsg (D_MULTI_DEBUG, "MULTI: REAP DEL %s",
|
|
mroute_addr_print (&r->addr, &gc));
|
|
learn_address_script (m, NULL, "delete", &r->addr);
|
|
multi_route_del (r);
|
|
hash_iterator_delete_element (&hi);
|
|
}
|
|
}
|
|
hash_iterator_free (&hi);
|
|
gc_free (&gc);
|
|
}
|
|
|
|
static void
|
|
multi_reap_all (const struct multi_context *m)
|
|
{
|
|
multi_reap_range (m, -1, 0);
|
|
}
|
|
|
|
static struct multi_reap *
|
|
multi_reap_new (int buckets_per_pass)
|
|
{
|
|
struct multi_reap *mr;
|
|
ALLOC_OBJ (mr, struct multi_reap);
|
|
mr->bucket_base = 0;
|
|
mr->buckets_per_pass = buckets_per_pass;
|
|
mr->last_call = now;
|
|
return mr;
|
|
}
|
|
|
|
void
|
|
multi_reap_process_dowork (const struct multi_context *m)
|
|
{
|
|
struct multi_reap *mr = m->reaper;
|
|
if (mr->bucket_base >= hash_n_buckets (m->vhash))
|
|
mr->bucket_base = 0;
|
|
multi_reap_range (m, mr->bucket_base, mr->bucket_base + mr->buckets_per_pass);
|
|
mr->bucket_base += mr->buckets_per_pass;
|
|
mr->last_call = now;
|
|
}
|
|
|
|
static void
|
|
multi_reap_free (struct multi_reap *mr)
|
|
{
|
|
free (mr);
|
|
}
|
|
|
|
/*
|
|
* How many buckets in vhash to reap per pass.
|
|
*/
|
|
static int
|
|
reap_buckets_per_pass (int n_buckets)
|
|
{
|
|
return constrain_int (n_buckets / REAP_DIVISOR, REAP_MIN, REAP_MAX);
|
|
}
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
|
|
static uint32_t
|
|
cid_hash_function (const void *key, uint32_t iv)
|
|
{
|
|
const unsigned long *k = (const unsigned long *)key;
|
|
return (uint32_t) *k;
|
|
}
|
|
|
|
static bool
|
|
cid_compare_function (const void *key1, const void *key2)
|
|
{
|
|
const unsigned long *k1 = (const unsigned long *)key1;
|
|
const unsigned long *k2 = (const unsigned long *)key2;
|
|
return *k1 == *k2;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Main initialization function, init multi_context object.
|
|
*/
|
|
void
|
|
multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int thread_mode)
|
|
{
|
|
int dev = DEV_TYPE_UNDEF;
|
|
|
|
msg (D_MULTI_LOW, "MULTI: multi_init called, r=%d v=%d",
|
|
t->options.real_hash_size,
|
|
t->options.virtual_hash_size);
|
|
|
|
/*
|
|
* Get tun/tap/null device type
|
|
*/
|
|
dev = dev_type_enum (t->options.dev, t->options.dev_type);
|
|
|
|
/*
|
|
* Init our multi_context object.
|
|
*/
|
|
CLEAR (*m);
|
|
|
|
m->thread_mode = thread_mode;
|
|
|
|
/*
|
|
* Real address hash table (source port number is
|
|
* considered to be part of the address). Used
|
|
* to determine which client sent an incoming packet
|
|
* which is seen on the TCP/UDP socket.
|
|
*/
|
|
m->hash = hash_init (t->options.real_hash_size,
|
|
get_random (),
|
|
mroute_addr_hash_function,
|
|
mroute_addr_compare_function);
|
|
|
|
/*
|
|
* Virtual address hash table. Used to determine
|
|
* which client to route a packet to.
|
|
*/
|
|
m->vhash = hash_init (t->options.virtual_hash_size,
|
|
get_random (),
|
|
mroute_addr_hash_function,
|
|
mroute_addr_compare_function);
|
|
|
|
/*
|
|
* This hash table is a clone of m->hash but with a
|
|
* bucket size of one so that it can be used
|
|
* for fast iteration through the list.
|
|
*/
|
|
m->iter = hash_init (1,
|
|
get_random (),
|
|
mroute_addr_hash_function,
|
|
mroute_addr_compare_function);
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
m->cid_hash = hash_init (t->options.real_hash_size,
|
|
0,
|
|
cid_hash_function,
|
|
cid_compare_function);
|
|
#endif
|
|
|
|
/*
|
|
* This is our scheduler, for time-based wakeup
|
|
* events.
|
|
*/
|
|
m->schedule = schedule_init ();
|
|
|
|
/*
|
|
* Limit frequency of incoming connections to control
|
|
* DoS.
|
|
*/
|
|
m->new_connection_limiter = frequency_limit_init (t->options.cf_max,
|
|
t->options.cf_per);
|
|
|
|
/*
|
|
* Allocate broadcast/multicast buffer list
|
|
*/
|
|
m->mbuf = mbuf_init (t->options.n_bcast_buf);
|
|
|
|
/*
|
|
* Different status file format options are available
|
|
*/
|
|
m->status_file_version = t->options.status_file_version;
|
|
|
|
/*
|
|
* Possibly allocate an ifconfig pool, do it
|
|
* differently based on whether a tun or tap style
|
|
* tunnel.
|
|
*/
|
|
if (t->options.ifconfig_pool_defined)
|
|
{
|
|
if (dev == DEV_TYPE_TAP)
|
|
{
|
|
m->ifconfig_pool = ifconfig_pool_init (IFCONFIG_POOL_INDIV,
|
|
t->options.ifconfig_pool_start,
|
|
t->options.ifconfig_pool_end,
|
|
t->options.duplicate_cn);
|
|
}
|
|
else if (dev == DEV_TYPE_TUN)
|
|
{
|
|
m->ifconfig_pool = ifconfig_pool_init (
|
|
(t->options.topology == TOP_NET30) ? IFCONFIG_POOL_30NET : IFCONFIG_POOL_INDIV,
|
|
t->options.ifconfig_pool_start,
|
|
t->options.ifconfig_pool_end,
|
|
t->options.duplicate_cn);
|
|
}
|
|
else
|
|
{
|
|
ASSERT (0);
|
|
}
|
|
|
|
/* reload pool data from file */
|
|
if (t->c1.ifconfig_pool_persist)
|
|
ifconfig_pool_read (t->c1.ifconfig_pool_persist, m->ifconfig_pool);
|
|
}
|
|
|
|
/*
|
|
* Help us keep track of routing table.
|
|
*/
|
|
m->route_helper = mroute_helper_init (MULTI_CACHE_ROUTE_TTL);
|
|
|
|
/*
|
|
* Initialize route and instance reaper.
|
|
*/
|
|
m->reaper = multi_reap_new (reap_buckets_per_pass (t->options.virtual_hash_size));
|
|
|
|
/*
|
|
* Get local ifconfig address
|
|
*/
|
|
CLEAR (m->local);
|
|
ASSERT (t->c1.tuntap);
|
|
mroute_extract_in_addr_t (&m->local, t->c1.tuntap->local);
|
|
|
|
/*
|
|
* Per-client limits
|
|
*/
|
|
m->max_clients = t->options.max_clients;
|
|
|
|
/*
|
|
* Initialize multi-socket TCP I/O wait object
|
|
*/
|
|
if (tcp_mode)
|
|
m->mtcp = multi_tcp_init (t->options.max_clients, &m->max_clients);
|
|
m->tcp_queue_limit = t->options.tcp_queue_limit;
|
|
|
|
/*
|
|
* Allow client <-> client communication, without going through
|
|
* tun/tap interface and network stack?
|
|
*/
|
|
m->enable_c2c = t->options.enable_c2c;
|
|
}
|
|
|
|
const char *
|
|
multi_instance_string (const struct multi_instance *mi, bool null, struct gc_arena *gc)
|
|
{
|
|
if (mi)
|
|
{
|
|
struct buffer out = alloc_buf_gc (256, gc);
|
|
const char *cn = tls_common_name (mi->context.c2.tls_multi, true);
|
|
|
|
if (cn)
|
|
buf_printf (&out, "%s/", cn);
|
|
buf_printf (&out, "%s", mroute_addr_print (&mi->real, gc));
|
|
return BSTR (&out);
|
|
}
|
|
else if (null)
|
|
return NULL;
|
|
else
|
|
return "UNDEF";
|
|
}
|
|
|
|
void
|
|
generate_prefix (struct multi_instance *mi)
|
|
{
|
|
mi->msg_prefix = multi_instance_string (mi, true, &mi->gc);
|
|
set_prefix (mi);
|
|
}
|
|
|
|
void
|
|
ungenerate_prefix (struct multi_instance *mi)
|
|
{
|
|
mi->msg_prefix = NULL;
|
|
set_prefix (mi);
|
|
}
|
|
|
|
static const char *
|
|
mi_prefix (const struct multi_instance *mi)
|
|
{
|
|
if (mi && mi->msg_prefix)
|
|
return mi->msg_prefix;
|
|
else
|
|
return "UNDEF_I";
|
|
}
|
|
|
|
/*
|
|
* Tell the route helper about deleted iroutes so
|
|
* that it can update its mask of currently used
|
|
* CIDR netlengths.
|
|
*/
|
|
static void
|
|
multi_del_iroutes (struct multi_context *m,
|
|
struct multi_instance *mi)
|
|
{
|
|
const struct iroute *ir;
|
|
if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN)
|
|
{
|
|
for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
|
|
mroute_helper_del_iroute (m->route_helper, ir);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setenv_stats (struct context *c)
|
|
{
|
|
setenv_counter (c->c2.es, "bytes_received", c->c2.link_read_bytes);
|
|
setenv_counter (c->c2.es, "bytes_sent", c->c2.link_write_bytes);
|
|
}
|
|
|
|
static void
|
|
multi_client_disconnect_setenv (struct multi_context *m,
|
|
struct multi_instance *mi)
|
|
{
|
|
/* setenv client real IP address */
|
|
setenv_trusted (mi->context.c2.es, get_link_socket_info (&mi->context));
|
|
|
|
/* setenv stats */
|
|
setenv_stats (&mi->context);
|
|
|
|
/* setenv connection duration */
|
|
{
|
|
const unsigned int duration = (unsigned int) now - mi->created;
|
|
setenv_unsigned (mi->context.c2.es, "time_duration", duration);
|
|
}
|
|
}
|
|
|
|
static void
|
|
multi_client_disconnect_script (struct multi_context *m,
|
|
struct multi_instance *mi)
|
|
{
|
|
if ((mi->context.c2.context_auth == CAS_SUCCEEDED && mi->connection_established_flag)
|
|
|| mi->context.c2.context_auth == CAS_PARTIAL)
|
|
{
|
|
multi_client_disconnect_setenv (m, mi);
|
|
|
|
if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
|
|
{
|
|
if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
|
|
msg (M_WARN, "WARNING: client-disconnect plugin call failed");
|
|
}
|
|
|
|
if (mi->context.options.client_disconnect_script)
|
|
{
|
|
struct argv argv = argv_new ();
|
|
setenv_str (mi->context.c2.es, "script_type", "client-disconnect");
|
|
argv_printf (&argv, "%sc", mi->context.options.client_disconnect_script);
|
|
openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-disconnect command failed");
|
|
argv_reset (&argv);
|
|
}
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
if (management)
|
|
management_notify_client_close (management, &mi->context.c2.mda_context, mi->context.c2.es);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
multi_close_instance (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
bool shutdown)
|
|
{
|
|
perf_push (PERF_MULTI_CLOSE_INSTANCE);
|
|
|
|
ASSERT (!mi->halt);
|
|
mi->halt = true;
|
|
|
|
dmsg (D_MULTI_DEBUG, "MULTI: multi_close_instance called");
|
|
|
|
/* adjust current client connection count */
|
|
m->n_clients += mi->n_clients_delta;
|
|
mi->n_clients_delta = 0;
|
|
|
|
/* prevent dangling pointers */
|
|
if (m->pending == mi)
|
|
multi_set_pending (m, NULL);
|
|
if (m->earliest_wakeup == mi)
|
|
m->earliest_wakeup = NULL;
|
|
|
|
if (!shutdown)
|
|
{
|
|
if (mi->did_real_hash)
|
|
{
|
|
ASSERT (hash_remove (m->hash, &mi->real));
|
|
}
|
|
if (mi->did_iter)
|
|
{
|
|
ASSERT (hash_remove (m->iter, &mi->real));
|
|
}
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
if (mi->did_cid_hash)
|
|
{
|
|
ASSERT (hash_remove (m->cid_hash, &mi->context.c2.mda_context.cid));
|
|
}
|
|
#endif
|
|
|
|
schedule_remove_entry (m->schedule, (struct schedule_entry *) mi);
|
|
|
|
ifconfig_pool_release (m->ifconfig_pool, mi->vaddr_handle, false);
|
|
|
|
if (mi->did_iroutes)
|
|
{
|
|
multi_del_iroutes (m, mi);
|
|
mi->did_iroutes = false;
|
|
}
|
|
|
|
if (m->mtcp)
|
|
multi_tcp_dereference_instance (m->mtcp, mi);
|
|
|
|
mbuf_dereference_instance (m->mbuf, mi);
|
|
}
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
set_cc_config (mi, NULL);
|
|
#endif
|
|
|
|
multi_client_disconnect_script (m, mi);
|
|
|
|
if (mi->did_open_context)
|
|
close_context (&mi->context, SIGTERM, CC_GC_FREE);
|
|
|
|
multi_tcp_instance_specific_free (mi);
|
|
|
|
ungenerate_prefix (mi);
|
|
|
|
/*
|
|
* Don't actually delete the instance memory allocation yet,
|
|
* because virtual routes may still point to it. Let the
|
|
* vhash reaper deal with it.
|
|
*/
|
|
multi_instance_dec_refcount (mi);
|
|
|
|
perf_pop ();
|
|
}
|
|
|
|
/*
|
|
* Called on shutdown or restart.
|
|
*/
|
|
void
|
|
multi_uninit (struct multi_context *m)
|
|
{
|
|
if (m->thread_mode & MC_WORK_THREAD)
|
|
{
|
|
multi_top_free (m);
|
|
m->thread_mode = MC_UNDEF;
|
|
}
|
|
else if (m->thread_mode)
|
|
{
|
|
if (m->hash)
|
|
{
|
|
struct hash_iterator hi;
|
|
struct hash_element *he;
|
|
|
|
hash_iterator_init (m->iter, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
mi->did_iter = false;
|
|
multi_close_instance (m, mi, true);
|
|
}
|
|
hash_iterator_free (&hi);
|
|
|
|
multi_reap_all (m);
|
|
|
|
hash_free (m->hash);
|
|
hash_free (m->vhash);
|
|
hash_free (m->iter);
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
hash_free (m->cid_hash);
|
|
#endif
|
|
m->hash = NULL;
|
|
|
|
schedule_free (m->schedule);
|
|
mbuf_free (m->mbuf);
|
|
ifconfig_pool_free (m->ifconfig_pool);
|
|
frequency_limit_free (m->new_connection_limiter);
|
|
multi_reap_free (m->reaper);
|
|
mroute_helper_free (m->route_helper);
|
|
multi_tcp_free (m->mtcp);
|
|
m->thread_mode = MC_UNDEF;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a client instance object for a newly connected client.
|
|
*/
|
|
struct multi_instance *
|
|
multi_create_instance (struct multi_context *m, const struct mroute_addr *real)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
struct multi_instance *mi;
|
|
|
|
perf_push (PERF_MULTI_CREATE_INSTANCE);
|
|
|
|
msg (D_MULTI_LOW, "MULTI: multi_create_instance called");
|
|
|
|
ALLOC_OBJ_CLEAR (mi, struct multi_instance);
|
|
|
|
mutex_init (&mi->mutex);
|
|
mi->gc = gc_new ();
|
|
multi_instance_inc_refcount (mi);
|
|
mi->vaddr_handle = -1;
|
|
mi->created = now;
|
|
mroute_addr_init (&mi->real);
|
|
|
|
if (real)
|
|
{
|
|
mi->real = *real;
|
|
generate_prefix (mi);
|
|
}
|
|
|
|
mi->did_open_context = true;
|
|
inherit_context_child (&mi->context, &m->top);
|
|
if (IS_SIG (&mi->context))
|
|
goto err;
|
|
|
|
mi->context.c2.context_auth = CAS_PENDING;
|
|
|
|
if (hash_n_elements (m->hash) >= m->max_clients)
|
|
{
|
|
msg (D_MULTI_ERRORS, "MULTI: new incoming connection would exceed maximum number of clients (%d)", m->max_clients);
|
|
goto err;
|
|
}
|
|
|
|
if (!real) /* TCP mode? */
|
|
{
|
|
if (!multi_tcp_instance_specific_init (m, mi))
|
|
goto err;
|
|
generate_prefix (mi);
|
|
}
|
|
|
|
if (!hash_add (m->iter, &mi->real, mi, false))
|
|
{
|
|
msg (D_MULTI_LOW, "MULTI: unable to add real address [%s] to iterator hash table",
|
|
mroute_addr_print (&mi->real, &gc));
|
|
goto err;
|
|
}
|
|
mi->did_iter = true;
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
do {
|
|
mi->context.c2.mda_context.cid = m->cid_counter++;
|
|
} while (!hash_add (m->cid_hash, &mi->context.c2.mda_context.cid, mi, false));
|
|
mi->did_cid_hash = true;
|
|
#endif
|
|
|
|
mi->context.c2.push_reply_deferred = true;
|
|
|
|
if (!multi_process_post (m, mi, MPP_PRE_SELECT))
|
|
{
|
|
msg (D_MULTI_ERRORS, "MULTI: signal occurred during client instance initialization");
|
|
goto err;
|
|
}
|
|
|
|
perf_pop ();
|
|
gc_free (&gc);
|
|
return mi;
|
|
|
|
err:
|
|
multi_close_instance (m, mi, false);
|
|
perf_pop ();
|
|
gc_free (&gc);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Dump tables -- triggered by SIGUSR2.
|
|
* If status file is defined, write to file.
|
|
* If status file is NULL, write to syslog.
|
|
*/
|
|
void
|
|
multi_print_status (struct multi_context *m, struct status_output *so, const int version)
|
|
{
|
|
if (m->hash)
|
|
{
|
|
struct gc_arena gc_top = gc_new ();
|
|
struct hash_iterator hi;
|
|
const struct hash_element *he;
|
|
|
|
status_reset (so);
|
|
|
|
if (version == 1) /* WAS: m->status_file_version */
|
|
{
|
|
/*
|
|
* Status file version 1
|
|
*/
|
|
status_printf (so, PACKAGE_NAME " CLIENT LIST");
|
|
status_printf (so, "Updated,%s", time_string (0, 0, false, &gc_top));
|
|
status_printf (so, "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since");
|
|
hash_iterator_init (m->hash, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
|
|
if (!mi->halt)
|
|
{
|
|
status_printf (so, "%s,%s," counter_format "," counter_format ",%s",
|
|
tls_common_name (mi->context.c2.tls_multi, false),
|
|
mroute_addr_print (&mi->real, &gc),
|
|
mi->context.c2.link_read_bytes,
|
|
mi->context.c2.link_write_bytes,
|
|
time_string (mi->created, 0, false, &gc));
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
hash_iterator_free (&hi);
|
|
|
|
status_printf (so, "ROUTING TABLE");
|
|
status_printf (so, "Virtual Address,Common Name,Real Address,Last Ref");
|
|
hash_iterator_init (m->vhash, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const struct multi_route *route = (struct multi_route *) he->value;
|
|
|
|
if (multi_route_defined (m, route))
|
|
{
|
|
const struct multi_instance *mi = route->instance;
|
|
const struct mroute_addr *ma = &route->addr;
|
|
char flags[2] = {0, 0};
|
|
|
|
if (route->flags & MULTI_ROUTE_CACHE)
|
|
flags[0] = 'C';
|
|
status_printf (so, "%s%s,%s,%s,%s",
|
|
mroute_addr_print (ma, &gc),
|
|
flags,
|
|
tls_common_name (mi->context.c2.tls_multi, false),
|
|
mroute_addr_print (&mi->real, &gc),
|
|
time_string (route->last_reference, 0, false, &gc));
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
hash_iterator_free (&hi);
|
|
|
|
status_printf (so, "GLOBAL STATS");
|
|
if (m->mbuf)
|
|
status_printf (so, "Max bcast/mcast queue length,%d",
|
|
mbuf_maximum_queued (m->mbuf));
|
|
|
|
status_printf (so, "END");
|
|
}
|
|
else if (version == 2 || version == 3)
|
|
{
|
|
const char sep = (version == 3) ? '\t' : ',';
|
|
|
|
/*
|
|
* Status file version 2 and 3
|
|
*/
|
|
status_printf (so, "TITLE%c%s", sep, title_string);
|
|
status_printf (so, "TIME%c%s%c%u", sep, time_string (now, 0, false, &gc_top), sep, (unsigned int)now);
|
|
status_printf (so, "HEADER%cCLIENT_LIST%cCommon Name%cReal Address%cVirtual Address%cBytes Received%cBytes Sent%cConnected Since%cConnected Since (time_t)",
|
|
sep, sep, sep, sep, sep, sep, sep, sep);
|
|
hash_iterator_init (m->hash, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
|
|
if (!mi->halt)
|
|
{
|
|
status_printf (so, "CLIENT_LIST%c%s%c%s%c%s%c" counter_format "%c" counter_format "%c%s%c%u",
|
|
sep, tls_common_name (mi->context.c2.tls_multi, false),
|
|
sep, mroute_addr_print (&mi->real, &gc),
|
|
sep, print_in_addr_t (mi->reporting_addr, IA_EMPTY_IF_UNDEF, &gc),
|
|
sep, mi->context.c2.link_read_bytes,
|
|
sep, mi->context.c2.link_write_bytes,
|
|
sep, time_string (mi->created, 0, false, &gc),
|
|
sep, (unsigned int)mi->created);
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
hash_iterator_free (&hi);
|
|
|
|
status_printf (so, "HEADER%cROUTING_TABLE%cVirtual Address%cCommon Name%cReal Address%cLast Ref%cLast Ref (time_t)",
|
|
sep, sep, sep, sep, sep, sep);
|
|
hash_iterator_init (m->vhash, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const struct multi_route *route = (struct multi_route *) he->value;
|
|
|
|
if (multi_route_defined (m, route))
|
|
{
|
|
const struct multi_instance *mi = route->instance;
|
|
const struct mroute_addr *ma = &route->addr;
|
|
char flags[2] = {0, 0};
|
|
|
|
if (route->flags & MULTI_ROUTE_CACHE)
|
|
flags[0] = 'C';
|
|
status_printf (so, "ROUTING_TABLE%c%s%s%c%s%c%s%c%s%c%u",
|
|
sep, mroute_addr_print (ma, &gc), flags,
|
|
sep, tls_common_name (mi->context.c2.tls_multi, false),
|
|
sep, mroute_addr_print (&mi->real, &gc),
|
|
sep, time_string (route->last_reference, 0, false, &gc),
|
|
sep, (unsigned int)route->last_reference);
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
hash_iterator_free (&hi);
|
|
|
|
if (m->mbuf)
|
|
status_printf (so, "GLOBAL_STATS%cMax bcast/mcast queue length%c%d",
|
|
sep, sep, mbuf_maximum_queued (m->mbuf));
|
|
|
|
status_printf (so, "END");
|
|
}
|
|
else
|
|
{
|
|
status_printf (so, "ERROR: bad status format version number");
|
|
}
|
|
|
|
#ifdef PACKET_TRUNCATION_CHECK
|
|
{
|
|
status_printf (so, "HEADER,ERRORS,Common Name,TUN Read Trunc,TUN Write Trunc,Pre-encrypt Trunc,Post-decrypt Trunc");
|
|
hash_iterator_init (m->hash, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
|
|
if (!mi->halt)
|
|
{
|
|
status_printf (so, "ERRORS,%s," counter_format "," counter_format "," counter_format "," counter_format,
|
|
tls_common_name (mi->context.c2.tls_multi, false),
|
|
m->top.c2.n_trunc_tun_read,
|
|
mi->context.c2.n_trunc_tun_write,
|
|
mi->context.c2.n_trunc_pre_encrypt,
|
|
mi->context.c2.n_trunc_post_decrypt);
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
hash_iterator_free (&hi);
|
|
}
|
|
#endif
|
|
|
|
status_flush (so);
|
|
gc_free (&gc_top);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Learn a virtual address or route.
|
|
* The learn will fail if the learn address
|
|
* script/plugin fails. In this case the
|
|
* return value may be != mi.
|
|
* Return the instance which owns this route,
|
|
* or NULL if none.
|
|
*/
|
|
static struct multi_instance *
|
|
multi_learn_addr (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
const struct mroute_addr *addr,
|
|
const unsigned int flags)
|
|
{
|
|
struct hash_element *he;
|
|
const uint32_t hv = hash_value (m->vhash, addr);
|
|
struct hash_bucket *bucket = hash_bucket (m->vhash, hv);
|
|
struct multi_route *oldroute = NULL;
|
|
struct multi_instance *owner = NULL;
|
|
|
|
hash_bucket_lock (bucket);
|
|
|
|
/* if route currently exists, get the instance which owns it */
|
|
he = hash_lookup_fast (m->vhash, bucket, addr, hv);
|
|
if (he)
|
|
oldroute = (struct multi_route *) he->value;
|
|
if (oldroute && multi_route_defined (m, oldroute))
|
|
owner = oldroute->instance;
|
|
|
|
/* do we need to add address to hash table? */
|
|
if ((!owner || owner != mi)
|
|
&& mroute_learnable_address (addr)
|
|
&& !mroute_addr_equal (addr, &m->local))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
struct multi_route *newroute;
|
|
bool learn_succeeded = false;
|
|
|
|
ALLOC_OBJ (newroute, struct multi_route);
|
|
newroute->addr = *addr;
|
|
newroute->instance = mi;
|
|
newroute->flags = flags;
|
|
newroute->last_reference = now;
|
|
newroute->cache_generation = 0;
|
|
|
|
/* The cache is invalidated when cache_generation is incremented */
|
|
if (flags & MULTI_ROUTE_CACHE)
|
|
newroute->cache_generation = m->route_helper->cache_generation;
|
|
|
|
if (oldroute) /* route already exists? */
|
|
{
|
|
if (route_quota_test (m, mi) && learn_address_script (m, mi, "update", &newroute->addr))
|
|
{
|
|
learn_succeeded = true;
|
|
owner = mi;
|
|
multi_instance_inc_refcount (mi);
|
|
route_quota_inc (mi);
|
|
|
|
/* delete old route */
|
|
multi_route_del (oldroute);
|
|
|
|
/* modify hash table entry, replacing old route */
|
|
he->key = &newroute->addr;
|
|
he->value = newroute;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (route_quota_test (m, mi) && learn_address_script (m, mi, "add", &newroute->addr))
|
|
{
|
|
learn_succeeded = true;
|
|
owner = mi;
|
|
multi_instance_inc_refcount (mi);
|
|
route_quota_inc (mi);
|
|
|
|
/* add new route */
|
|
hash_add_fast (m->vhash, bucket, &newroute->addr, hv, newroute);
|
|
}
|
|
}
|
|
|
|
msg (D_MULTI_LOW, "MULTI: Learn%s: %s -> %s",
|
|
learn_succeeded ? "" : " FAILED",
|
|
mroute_addr_print (&newroute->addr, &gc),
|
|
multi_instance_string (mi, false, &gc));
|
|
|
|
if (!learn_succeeded)
|
|
free (newroute);
|
|
|
|
gc_free (&gc);
|
|
}
|
|
|
|
hash_bucket_unlock (bucket);
|
|
return owner;
|
|
}
|
|
|
|
/*
|
|
* Get client instance based on virtual address.
|
|
*/
|
|
static struct multi_instance *
|
|
multi_get_instance_by_virtual_addr (struct multi_context *m,
|
|
const struct mroute_addr *addr,
|
|
bool cidr_routing)
|
|
{
|
|
struct multi_route *route;
|
|
struct multi_instance *ret = NULL;
|
|
|
|
/* check for local address */
|
|
if (mroute_addr_equal (addr, &m->local))
|
|
return NULL;
|
|
|
|
route = (struct multi_route *) hash_lookup (m->vhash, addr);
|
|
|
|
/* does host route (possible cached) exist? */
|
|
if (route && multi_route_defined (m, route))
|
|
{
|
|
struct multi_instance *mi = route->instance;
|
|
route->last_reference = now;
|
|
ret = mi;
|
|
}
|
|
else if (cidr_routing) /* do we need to regenerate a host route cache entry? */
|
|
{
|
|
struct mroute_helper *rh = m->route_helper;
|
|
struct mroute_addr tryaddr;
|
|
int i;
|
|
|
|
mroute_helper_lock (rh);
|
|
|
|
/* cycle through each CIDR length */
|
|
for (i = 0; i < rh->n_net_len; ++i)
|
|
{
|
|
tryaddr = *addr;
|
|
tryaddr.type |= MR_WITH_NETBITS;
|
|
tryaddr.netbits = rh->net_len[i];
|
|
mroute_addr_mask_host_bits (&tryaddr);
|
|
|
|
/* look up a possible route with netbits netmask */
|
|
route = (struct multi_route *) hash_lookup (m->vhash, &tryaddr);
|
|
|
|
if (route && multi_route_defined (m, route))
|
|
{
|
|
/* found an applicable route, cache host route */
|
|
struct multi_instance *mi = route->instance;
|
|
multi_learn_addr (m, mi, addr, MULTI_ROUTE_CACHE|MULTI_ROUTE_AGEABLE);
|
|
ret = mi;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mroute_helper_unlock (rh);
|
|
}
|
|
|
|
#ifdef ENABLE_DEBUG
|
|
if (check_debug_level (D_MULTI_DEBUG))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const char *addr_text = mroute_addr_print (addr, &gc);
|
|
if (ret)
|
|
{
|
|
dmsg (D_MULTI_DEBUG, "GET INST BY VIRT: %s -> %s via %s",
|
|
addr_text,
|
|
multi_instance_string (ret, false, &gc),
|
|
mroute_addr_print (&route->addr, &gc));
|
|
}
|
|
else
|
|
{
|
|
dmsg (D_MULTI_DEBUG, "GET INST BY VIRT: %s [failed]",
|
|
addr_text);
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
#endif
|
|
|
|
ASSERT (!(ret && ret->halt));
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Helper function to multi_learn_addr().
|
|
*/
|
|
static struct multi_instance *
|
|
multi_learn_in_addr_t (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
in_addr_t a,
|
|
int netbits, /* -1 if host route, otherwise # of network bits in address */
|
|
bool primary)
|
|
{
|
|
struct openvpn_sockaddr remote_si;
|
|
struct mroute_addr addr;
|
|
|
|
CLEAR (remote_si);
|
|
remote_si.sa.sin_family = AF_INET;
|
|
remote_si.sa.sin_addr.s_addr = htonl (a);
|
|
ASSERT (mroute_extract_openvpn_sockaddr (&addr, &remote_si, false));
|
|
|
|
if (netbits >= 0)
|
|
{
|
|
addr.type |= MR_WITH_NETBITS;
|
|
addr.netbits = (uint8_t) netbits;
|
|
}
|
|
|
|
{
|
|
struct multi_instance *owner = multi_learn_addr (m, mi, &addr, 0);
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
if (management && owner)
|
|
management_learn_addr (management, &mi->context.c2.mda_context, &addr, primary);
|
|
#endif
|
|
return owner;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A new client has connected, add routes (server -> client)
|
|
* to internal routing table.
|
|
*/
|
|
static void
|
|
multi_add_iroutes (struct multi_context *m,
|
|
struct multi_instance *mi)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
const struct iroute *ir;
|
|
if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN)
|
|
{
|
|
mi->did_iroutes = true;
|
|
for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
|
|
{
|
|
if (ir->netbits >= 0)
|
|
msg (D_MULTI_LOW, "MULTI: internal route %s/%d -> %s",
|
|
print_in_addr_t (ir->network, 0, &gc),
|
|
ir->netbits,
|
|
multi_instance_string (mi, false, &gc));
|
|
else
|
|
msg (D_MULTI_LOW, "MULTI: internal route %s -> %s",
|
|
print_in_addr_t (ir->network, 0, &gc),
|
|
multi_instance_string (mi, false, &gc));
|
|
|
|
mroute_helper_add_iroute (m->route_helper, ir);
|
|
|
|
multi_learn_in_addr_t (m, mi, ir->network, ir->netbits, false);
|
|
}
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
|
|
/*
|
|
* Given an instance (new_mi), delete all other instances which use the
|
|
* same common name.
|
|
*/
|
|
static void
|
|
multi_delete_dup (struct multi_context *m, struct multi_instance *new_mi)
|
|
{
|
|
if (new_mi)
|
|
{
|
|
const char *new_cn = tls_common_name (new_mi->context.c2.tls_multi, true);
|
|
if (new_cn)
|
|
{
|
|
struct hash_iterator hi;
|
|
struct hash_element *he;
|
|
int count = 0;
|
|
|
|
hash_iterator_init (m->iter, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
if (mi != new_mi && !mi->halt)
|
|
{
|
|
const char *cn = tls_common_name (mi->context.c2.tls_multi, true);
|
|
if (cn && !strcmp (cn, new_cn))
|
|
{
|
|
mi->did_iter = false;
|
|
multi_close_instance (m, mi, false);
|
|
hash_iterator_delete_element (&hi);
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
hash_iterator_free (&hi);
|
|
|
|
if (count)
|
|
msg (D_MULTI_LOW, "MULTI: new connection by client '%s' will cause previous active sessions by this client to be dropped. Remember to use the --duplicate-cn option if you want multiple clients using the same certificate or username to concurrently connect.", new_cn);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ensure that endpoint to be pushed to client
|
|
* complies with --ifconfig-push-constraint directive.
|
|
*/
|
|
static bool
|
|
ifconfig_push_constraint_satisfied (const struct context *c)
|
|
{
|
|
const struct options *o = &c->options;
|
|
if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined)
|
|
return (o->push_ifconfig_constraint_netmask & c->c2.push_ifconfig_local) == o->push_ifconfig_constraint_network;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 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).
|
|
*/
|
|
static void
|
|
multi_select_virtual_addr (struct multi_context *m, struct multi_instance *mi)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
|
|
/*
|
|
* If ifconfig addresses were set by dynamic config file,
|
|
* release pool addresses, otherwise keep them.
|
|
*/
|
|
if (mi->context.options.push_ifconfig_defined)
|
|
{
|
|
/* ifconfig addresses were set statically,
|
|
release dynamic allocation */
|
|
if (mi->vaddr_handle >= 0)
|
|
{
|
|
ifconfig_pool_release (m->ifconfig_pool, mi->vaddr_handle, true);
|
|
mi->vaddr_handle = -1;
|
|
}
|
|
|
|
mi->context.c2.push_ifconfig_defined = true;
|
|
mi->context.c2.push_ifconfig_local = mi->context.options.push_ifconfig_local;
|
|
mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.push_ifconfig_remote_netmask;
|
|
}
|
|
else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a pool address */
|
|
{
|
|
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_handle = ifconfig_pool_acquire (m->ifconfig_pool, &local, &remote, cn);
|
|
if (mi->vaddr_handle >= 0)
|
|
{
|
|
const int tunnel_type = TUNNEL_TYPE (mi->context.c1.tuntap);
|
|
const int tunnel_topology = TUNNEL_TOPOLOGY (mi->context.c1.tuntap);
|
|
|
|
/* set push_ifconfig_remote_netmask from pool ifconfig address(es) */
|
|
mi->context.c2.push_ifconfig_local = remote;
|
|
if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
|
|
{
|
|
mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
|
|
if (!mi->context.c2.push_ifconfig_remote_netmask)
|
|
mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
|
|
}
|
|
else if (tunnel_type == DEV_TYPE_TUN)
|
|
{
|
|
if (tunnel_topology == TOP_P2P)
|
|
mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
|
|
else if (tunnel_topology == TOP_NET30)
|
|
mi->context.c2.push_ifconfig_remote_netmask = local;
|
|
}
|
|
|
|
if (mi->context.c2.push_ifconfig_remote_netmask)
|
|
mi->context.c2.push_ifconfig_defined = true;
|
|
else
|
|
msg (D_MULTI_ERRORS, "MULTI: no --ifconfig-pool netmask parameter is available to push to %s",
|
|
multi_instance_string (mi, false, &gc));
|
|
}
|
|
else
|
|
{
|
|
msg (D_MULTI_ERRORS, "MULTI: no free --ifconfig-pool addresses are available");
|
|
}
|
|
}
|
|
gc_free (&gc);
|
|
}
|
|
|
|
/*
|
|
* Set virtual address environmental variables.
|
|
*/
|
|
static void
|
|
multi_set_virtual_addr_env (struct multi_context *m, struct multi_instance *mi)
|
|
{
|
|
setenv_del (mi->context.c2.es, "ifconfig_pool_local_ip");
|
|
setenv_del (mi->context.c2.es, "ifconfig_pool_remote_ip");
|
|
setenv_del (mi->context.c2.es, "ifconfig_pool_netmask");
|
|
|
|
if (mi->context.c2.push_ifconfig_defined)
|
|
{
|
|
const int tunnel_type = TUNNEL_TYPE (mi->context.c1.tuntap);
|
|
const int tunnel_topology = TUNNEL_TOPOLOGY (mi->context.c1.tuntap);
|
|
|
|
setenv_in_addr_t (mi->context.c2.es,
|
|
"ifconfig_pool_remote_ip",
|
|
mi->context.c2.push_ifconfig_local,
|
|
SA_SET_IF_NONZERO);
|
|
|
|
if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
|
|
{
|
|
setenv_in_addr_t (mi->context.c2.es,
|
|
"ifconfig_pool_netmask",
|
|
mi->context.c2.push_ifconfig_remote_netmask,
|
|
SA_SET_IF_NONZERO);
|
|
}
|
|
else if (tunnel_type == DEV_TYPE_TUN)
|
|
{
|
|
setenv_in_addr_t (mi->context.c2.es,
|
|
"ifconfig_pool_local_ip",
|
|
mi->context.c2.push_ifconfig_remote_netmask,
|
|
SA_SET_IF_NONZERO);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called after client-connect script is called
|
|
*/
|
|
static void
|
|
multi_client_connect_post (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
const char *dc_file,
|
|
unsigned int option_permissions_mask,
|
|
unsigned int *option_types_found)
|
|
{
|
|
/* Did script generate a dynamic config file? */
|
|
if (test_file (dc_file))
|
|
{
|
|
options_server_import (&mi->context.options,
|
|
dc_file,
|
|
D_IMPORT_ERRORS|M_OPTERR,
|
|
option_permissions_mask,
|
|
option_types_found,
|
|
mi->context.c2.es);
|
|
|
|
if (!delete_file (dc_file))
|
|
msg (D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
|
|
dc_file);
|
|
|
|
/*
|
|
* If the --client-connect script generates a config file
|
|
* with an --ifconfig-push directive, it will override any
|
|
* --ifconfig-push directive from the --client-config-dir
|
|
* directory or any --ifconfig-pool dynamic address.
|
|
*/
|
|
multi_select_virtual_addr (m, mi);
|
|
multi_set_virtual_addr_env (m, mi);
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_PLUGIN
|
|
|
|
/*
|
|
* Called after client-connect plug-in is called
|
|
*/
|
|
static void
|
|
multi_client_connect_post_plugin (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
const struct plugin_return *pr,
|
|
unsigned int option_permissions_mask,
|
|
unsigned int *option_types_found)
|
|
{
|
|
struct plugin_return config;
|
|
|
|
plugin_return_get_column (pr, &config, "config");
|
|
|
|
/* Did script generate a dynamic config file? */
|
|
if (plugin_return_defined (&config))
|
|
{
|
|
int i;
|
|
for (i = 0; i < config.n; ++i)
|
|
{
|
|
if (config.list[i] && config.list[i]->value)
|
|
options_string_import (&mi->context.options,
|
|
config.list[i]->value,
|
|
D_IMPORT_ERRORS|M_OPTERR,
|
|
option_permissions_mask,
|
|
option_types_found,
|
|
mi->context.c2.es);
|
|
}
|
|
|
|
/*
|
|
* If the --client-connect script generates a config file
|
|
* with an --ifconfig-push directive, it will override any
|
|
* --ifconfig-push directive from the --client-config-dir
|
|
* directory or any --ifconfig-pool dynamic address.
|
|
*/
|
|
multi_select_virtual_addr (m, mi);
|
|
multi_set_virtual_addr_env (m, mi);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
|
|
/*
|
|
* Called to load management-derived client-connect config
|
|
*/
|
|
static void
|
|
multi_client_connect_mda (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
const struct buffer_list *config,
|
|
unsigned int option_permissions_mask,
|
|
unsigned int *option_types_found)
|
|
{
|
|
if (config)
|
|
{
|
|
struct buffer_entry *be;
|
|
|
|
for (be = config->head; be != NULL; be = be->next)
|
|
{
|
|
const char *opt = BSTR(&be->buf);
|
|
options_string_import (&mi->context.options,
|
|
opt,
|
|
D_IMPORT_ERRORS|M_OPTERR,
|
|
option_permissions_mask,
|
|
option_types_found,
|
|
mi->context.c2.es);
|
|
}
|
|
|
|
/*
|
|
* If the --client-connect script generates a config file
|
|
* with an --ifconfig-push directive, it will override any
|
|
* --ifconfig-push directive from the --client-config-dir
|
|
* directory or any --ifconfig-pool dynamic address.
|
|
*/
|
|
multi_select_virtual_addr (m, mi);
|
|
multi_set_virtual_addr_env (m, mi);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static void
|
|
multi_client_connect_setenv (struct multi_context *m,
|
|
struct multi_instance *mi)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
|
|
/* setenv incoming cert common name for script */
|
|
setenv_str (mi->context.c2.es, "common_name", tls_common_name (mi->context.c2.tls_multi, true));
|
|
|
|
/* setenv client real IP address */
|
|
setenv_trusted (mi->context.c2.es, get_link_socket_info (&mi->context));
|
|
|
|
/* setenv client virtual IP address */
|
|
multi_set_virtual_addr_env (m, mi);
|
|
|
|
/* setenv connection time */
|
|
{
|
|
const char *created_ascii = time_string (mi->created, 0, false, &gc);
|
|
setenv_str (mi->context.c2.es, "time_ascii", created_ascii);
|
|
setenv_unsigned (mi->context.c2.es, "time_unix", (unsigned int)mi->created);
|
|
}
|
|
|
|
gc_free (&gc);
|
|
}
|
|
|
|
/*
|
|
* Called as soon as the SSL/TLS connection authenticates.
|
|
*
|
|
* Instance-specific directives to be processed:
|
|
*
|
|
* iroute start-ip end-ip
|
|
* ifconfig-push local remote-netmask
|
|
* push
|
|
*/
|
|
static void
|
|
multi_connection_established (struct multi_context *m, struct multi_instance *mi)
|
|
{
|
|
if (tls_authentication_status (mi->context.c2.tls_multi, 0) == TLS_AUTHENTICATION_SUCCEEDED)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
unsigned int option_types_found = 0;
|
|
|
|
const unsigned int option_permissions_mask =
|
|
OPT_P_INSTANCE
|
|
| OPT_P_INHERIT
|
|
| OPT_P_PUSH
|
|
| OPT_P_TIMER
|
|
| OPT_P_CONFIG
|
|
| OPT_P_ECHO
|
|
| OPT_P_COMP
|
|
| OPT_P_SOCKFLAGS;
|
|
|
|
int cc_succeeded = true; /* client connect script status */
|
|
int cc_succeeded_count = 0;
|
|
|
|
ASSERT (mi->context.c1.tuntap);
|
|
|
|
/* lock down the common name and cert hashes so they can't change during future TLS renegotiations */
|
|
tls_lock_common_name (mi->context.c2.tls_multi);
|
|
tls_lock_cert_hash_set (mi->context.c2.tls_multi);
|
|
|
|
/* generate a msg() prefix for this client instance */
|
|
generate_prefix (mi);
|
|
|
|
/* delete instances of previous clients with same common-name */
|
|
if (!mi->context.options.duplicate_cn)
|
|
multi_delete_dup (m, mi);
|
|
|
|
/* reset pool handle to null */
|
|
mi->vaddr_handle = -1;
|
|
|
|
/*
|
|
* Try to source a dynamic config file from the
|
|
* --client-config-dir directory.
|
|
*/
|
|
if (mi->context.options.client_config_dir)
|
|
{
|
|
const char *ccd_file;
|
|
|
|
ccd_file = gen_path (mi->context.options.client_config_dir,
|
|
tls_common_name (mi->context.c2.tls_multi, false),
|
|
&gc);
|
|
|
|
/* try common-name file */
|
|
if (test_file (ccd_file))
|
|
{
|
|
options_server_import (&mi->context.options,
|
|
ccd_file,
|
|
D_IMPORT_ERRORS|M_OPTERR,
|
|
option_permissions_mask,
|
|
&option_types_found,
|
|
mi->context.c2.es);
|
|
}
|
|
else /* try default file */
|
|
{
|
|
ccd_file = gen_path (mi->context.options.client_config_dir,
|
|
CCD_DEFAULT,
|
|
&gc);
|
|
|
|
if (test_file (ccd_file))
|
|
{
|
|
options_server_import (&mi->context.options,
|
|
ccd_file,
|
|
D_IMPORT_ERRORS|M_OPTERR,
|
|
option_permissions_mask,
|
|
&option_types_found,
|
|
mi->context.c2.es);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Select a virtual address from either --ifconfig-push in --client-config-dir file
|
|
* or --ifconfig-pool.
|
|
*/
|
|
multi_select_virtual_addr (m, mi);
|
|
|
|
/* do --client-connect setenvs */
|
|
multi_client_connect_setenv (m, mi);
|
|
|
|
#ifdef ENABLE_PLUGIN
|
|
/*
|
|
* Call client-connect plug-in.
|
|
*/
|
|
|
|
/* deprecated callback, use a file for passing back return info */
|
|
if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
|
|
{
|
|
struct argv argv = argv_new ();
|
|
const char *dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc);
|
|
argv_printf (&argv, "%s", dc_file);
|
|
delete_file (dc_file);
|
|
if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, &argv, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
|
|
{
|
|
msg (M_WARN, "WARNING: client-connect plugin call failed");
|
|
cc_succeeded = false;
|
|
}
|
|
else
|
|
{
|
|
multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found);
|
|
++cc_succeeded_count;
|
|
}
|
|
argv_reset (&argv);
|
|
}
|
|
|
|
/* V2 callback, use a plugin_return struct for passing back return info */
|
|
if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2))
|
|
{
|
|
struct plugin_return pr;
|
|
|
|
plugin_return_init (&pr);
|
|
|
|
if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2, NULL, &pr, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
|
|
{
|
|
msg (M_WARN, "WARNING: client-connect-v2 plugin call failed");
|
|
cc_succeeded = false;
|
|
}
|
|
else
|
|
{
|
|
multi_client_connect_post_plugin (m, mi, &pr, option_permissions_mask, &option_types_found);
|
|
++cc_succeeded_count;
|
|
}
|
|
|
|
plugin_return_free (&pr);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Run --client-connect script.
|
|
*/
|
|
if (mi->context.options.client_connect_script && cc_succeeded)
|
|
{
|
|
struct argv argv = argv_new ();
|
|
const char *dc_file = NULL;
|
|
|
|
setenv_str (mi->context.c2.es, "script_type", "client-connect");
|
|
|
|
dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc);
|
|
|
|
delete_file (dc_file);
|
|
|
|
argv_printf (&argv, "%sc %s",
|
|
mi->context.options.client_connect_script,
|
|
dc_file);
|
|
|
|
if (openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-connect command failed"))
|
|
{
|
|
multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found);
|
|
++cc_succeeded_count;
|
|
}
|
|
else
|
|
cc_succeeded = false;
|
|
|
|
argv_reset (&argv);
|
|
}
|
|
|
|
/*
|
|
* Check for client-connect script left by management interface client
|
|
*/
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
if (cc_succeeded && mi->cc_config)
|
|
{
|
|
multi_client_connect_mda (m, mi, mi->cc_config, option_permissions_mask, &option_types_found);
|
|
++cc_succeeded_count;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Check for "disable" directive in client-config-dir file
|
|
* or config file generated by --client-connect script.
|
|
*/
|
|
if (mi->context.options.disable)
|
|
{
|
|
msg (D_MULTI_ERRORS, "MULTI: client has been rejected due to 'disable' directive");
|
|
cc_succeeded = false;
|
|
}
|
|
|
|
if (cc_succeeded)
|
|
{
|
|
/*
|
|
* Process sourced options.
|
|
*/
|
|
do_deferred_options (&mi->context, option_types_found);
|
|
|
|
/*
|
|
* make sure we got ifconfig settings from somewhere
|
|
*/
|
|
if (!mi->context.c2.push_ifconfig_defined)
|
|
{
|
|
msg (D_MULTI_ERRORS, "MULTI: no dynamic or static remote --ifconfig address is available for %s",
|
|
multi_instance_string (mi, false, &gc));
|
|
}
|
|
|
|
/*
|
|
* make sure that ifconfig settings comply with constraints
|
|
*/
|
|
if (!ifconfig_push_constraint_satisfied (&mi->context))
|
|
{
|
|
/* 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)",
|
|
multi_instance_string (mi, false, &gc),
|
|
print_in_addr_t (mi->context.c2.push_ifconfig_local, 0, &gc),
|
|
print_in_addr_t (mi->context.options.push_ifconfig_constraint_network, 0, &gc),
|
|
print_in_addr_t (mi->context.options.push_ifconfig_constraint_netmask, 0, &gc));
|
|
}
|
|
|
|
/*
|
|
* For routed tunnels, set up internal route to endpoint
|
|
* plus add all iroute routes.
|
|
*/
|
|
if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN)
|
|
{
|
|
if (mi->context.c2.push_ifconfig_defined)
|
|
{
|
|
multi_learn_in_addr_t (m, mi, mi->context.c2.push_ifconfig_local, -1, true);
|
|
msg (D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s",
|
|
multi_instance_string (mi, false, &gc),
|
|
print_in_addr_t (mi->context.c2.push_ifconfig_local, 0, &gc));
|
|
}
|
|
|
|
/* add routes locally, pointing to new client, if
|
|
--iroute options have been specified */
|
|
multi_add_iroutes (m, mi);
|
|
|
|
/*
|
|
* iroutes represent subnets which are "owned" by a particular
|
|
* client. Therefore, do not actually push a route to a client
|
|
* if it matches one of the client's iroutes.
|
|
*/
|
|
remove_iroutes_from_push_route_list (&mi->context.options);
|
|
}
|
|
else if (mi->context.options.iroutes)
|
|
{
|
|
msg (D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute only works with tun-style tunnels",
|
|
multi_instance_string (mi, false, &gc));
|
|
}
|
|
|
|
/* set our client's VPN endpoint for status reporting purposes */
|
|
mi->reporting_addr = mi->context.c2.push_ifconfig_local;
|
|
|
|
/* set context-level authentication flag */
|
|
mi->context.c2.context_auth = CAS_SUCCEEDED;
|
|
}
|
|
else
|
|
{
|
|
/* set context-level authentication flag */
|
|
mi->context.c2.context_auth = cc_succeeded_count ? CAS_PARTIAL : CAS_FAILED;
|
|
}
|
|
|
|
/* set flag so we don't get called again */
|
|
mi->connection_established_flag = true;
|
|
|
|
/* increment number of current authenticated clients */
|
|
++m->n_clients;
|
|
--mi->n_clients_delta;
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
if (management)
|
|
management_connection_established (management, &mi->context.c2.mda_context, mi->context.c2.es);
|
|
#endif
|
|
|
|
gc_free (&gc);
|
|
}
|
|
|
|
/*
|
|
* Reply now to client's PUSH_REQUEST query
|
|
*/
|
|
mi->context.c2.push_reply_deferred = false;
|
|
}
|
|
|
|
/*
|
|
* Add a mbuf buffer to a particular
|
|
* instance.
|
|
*/
|
|
void
|
|
multi_add_mbuf (struct multi_context *m,
|
|
struct multi_instance *mi,
|
|
struct mbuf_buffer *mb)
|
|
{
|
|
if (multi_output_queue_ready (m, mi))
|
|
{
|
|
struct mbuf_item item;
|
|
item.buffer = mb;
|
|
item.instance = mi;
|
|
mbuf_add_item (m->mbuf, &item);
|
|
}
|
|
else
|
|
{
|
|
msg (D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_add_mbuf)");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add a packet to a client instance output queue.
|
|
*/
|
|
static inline void
|
|
multi_unicast (struct multi_context *m,
|
|
const struct buffer *buf,
|
|
struct multi_instance *mi)
|
|
{
|
|
struct mbuf_buffer *mb;
|
|
|
|
if (BLEN (buf) > 0)
|
|
{
|
|
mb = mbuf_alloc_buf (buf);
|
|
mb->flags = MF_UNICAST;
|
|
multi_add_mbuf (m, mi, mb);
|
|
mbuf_free_buf (mb);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Broadcast a packet to all clients.
|
|
*/
|
|
static void
|
|
multi_bcast (struct multi_context *m,
|
|
const struct buffer *buf,
|
|
const struct multi_instance *sender_instance,
|
|
const struct mroute_addr *sender_addr)
|
|
{
|
|
struct hash_iterator hi;
|
|
struct hash_element *he;
|
|
struct multi_instance *mi;
|
|
struct mbuf_buffer *mb;
|
|
|
|
if (BLEN (buf) > 0)
|
|
{
|
|
perf_push (PERF_MULTI_BCAST);
|
|
#ifdef MULTI_DEBUG_EVENT_LOOP
|
|
printf ("BCAST len=%d\n", BLEN (buf));
|
|
#endif
|
|
mb = mbuf_alloc_buf (buf);
|
|
hash_iterator_init (m->iter, &hi, true);
|
|
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
mi = (struct multi_instance *) he->value;
|
|
if (mi != sender_instance && !mi->halt)
|
|
{
|
|
#ifdef ENABLE_PF
|
|
if (sender_instance)
|
|
{
|
|
if (!pf_c2c_test (&sender_instance->context, &mi->context, "bcast_c2c"))
|
|
{
|
|
msg (D_PF_DROPPED_BCAST, "PF: client[%s] -> client[%s] packet dropped by BCAST packet filter",
|
|
mi_prefix (sender_instance),
|
|
mi_prefix (mi));
|
|
continue;
|
|
}
|
|
}
|
|
if (sender_addr)
|
|
{
|
|
if (!pf_addr_test (&mi->context, sender_addr, "bcast_src_addr"))
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
msg (D_PF_DROPPED_BCAST, "PF: addr[%s] -> client[%s] packet dropped by BCAST packet filter",
|
|
mroute_addr_print_ex (sender_addr, MAPF_SHOW_ARP, &gc),
|
|
mi_prefix (mi));
|
|
gc_free (&gc);
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
multi_add_mbuf (m, mi, mb);
|
|
}
|
|
}
|
|
|
|
hash_iterator_free (&hi);
|
|
mbuf_free_buf (mb);
|
|
perf_pop ();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Given a time delta, indicating that we wish to be
|
|
* awoken by the scheduler at time now + delta, figure
|
|
* a sigma parameter (in microseconds) that represents
|
|
* a sort of fuzz factor around delta, so that we're
|
|
* really telling the scheduler to wake us up any time
|
|
* between now + delta - sigma and now + delta + sigma.
|
|
*
|
|
* The sigma parameter helps the scheduler to run more efficiently.
|
|
* Sigma should be no larger than TV_WITHIN_SIGMA_MAX_USEC
|
|
*/
|
|
static inline unsigned int
|
|
compute_wakeup_sigma (const struct timeval *delta)
|
|
{
|
|
if (delta->tv_sec < 1)
|
|
{
|
|
/* if < 1 sec, fuzz = # of microseconds / 8 */
|
|
return delta->tv_usec >> 3;
|
|
}
|
|
else
|
|
{
|
|
/* if < 10 minutes, fuzz = 13.1% of timeout */
|
|
if (delta->tv_sec < 600)
|
|
return delta->tv_sec << 17;
|
|
else
|
|
return 120000000; /* if >= 10 minutes, fuzz = 2 minutes */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Figure instance-specific timers, convert
|
|
* earliest to absolute time in mi->wakeup,
|
|
* call scheduler with our future wakeup time.
|
|
*
|
|
* Also close context on signal.
|
|
*/
|
|
bool
|
|
multi_process_post (struct multi_context *m, struct multi_instance *mi, const unsigned int flags)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (!IS_SIG (&mi->context) && ((flags & MPP_PRE_SELECT) || ((flags & MPP_CONDITIONAL_PRE_SELECT) && !ANY_OUT (&mi->context))))
|
|
{
|
|
/* figure timeouts and fetch possible outgoing
|
|
to_link packets (such as ping or TLS control) */
|
|
pre_select (&mi->context);
|
|
|
|
if (!IS_SIG (&mi->context))
|
|
{
|
|
/* calculate an absolute wakeup time */
|
|
ASSERT (!openvpn_gettimeofday (&mi->wakeup, NULL));
|
|
tv_add (&mi->wakeup, &mi->context.c2.timeval);
|
|
|
|
/* tell scheduler to wake us up at some point in the future */
|
|
schedule_add_entry (m->schedule,
|
|
(struct schedule_entry *) mi,
|
|
&mi->wakeup,
|
|
compute_wakeup_sigma (&mi->context.c2.timeval));
|
|
|
|
/* connection is "established" when SSL/TLS key negotiation succeeds
|
|
and (if specified) auth user/pass succeeds */
|
|
if (!mi->connection_established_flag && CONNECTION_ESTABLISHED (&mi->context))
|
|
multi_connection_established (m, mi);
|
|
}
|
|
}
|
|
|
|
if (IS_SIG (&mi->context))
|
|
{
|
|
if (flags & MPP_CLOSE_ON_SIGNAL)
|
|
{
|
|
multi_close_instance_on_signal (m, mi);
|
|
ret = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* continue to pend on output? */
|
|
multi_set_pending (m, ANY_OUT (&mi->context) ? mi : NULL);
|
|
|
|
#ifdef MULTI_DEBUG_EVENT_LOOP
|
|
printf ("POST %s[%d] to=%d lo=%d/%d w=%d/%d\n",
|
|
id(mi),
|
|
(int) (mi == m->pending),
|
|
mi ? mi->context.c2.to_tun.len : -1,
|
|
mi ? mi->context.c2.to_link.len : -1,
|
|
(mi && mi->context.c2.fragment) ? mi->context.c2.fragment->outgoing.len : -1,
|
|
(int)mi->context.c2.timeval.tv_sec,
|
|
(int)mi->context.c2.timeval.tv_usec);
|
|
#endif
|
|
}
|
|
|
|
if ((flags & MPP_RECORD_TOUCH) && m->mpp_touched)
|
|
*m->mpp_touched = mi;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Process packets in the TCP/UDP socket -> TUN/TAP interface direction,
|
|
* i.e. client -> server direction.
|
|
*/
|
|
bool
|
|
multi_process_incoming_link (struct multi_context *m, struct multi_instance *instance, const unsigned int mpp_flags)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
|
|
struct context *c;
|
|
struct mroute_addr src, dest;
|
|
unsigned int mroute_flags;
|
|
struct multi_instance *mi;
|
|
bool ret = true;
|
|
|
|
if (m->pending)
|
|
return true;
|
|
|
|
if (!instance)
|
|
{
|
|
#ifdef MULTI_DEBUG_EVENT_LOOP
|
|
printf ("TCP/UDP -> TUN [%d]\n", BLEN (&m->top.c2.buf));
|
|
#endif
|
|
multi_set_pending (m, multi_get_create_instance_udp (m));
|
|
}
|
|
else
|
|
multi_set_pending (m, instance);
|
|
|
|
if (m->pending)
|
|
{
|
|
set_prefix (m->pending);
|
|
|
|
/* get instance context */
|
|
c = &m->pending->context;
|
|
|
|
if (!instance)
|
|
{
|
|
/* transfer packet pointer from top-level context buffer to instance */
|
|
c->c2.buf = m->top.c2.buf;
|
|
|
|
/* transfer from-addr from top-level context buffer to instance */
|
|
c->c2.from = m->top.c2.from;
|
|
}
|
|
|
|
if (BLEN (&c->c2.buf) > 0)
|
|
{
|
|
/* decrypt in instance context */
|
|
process_incoming_link (c);
|
|
|
|
if (TUNNEL_TYPE (m->top.c1.tuntap) == DEV_TYPE_TUN)
|
|
{
|
|
/* extract packet source and dest addresses */
|
|
mroute_flags = mroute_extract_addr_from_packet (&src,
|
|
&dest,
|
|
NULL,
|
|
NULL,
|
|
&c->c2.to_tun,
|
|
DEV_TYPE_TUN);
|
|
|
|
/* drop packet if extract failed */
|
|
if (!(mroute_flags & MROUTE_EXTRACT_SUCCEEDED))
|
|
{
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
/* make sure that source address is associated with this client */
|
|
else if (multi_get_instance_by_virtual_addr (m, &src, true) != m->pending)
|
|
{
|
|
msg (D_MULTI_DROPPED, "MULTI: bad source address from client [%s], packet dropped",
|
|
mroute_addr_print (&src, &gc));
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
/* client-to-client communication enabled? */
|
|
else if (m->enable_c2c)
|
|
{
|
|
/* multicast? */
|
|
if (mroute_flags & MROUTE_EXTRACT_MCAST)
|
|
{
|
|
/* for now, treat multicast as broadcast */
|
|
multi_bcast (m, &c->c2.to_tun, m->pending, NULL);
|
|
}
|
|
else /* possible client to client routing */
|
|
{
|
|
ASSERT (!(mroute_flags & MROUTE_EXTRACT_BCAST));
|
|
mi = multi_get_instance_by_virtual_addr (m, &dest, true);
|
|
|
|
/* if dest addr is a known client, route to it */
|
|
if (mi)
|
|
{
|
|
#ifdef ENABLE_PF
|
|
if (!pf_c2c_test (c, &mi->context, "tun_c2c"))
|
|
{
|
|
msg (D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TUN packet filter",
|
|
mi_prefix (mi));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
multi_unicast (m, &c->c2.to_tun, mi);
|
|
register_activity (c, BLEN(&c->c2.to_tun));
|
|
}
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
}
|
|
}
|
|
#ifdef ENABLE_PF
|
|
if (c->c2.to_tun.len && !pf_addr_test (c, &dest, "tun_dest_addr"))
|
|
{
|
|
msg (D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TUN packet filter",
|
|
mroute_addr_print_ex (&dest, MAPF_SHOW_ARP, &gc));
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
#endif
|
|
}
|
|
else if (TUNNEL_TYPE (m->top.c1.tuntap) == DEV_TYPE_TAP)
|
|
{
|
|
#ifdef ENABLE_PF
|
|
struct mroute_addr edest;
|
|
mroute_addr_reset (&edest);
|
|
#endif
|
|
/* extract packet source and dest addresses */
|
|
mroute_flags = mroute_extract_addr_from_packet (&src,
|
|
&dest,
|
|
NULL,
|
|
#ifdef ENABLE_PF
|
|
&edest,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
&c->c2.to_tun,
|
|
DEV_TYPE_TAP);
|
|
|
|
if (mroute_flags & MROUTE_EXTRACT_SUCCEEDED)
|
|
{
|
|
if (multi_learn_addr (m, m->pending, &src, 0) == m->pending)
|
|
{
|
|
/* check for broadcast */
|
|
if (m->enable_c2c)
|
|
{
|
|
if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST))
|
|
{
|
|
multi_bcast (m, &c->c2.to_tun, m->pending, NULL);
|
|
}
|
|
else /* try client-to-client routing */
|
|
{
|
|
mi = multi_get_instance_by_virtual_addr (m, &dest, false);
|
|
|
|
/* if dest addr is a known client, route to it */
|
|
if (mi)
|
|
{
|
|
#ifdef ENABLE_PF
|
|
if (!pf_c2c_test (c, &mi->context, "tap_c2c"))
|
|
{
|
|
msg (D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TAP packet filter",
|
|
mi_prefix (mi));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
multi_unicast (m, &c->c2.to_tun, mi);
|
|
register_activity (c, BLEN(&c->c2.to_tun));
|
|
}
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
}
|
|
}
|
|
#ifdef ENABLE_PF
|
|
if (c->c2.to_tun.len && !pf_addr_test (c, &edest, "tap_dest_addr"))
|
|
{
|
|
msg (D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TAP packet filter",
|
|
mroute_addr_print_ex (&edest, MAPF_SHOW_ARP, &gc));
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
msg (D_MULTI_DROPPED, "MULTI: bad source address from client [%s], packet dropped",
|
|
mroute_addr_print (&src, &gc));
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
c->c2.to_tun.len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* postprocess and set wakeup */
|
|
ret = multi_process_post (m, m->pending, mpp_flags);
|
|
|
|
clear_prefix ();
|
|
}
|
|
|
|
gc_free (&gc);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Process packets in the TUN/TAP interface -> TCP/UDP socket direction,
|
|
* i.e. server -> client direction.
|
|
*/
|
|
bool
|
|
multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flags)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
bool ret = true;
|
|
|
|
if (BLEN (&m->top.c2.buf) > 0)
|
|
{
|
|
unsigned int mroute_flags;
|
|
struct mroute_addr src, dest;
|
|
const int dev_type = TUNNEL_TYPE (m->top.c1.tuntap);
|
|
|
|
#ifdef ENABLE_PF
|
|
struct mroute_addr esrc, *e1, *e2;
|
|
if (dev_type == DEV_TYPE_TUN)
|
|
{
|
|
e1 = NULL;
|
|
e2 = &src;
|
|
}
|
|
else
|
|
{
|
|
e1 = e2 = &esrc;
|
|
mroute_addr_reset (&esrc);
|
|
}
|
|
#endif
|
|
|
|
#ifdef MULTI_DEBUG_EVENT_LOOP
|
|
printf ("TUN -> TCP/UDP [%d]\n", BLEN (&m->top.c2.buf));
|
|
#endif
|
|
|
|
if (m->pending)
|
|
return true;
|
|
|
|
/*
|
|
* Route an incoming tun/tap packet to
|
|
* the appropriate multi_instance object.
|
|
*/
|
|
|
|
mroute_flags = mroute_extract_addr_from_packet (&src,
|
|
&dest,
|
|
#ifdef ENABLE_PF
|
|
e1,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
NULL,
|
|
&m->top.c2.buf,
|
|
dev_type);
|
|
|
|
if (mroute_flags & MROUTE_EXTRACT_SUCCEEDED)
|
|
{
|
|
struct context *c;
|
|
|
|
/* broadcast or multicast dest addr? */
|
|
if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST))
|
|
{
|
|
/* for now, treat multicast as broadcast */
|
|
#ifdef ENABLE_PF
|
|
multi_bcast (m, &m->top.c2.buf, NULL, e2);
|
|
#else
|
|
multi_bcast (m, &m->top.c2.buf, NULL, NULL);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
multi_set_pending (m, multi_get_instance_by_virtual_addr (m, &dest, dev_type == DEV_TYPE_TUN));
|
|
|
|
if (m->pending)
|
|
{
|
|
/* get instance context */
|
|
c = &m->pending->context;
|
|
|
|
set_prefix (m->pending);
|
|
|
|
#ifdef ENABLE_PF
|
|
if (!pf_addr_test (c, e2, "tun_tap_src_addr"))
|
|
{
|
|
msg (D_PF_DROPPED, "PF: addr[%s] -> client packet dropped by packet filter",
|
|
mroute_addr_print_ex (&src, MAPF_SHOW_ARP, &gc));
|
|
buf_reset_len (&c->c2.buf);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (multi_output_queue_ready (m, m->pending))
|
|
{
|
|
/* transfer packet pointer from top-level context buffer to instance */
|
|
c->c2.buf = m->top.c2.buf;
|
|
}
|
|
else
|
|
{
|
|
/* drop packet */
|
|
msg (D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_process_incoming_tun)");
|
|
buf_reset_len (&c->c2.buf);
|
|
}
|
|
}
|
|
|
|
/* encrypt in instance context */
|
|
process_incoming_tun (c);
|
|
|
|
/* postprocess and set wakeup */
|
|
ret = multi_process_post (m, m->pending, mpp_flags);
|
|
|
|
clear_prefix ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
gc_free (&gc);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Process a possible client-to-client/bcast/mcast message in the
|
|
* queue.
|
|
*/
|
|
struct multi_instance *
|
|
multi_get_queue (struct mbuf_set *ms)
|
|
{
|
|
struct mbuf_item item;
|
|
|
|
if (mbuf_extract_item (ms, &item, true)) /* cleartext IP packet */
|
|
{
|
|
unsigned int pipv4_flags = PIPV4_PASSTOS;
|
|
|
|
set_prefix (item.instance);
|
|
item.instance->context.c2.buf = item.buffer->buf;
|
|
if (item.buffer->flags & MF_UNICAST) /* --mssfix doesn't make sense for broadcast or multicast */
|
|
pipv4_flags |= PIPV4_MSSFIX;
|
|
process_ipv4_header (&item.instance->context, pipv4_flags, &item.instance->context.c2.buf);
|
|
encrypt_sign (&item.instance->context, true);
|
|
mbuf_free_buf (item.buffer);
|
|
|
|
dmsg (D_MULTI_DEBUG, "MULTI: C2C/MCAST/BCAST");
|
|
|
|
clear_prefix ();
|
|
return item.instance;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when an I/O wait times out. Usually means that a particular
|
|
* client instance object needs timer-based service.
|
|
*/
|
|
bool
|
|
multi_process_timeout (struct multi_context *m, const unsigned int mpp_flags)
|
|
{
|
|
bool ret = true;
|
|
|
|
#ifdef MULTI_DEBUG_EVENT_LOOP
|
|
printf ("%s -> TIMEOUT\n", id(m->earliest_wakeup));
|
|
#endif
|
|
|
|
/* instance marked for wakeup? */
|
|
if (m->earliest_wakeup)
|
|
{
|
|
set_prefix (m->earliest_wakeup);
|
|
ret = multi_process_post (m, m->earliest_wakeup, mpp_flags);
|
|
m->earliest_wakeup = NULL;
|
|
clear_prefix ();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Drop a TUN/TAP outgoing packet..
|
|
*/
|
|
void
|
|
multi_process_drop_outgoing_tun (struct multi_context *m, const unsigned int mpp_flags)
|
|
{
|
|
struct multi_instance *mi = m->pending;
|
|
|
|
ASSERT (mi);
|
|
|
|
set_prefix (mi);
|
|
|
|
msg (D_MULTI_ERRORS, "MULTI: Outgoing TUN queue full, dropped packet len=%d",
|
|
mi->context.c2.to_tun.len);
|
|
|
|
buf_reset (&mi->context.c2.to_tun);
|
|
|
|
multi_process_post (m, mi, mpp_flags);
|
|
clear_prefix ();
|
|
}
|
|
|
|
/*
|
|
* Per-client route quota management
|
|
*/
|
|
|
|
void
|
|
route_quota_exceeded (const struct multi_context *m, const struct multi_instance *mi)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
msg (D_ROUTE_QUOTA, "MULTI ROUTE: route quota (%d) exceeded for %s (see --max-routes-per-client option)",
|
|
mi->context.options.max_routes_per_client,
|
|
multi_instance_string (mi, false, &gc));
|
|
gc_free (&gc);
|
|
}
|
|
|
|
#ifdef ENABLE_DEBUG
|
|
/*
|
|
* Flood clients with random packets
|
|
*/
|
|
static void
|
|
gremlin_flood_clients (struct multi_context *m)
|
|
{
|
|
const int level = GREMLIN_PACKET_FLOOD_LEVEL (m->top.options.gremlin);
|
|
if (level)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
struct buffer buf = alloc_buf_gc (BUF_SIZE (&m->top.c2.frame), &gc);
|
|
struct packet_flood_parms parm = get_packet_flood_parms (level);
|
|
int i;
|
|
|
|
ASSERT (buf_init (&buf, FRAME_HEADROOM (&m->top.c2.frame)));
|
|
parm.packet_size = min_int (parm.packet_size, MAX_RW_SIZE_TUN (&m->top.c2.frame));
|
|
|
|
msg (D_GREMLIN, "GREMLIN_FLOOD_CLIENTS: flooding clients with %d packets of size %d",
|
|
parm.n_packets,
|
|
parm.packet_size);
|
|
|
|
for (i = 0; i < parm.packet_size; ++i)
|
|
ASSERT (buf_write_u8 (&buf, get_random () & 0xFF));
|
|
|
|
for (i = 0; i < parm.n_packets; ++i)
|
|
multi_bcast (m, &buf, NULL, NULL);
|
|
|
|
gc_free (&gc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Process timers in the top-level context
|
|
*/
|
|
void
|
|
multi_process_per_second_timers_dowork (struct multi_context *m)
|
|
{
|
|
/* possibly reap instances/routes in vhash */
|
|
multi_reap_process (m);
|
|
|
|
/* possibly print to status log */
|
|
if (m->top.c1.status_output)
|
|
{
|
|
if (status_trigger (m->top.c1.status_output))
|
|
multi_print_status (m, m->top.c1.status_output, m->status_file_version);
|
|
}
|
|
|
|
/* possibly flush ifconfig-pool file */
|
|
multi_ifconfig_pool_persist (m, false);
|
|
|
|
#ifdef ENABLE_DEBUG
|
|
gremlin_flood_clients (m);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
multi_top_init (struct multi_context *m, const struct context *top, const bool alloc_buffers)
|
|
{
|
|
inherit_context_top (&m->top, top);
|
|
m->top.c2.buffers = NULL;
|
|
if (alloc_buffers)
|
|
m->top.c2.buffers = init_context_buffers (&top->c2.frame);
|
|
}
|
|
|
|
void
|
|
multi_top_free (struct multi_context *m)
|
|
{
|
|
close_context (&m->top, -1, CC_GC_FREE);
|
|
free_context_buffers (m->top.c2.buffers);
|
|
}
|
|
|
|
/*
|
|
* Return true if event loop should break,
|
|
* false if it should continue.
|
|
*/
|
|
bool
|
|
multi_process_signal (struct multi_context *m)
|
|
{
|
|
if (m->top.sig->signal_received == SIGUSR2)
|
|
{
|
|
struct status_output *so = status_open (NULL, 0, M_INFO, NULL, 0);
|
|
multi_print_status (m, so, m->status_file_version);
|
|
status_close (so);
|
|
m->top.sig->signal_received = 0;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Called when an instance should be closed due to the
|
|
* reception of a soft signal.
|
|
*/
|
|
void
|
|
multi_close_instance_on_signal (struct multi_context *m, struct multi_instance *mi)
|
|
{
|
|
remap_signal (&mi->context);
|
|
set_prefix (mi);
|
|
print_signal (mi->context.sig, "client-instance", D_MULTI_LOW);
|
|
clear_prefix ();
|
|
multi_close_instance (m, mi, false);
|
|
}
|
|
|
|
static void
|
|
multi_signal_instance (struct multi_context *m, struct multi_instance *mi, const int sig)
|
|
{
|
|
mi->context.sig->signal_received = sig;
|
|
multi_close_instance_on_signal (m, mi);
|
|
}
|
|
|
|
/*
|
|
* Management subsystem callbacks
|
|
*/
|
|
|
|
#ifdef ENABLE_MANAGEMENT
|
|
|
|
static void
|
|
management_callback_status (void *arg, const int version, struct status_output *so)
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
|
|
if (!version)
|
|
multi_print_status (m, so, m->status_file_version);
|
|
else
|
|
multi_print_status (m, so, version);
|
|
}
|
|
|
|
static int
|
|
management_callback_n_clients (void *arg)
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
return m->n_clients;
|
|
}
|
|
|
|
static int
|
|
management_callback_kill_by_cn (void *arg, const char *del_cn)
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
struct hash_iterator hi;
|
|
struct hash_element *he;
|
|
int count = 0;
|
|
|
|
hash_iterator_init (m->iter, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
if (!mi->halt)
|
|
{
|
|
const char *cn = tls_common_name (mi->context.c2.tls_multi, false);
|
|
if (cn && !strcmp (cn, del_cn))
|
|
{
|
|
multi_signal_instance (m, mi, SIGTERM);
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
hash_iterator_free (&hi);
|
|
return count;
|
|
}
|
|
|
|
static int
|
|
management_callback_kill_by_addr (void *arg, const in_addr_t addr, const int port)
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
struct hash_iterator hi;
|
|
struct hash_element *he;
|
|
struct openvpn_sockaddr saddr;
|
|
struct mroute_addr maddr;
|
|
int count = 0;
|
|
|
|
CLEAR (saddr);
|
|
saddr.sa.sin_family = AF_INET;
|
|
saddr.sa.sin_addr.s_addr = htonl (addr);
|
|
saddr.sa.sin_port = htons (port);
|
|
if (mroute_extract_openvpn_sockaddr (&maddr, &saddr, true))
|
|
{
|
|
hash_iterator_init (m->iter, &hi, true);
|
|
while ((he = hash_iterator_next (&hi)))
|
|
{
|
|
struct multi_instance *mi = (struct multi_instance *) he->value;
|
|
if (!mi->halt && mroute_addr_equal (&maddr, &mi->real))
|
|
{
|
|
multi_signal_instance (m, mi, SIGTERM);
|
|
++count;
|
|
}
|
|
}
|
|
hash_iterator_free (&hi);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void
|
|
management_delete_event (void *arg, event_t event)
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
if (m->mtcp)
|
|
multi_tcp_delete_event (m->mtcp, event);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
|
|
static struct multi_instance *
|
|
lookup_by_cid (struct multi_context *m, const unsigned long cid)
|
|
{
|
|
if (m)
|
|
{
|
|
struct multi_instance *mi = (struct multi_instance *) hash_lookup (m->cid_hash, &cid);
|
|
if (mi && !mi->halt)
|
|
return mi;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
management_kill_by_cid (void *arg, const unsigned long cid)
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
struct multi_instance *mi = lookup_by_cid (m, cid);
|
|
if (mi)
|
|
{
|
|
send_restart (&mi->context); /* was: multi_signal_instance (m, mi, SIGTERM); */
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
management_client_auth (void *arg,
|
|
const unsigned long cid,
|
|
const unsigned int mda_key_id,
|
|
const bool auth,
|
|
const char *reason,
|
|
const char *client_reason,
|
|
struct buffer_list *cc_config) /* ownership transferred */
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
struct multi_instance *mi = lookup_by_cid (m, cid);
|
|
bool cc_config_owned = true;
|
|
bool ret = false;
|
|
|
|
if (mi)
|
|
{
|
|
ret = tls_authenticate_key (mi->context.c2.tls_multi, mda_key_id, auth, client_reason);
|
|
if (ret)
|
|
{
|
|
if (auth && !mi->connection_established_flag)
|
|
{
|
|
set_cc_config (mi, cc_config);
|
|
cc_config_owned = false;
|
|
}
|
|
if (!auth && reason)
|
|
msg (D_MULTI_LOW, "MULTI: connection rejected: %s, CLI:%s", reason, np(client_reason));
|
|
}
|
|
}
|
|
if (cc_config_owned && cc_config)
|
|
buffer_list_free (cc_config);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MANAGEMENT_PF
|
|
static bool
|
|
management_client_pf (void *arg,
|
|
const unsigned long cid,
|
|
struct buffer_list *pf_config) /* ownership transferred */
|
|
{
|
|
struct multi_context *m = (struct multi_context *) arg;
|
|
struct multi_instance *mi = lookup_by_cid (m, cid);
|
|
bool ret = false;
|
|
|
|
if (mi && pf_config)
|
|
ret = pf_load_from_buffer_list (&mi->context, pf_config);
|
|
|
|
if (pf_config)
|
|
buffer_list_free (pf_config);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
init_management_callback_multi (struct multi_context *m)
|
|
{
|
|
#ifdef ENABLE_MANAGEMENT
|
|
if (management)
|
|
{
|
|
struct management_callback cb;
|
|
CLEAR (cb);
|
|
cb.arg = m;
|
|
cb.flags = MCF_SERVER;
|
|
cb.status = management_callback_status;
|
|
cb.show_net = management_show_net_callback;
|
|
cb.kill_by_cn = management_callback_kill_by_cn;
|
|
cb.kill_by_addr = management_callback_kill_by_addr;
|
|
cb.delete_event = management_delete_event;
|
|
cb.n_clients = management_callback_n_clients;
|
|
#ifdef MANAGEMENT_DEF_AUTH
|
|
cb.kill_by_cid = management_kill_by_cid;
|
|
cb.client_auth = management_client_auth;
|
|
#endif
|
|
#ifdef MANAGEMENT_PF
|
|
cb.client_pf = management_client_pf;
|
|
#endif
|
|
management_set_callback (management, &cb);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
uninit_management_callback_multi (struct multi_context *m)
|
|
{
|
|
uninit_management_callback ();
|
|
}
|
|
|
|
/*
|
|
* Top level event loop.
|
|
*/
|
|
void
|
|
tunnel_server (struct context *top)
|
|
{
|
|
ASSERT (top->options.mode == MODE_SERVER);
|
|
|
|
switch (top->options.ce.proto) {
|
|
case PROTO_UDPv4:
|
|
tunnel_server_udp (top);
|
|
break;
|
|
case PROTO_TCPv4_SERVER:
|
|
tunnel_server_tcp (top);
|
|
break;
|
|
default:
|
|
ASSERT (0);
|
|
}
|
|
}
|
|
|
|
#else
|
|
static void dummy(void) {}
|
|
#endif /* P2MP_SERVER */
|