diff options
Diffstat (limited to 'src/openvpn/multi.c')
-rw-r--r-- | src/openvpn/multi.c | 2867 |
1 files changed, 2867 insertions, 0 deletions
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c new file mode 100644 index 0000000..9876b80 --- /dev/null +++ b/src/openvpn/multi.c @@ -0,0 +1,2867 @@ +/* + * 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. <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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#if P2MP_SERVER + +#include "multi.h" +#include "push.h" +#include "misc.h" +#include "otime.h" +#include "gremlin.h" +#include "mstats.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 inline void +update_mstat_n_clients(const int n_clients) +{ +#ifdef ENABLE_MEMSTATS + if (mmap_stats) + mmap_stats->n_clients = n_clients; +#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_run_script (&argv, es, 0, "--learn-address")) + 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, 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) + { + int pool_type = IFCONFIG_POOL_INDIV; + + if ( dev == DEV_TYPE_TUN && t->options.topology == TOP_NET30 ) + pool_type = IFCONFIG_POOL_30NET; + + m->ifconfig_pool = ifconfig_pool_init (pool_type, + t->options.ifconfig_pool_start, + t->options.ifconfig_pool_end, + t->options.duplicate_cn, + t->options.ifconfig_ipv6_pool_defined, + t->options.ifconfig_ipv6_pool_base, + t->options.ifconfig_ipv6_pool_netbits ); + + /* reload pool data from file */ + if (t->c1.ifconfig_pool_persist) + 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; + + /* initialize stale routes check timer */ + if (t->options.stale_routes_check_interval > 0) + { + msg (M_INFO, "Initializing stale route check timer to run every %i seconds and to removing routes with activity timeout older than %i seconds", + t->options.stale_routes_check_interval, t->options.stale_routes_ageing_time); + event_timeout_init (&m->stale_routes_check_et, t->options.stale_routes_check_interval, 0); + } +} + +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; + const struct iroute_ipv6 *ir6; + 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); + + for ( ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next ) + mroute_helper_del_iroute6 (m->route_helper, ir6); + } +} + +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_run_script (&argv, mi->context.c2.es, 0, "--client-disconnect"); + 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; + update_mstat_n_clients(m->n_clients); + 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); + 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_MEDIUM, "MULTI: multi_create_instance called"); + + ALLOC_OBJ_CLEAR (mi, struct multi_instance); + + 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, "OpenVPN 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); + 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); + 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)%cUsername", + sep, sep, sep, sep, sep, sep, sep, sep, sep); + hash_iterator_init (m->hash, &hi); + 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%c%s", + 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, + sep, tls_username (mi->context.c2.tls_multi, false)); + } + 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); + 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); + 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; + + /* 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); + } + + 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; + + /* 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; + } + } + } + +#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.addr.in4.sin_family = AF_INET; + remote_si.addr.in4.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; + } +} + +static struct multi_instance * +multi_learn_in6_addr (struct multi_context *m, + struct multi_instance *mi, + struct in6_addr a6, + int netbits, /* -1 if host route, otherwise # of network bits in address */ + bool primary) +{ + struct mroute_addr addr; + + addr.len = 16; + addr.type = MR_ADDR_IPV6; + addr.netbits = 0; + memcpy( &addr.addr, &a6, sizeof(a6) ); + + if (netbits >= 0) + { + addr.type |= MR_WITH_NETBITS; + addr.netbits = (uint8_t) netbits; + mroute_addr_mask_host_bits( &addr ); + } + + { + 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; + const struct iroute_ipv6 *ir6; + 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); + } + for ( ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next ) + { + if (ir6->netbits >= 0) + msg (D_MULTI_LOW, "MULTI: internal route %s/%d -> %s", + print_in6_addr (ir6->network, 0, &gc), + ir6->netbits, + multi_instance_string (mi, false, &gc)); + else + msg (D_MULTI_LOW, "MULTI: internal route %s -> %s", + print_in6_addr (ir6->network, 0, &gc), + multi_instance_string (mi, false, &gc)); + + mroute_helper_add_iroute6 (m->route_helper, ir6); + + multi_learn_in6_addr (m, mi, ir6->network, ir6->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); + 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); + } + } +} + +static void +check_stale_routes (struct multi_context *m) +{ + + struct gc_arena gc = gc_new (); + struct hash_iterator hi; + struct hash_element *he; + + dmsg (D_MULTI_DEBUG, "MULTI: Checking stale routes"); + hash_iterator_init_range (m->vhash, &hi, 0, hash_n_buckets (m->vhash)); + while ((he = hash_iterator_next (&hi)) != NULL) + { + struct multi_route *r = (struct multi_route *) he->value; + if (multi_route_defined (m, r) && difftime(now, r->last_reference) >= m->top.options.stale_routes_ageing_time) + { + dmsg (D_MULTI_DEBUG, "MULTI: Deleting stale route for address '%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); +} + +/* + * 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; +#ifdef ENABLE_CLIENT_NAT + mi->context.c2.push_ifconfig_local_alias = mi->context.options.push_ifconfig_local_alias; +#endif + + /* the current implementation does not allow "static IPv4, pool IPv6", + * (see below) so issue a warning if that happens - don't break the + * session, though, as we don't even know if this client WANTS IPv6 + */ + if ( mi->context.c1.tuntap->ipv6 && + mi->context.options.ifconfig_ipv6_pool_defined && + ! mi->context.options.push_ifconfig_ipv6_defined ) + { + msg( M_INFO, "MULTI_sva: WARNING: if --ifconfig-push is used for IPv4, automatic IPv6 assignment from --ifconfig-ipv6-pool does not work. Use --ifconfig-ipv6-push for IPv6 then." ); + } + } + else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a pool address */ + { + in_addr_t local=0, remote=0; + struct in6_addr remote_ipv6; + const char *cn = NULL; + + if (!mi->context.options.duplicate_cn) + cn = tls_common_name (mi->context.c2.tls_multi, true); + + CLEAR(remote_ipv6); + mi->vaddr_handle = ifconfig_pool_acquire (m->ifconfig_pool, &local, &remote, &remote_ipv6, 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); + + msg( M_INFO, "MULTI_sva: pool returned IPv4=%s, IPv6=%s", + print_in_addr_t( remote, 0, &gc ), + (mi->context.options.ifconfig_ipv6_pool_defined + ? print_in6_addr( remote_ipv6, 0, &gc ) + : "(Not enabled)") ); + + /* 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)); + + if ( mi->context.options.ifconfig_ipv6_pool_defined ) + { + mi->context.c2.push_ifconfig_ipv6_local = remote_ipv6; + mi->context.c2.push_ifconfig_ipv6_remote = + mi->context.c1.tuntap->local_ipv6; + mi->context.c2.push_ifconfig_ipv6_netbits = + mi->context.options.ifconfig_ipv6_pool_netbits; + mi->context.c2.push_ifconfig_ipv6_defined = true; + } + } + else + { + msg (D_MULTI_ERRORS, "MULTI: no free --ifconfig-pool addresses are available"); + } + } + + /* IPv6 push_ifconfig is a bit problematic - since IPv6 shares the + * pool handling with IPv4, the combination "static IPv4, dynamic IPv6" + * will fail (because no pool will be allocated in this case). + * OTOH, this doesn't make too much sense in reality - and the other + * way round ("dynamic IPv4, static IPv6") or "both static" makes sense + * -> and so it's implemented right now + */ + if ( mi->context.c1.tuntap->ipv6 && + mi->context.options.push_ifconfig_ipv6_defined ) + { + mi->context.c2.push_ifconfig_ipv6_local = + mi->context.options.push_ifconfig_ipv6_local; + mi->context.c2.push_ifconfig_ipv6_remote = + mi->context.options.push_ifconfig_ipv6_remote; + mi->context.c2.push_ifconfig_ipv6_netbits = + mi->context.options.push_ifconfig_ipv6_netbits; + mi->context.c2.push_ifconfig_ipv6_defined = true; + + msg( M_INFO, "MULTI_sva: push_ifconfig_ipv6 %s/%d", + print_in6_addr( mi->context.c2.push_ifconfig_ipv6_local, 0, &gc ), + mi->context.c2.push_ifconfig_ipv6_netbits ); + } + + 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); + } + } + + /* TODO: I'm not exactly sure what these environment variables are + * used for, but if we have them for IPv4, we should also have + * them for IPv6, no? + */ +} + +/* + * 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 (!platform_unlink (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_file (mi->context.options.tmp_dir, "cc", &gc); + + if( !dc_file ) { + cc_succeeded = false; + goto script_depr_failed; + } + + argv_printf (&argv, "%s", 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; + } + script_depr_failed: + 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_file (mi->context.options.tmp_dir, "cc", &gc); + if( !dc_file ) { + cc_succeeded = false; + goto script_failed; + } + + argv_printf (&argv, "%sc %s", + mi->context.options.client_connect_script, + dc_file); + + if (openvpn_run_script (&argv, mi->context.c2.es, 0, "--client-connect")) + { + multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found); + ++cc_succeeded_count; + } + else + cc_succeeded = false; + script_failed: + 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)); + } + + if (mi->context.c2.push_ifconfig_ipv6_defined) + { + multi_learn_in6_addr (m, mi, mi->context.c2.push_ifconfig_ipv6_local, -1, true); + /* TODO: find out where addresses are "unlearned"!! */ + msg (D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s", + multi_instance_string (mi, false, &gc), + print_in6_addr (mi->context.c2.push_ifconfig_ipv6_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; + update_mstat_n_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); + + 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 */ + } +} + +static void +multi_schedule_context_wakeup (struct multi_context *m, struct multi_instance *mi) +{ + /* 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)); +} + +/* + * 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)) + { + /* tell scheduler to wake us up at some point in the future */ + multi_schedule_context_wakeup(m, mi); + + /* 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)) /* 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 + +bool +stale_route_check_trigger (struct multi_context *m) +{ + struct timeval null; + CLEAR (null); + return event_timeout_trigger (&m->stale_routes_check_et, &null, ETT_DEFAULT); +} + +/* + * 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 + + /* Should we check for stale routes? */ + if (m->top.options.stale_routes_check_interval && stale_route_check_trigger (m)) + check_stale_routes (m); +} + +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); + 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.addr.in4.sin_family = AF_INET; + saddr.addr.in4.sin_addr.s_addr = htonl (addr); + saddr.addr.in4.sin_port = htons (port); + if (mroute_extract_openvpn_sockaddr (&maddr, &saddr, true)) + { + hash_iterator_init (m->iter, &hi); + 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, const char *kill_msg) +{ + struct multi_context *m = (struct multi_context *) arg; + struct multi_instance *mi = lookup_by_cid (m, cid); + if (mi) + { + send_restart (&mi->context, kill_msg); /* was: multi_signal_instance (m, mi, SIGTERM); */ + multi_schedule_context_wakeup(m, mi); + 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) + { + if (!mi->connection_established_flag) + { + set_cc_config (mi, cc_config); + cc_config_owned = false; + } + } + else + { + if (reason) + msg (D_MULTI_LOW, "MULTI: connection rejected: %s, CLI:%s", reason, np(client_reason)); + if (mi->connection_established_flag) + { + send_auth_failed (&mi->context, client_reason); /* mid-session reauth failed */ + multi_schedule_context_wakeup(m, mi); + } + } + } + } + if (cc_config_owned && cc_config) + buffer_list_free (cc_config); + return ret; +} + +static char * +management_get_peer_info (void *arg, const unsigned long cid) +{ + struct multi_context *m = (struct multi_context *) arg; + struct multi_instance *mi = lookup_by_cid (m, cid); + char *ret = NULL; + + if (mi) + ret = tls_get_peer_info (mi->context.c2.tls_multi); + + 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; + cb.get_peer_info = management_get_peer_info; +#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); + + if (proto_is_dgram(top->options.ce.proto)) + tunnel_server_udp(top); + else + tunnel_server_tcp(top); +} + +#else +static void dummy(void) {} +#endif /* P2MP_SERVER */ |