summaryrefslogtreecommitdiff
path: root/src/openvpn/multi.c
diff options
context:
space:
mode:
authorOthmar Gsenger <otti@anytun.org>2008-04-12 11:38:42 +0000
committerOthmar Gsenger <otti@anytun.org>2008-04-12 11:38:42 +0000
commitfffd213c8cba2135afda493d797c41c10354770e (patch)
treebb5eea1b12871d8c3fed0e687d83be3e504d11b2 /src/openvpn/multi.c
parentsvn cleanup (diff)
big svn cleanup
Diffstat (limited to 'src/openvpn/multi.c')
-rw-r--r--src/openvpn/multi.c2137
1 files changed, 2137 insertions, 0 deletions
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
new file mode 100644
index 0000000..22ea677
--- /dev/null
+++ b/src/openvpn/multi.c
@@ -0,0 +1,2137 @@
+/*
+ * 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-2005 OpenVPN Solutions LLC <info@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 WIN32
+#include "config-win32.h"
+#else
+#include "config.h"
+#endif
+
+#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"
+
+/*#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
+
+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;
+
+ /* get environmental variable source */
+ if (mi && mi->context.c2.es)
+ es = mi->context.c2.es;
+ else
+ es = env_set_create (&gc);
+
+ if (plugin_defined (m->top.c1.plugins, OPENVPN_PLUGIN_LEARN_ADDRESS))
+ {
+ struct buffer cmd = alloc_buf_gc (256, &gc);
+
+ buf_printf (&cmd, "\"%s\" \"%s\"",
+ op,
+ mroute_addr_print (addr, &gc));
+ if (mi)
+ buf_printf (&cmd, " \"%s\"", tls_common_name (mi->context.c2.tls_multi, false));
+
+ if (plugin_call (m->top.c1.plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, BSTR (&cmd), es))
+ {
+ msg (M_WARN, "WARNING: learn-address plugin call failed");
+ ret = false;
+ }
+ }
+
+ if (m->top.options.learn_address_script)
+ {
+ struct buffer cmd = alloc_buf_gc (256, &gc);
+
+ setenv_str (es, "script_type", "learn-address");
+
+ buf_printf (&cmd, "%s \"%s\" \"%s\"",
+ m->top.options.learn_address_script,
+ op,
+ mroute_addr_print (addr, &gc));
+ if (mi)
+ buf_printf (&cmd, " \"%s\"", tls_common_name (mi->context.c2.tls_multi, false));
+
+ if (!system_check (BSTR (&cmd), es, S_SCRIPT, "WARNING: learn-address command failed"))
+ ret = false;
+ }
+
+ 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);
+}
+
+/*
+ * 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,
+ 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,
+ 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,
+ mroute_addr_hash_function,
+ mroute_addr_compare_function);
+
+ /*
+ * 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 || t->options.ifconfig_pool_linear)
+ {
+ 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 (IFCONFIG_POOL_30NET,
+ 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);
+}
+
+/*
+ * 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
+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_counter (mi->context.c2.es, "bytes_received", mi->context.c2.link_read_bytes);
+ setenv_counter (mi->context.c2.es, "bytes_sent", mi->context.c2.link_write_bytes);
+
+}
+
+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 (m->top.c1.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
+ {
+ if (plugin_call (m->top.c1.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, mi->context.c2.es))
+ msg (M_WARN, "WARNING: client-disconnect plugin call failed");
+ }
+
+ if (mi->context.options.client_disconnect_script)
+ {
+ struct gc_arena gc = gc_new ();
+ struct buffer cmd = alloc_buf_gc (256, &gc);
+
+ setenv_str (mi->context.c2.es, "script_type", "client-disconnect");
+
+ buf_printf (&cmd, "%s", mi->context.options.client_disconnect_script);
+
+ system_check (BSTR (&cmd), mi->context.c2.es, S_SCRIPT, "client-disconnect command failed");
+
+ gc_free (&gc);
+ }
+ }
+}
+
+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");
+
+ /* 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));
+ }
+
+ 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);
+ }
+
+ 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);
+ 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;
+
+ 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)
+ {
+ /*
+ * Status file version 2
+ */
+ status_printf (so, "TITLE,%s", title_string);
+ status_printf (so, "TIME,%s,%u", time_string (now, 0, false, &gc_top), (unsigned int)now);
+ status_printf (so, "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t)");
+ 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,%s,%s,%s," counter_format "," counter_format ",%s,%u",
+ tls_common_name (mi->context.c2.tls_multi, false),
+ mroute_addr_print (&mi->real, &gc),
+ print_in_addr_t (mi->reporting_addr, IA_EMPTY_IF_UNDEF, &gc),
+ mi->context.c2.link_read_bytes,
+ mi->context.c2.link_write_bytes,
+ time_string (mi->created, 0, false, &gc),
+ (unsigned int)mi->created);
+ }
+ gc_free (&gc);
+ }
+ hash_iterator_free (&hi);
+
+ status_printf (so, "HEADER,ROUTING_TABLE,Virtual Address,Common Name,Real Address,Last Ref,Last Ref (time_t)");
+ 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,%s%s,%s,%s,%s,%u",
+ 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),
+ (unsigned int)route->last_reference);
+ }
+ gc_free (&gc);
+ }
+ hash_iterator_free (&hi);
+
+ if (m->mbuf)
+ status_printf (so, "GLOBAL_STATS,Max bcast/mcast queue length,%d",
+ mbuf_maximum_queued (m->mbuf));
+
+ status_printf (so, "END");
+ }
+ else
+ {
+ status_printf (so, "ERROR: bad status format version number");
+ }
+ 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 */
+{
+ struct sockaddr_in remote_si;
+ struct mroute_addr addr;
+
+ CLEAR (remote_si);
+ remote_si.sin_family = AF_INET;
+ remote_si.sin_addr.s_addr = htonl (a);
+ ASSERT (mroute_extract_sockaddr_in (&addr, &remote_si, false));
+
+ if (netbits >= 0)
+ {
+ addr.type |= MR_WITH_NETBITS;
+ addr.netbits = (uint8_t) netbits;
+ }
+ return multi_learn_addr (m, mi, &addr, 0);
+}
+
+/*
+ * 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);
+ }
+ }
+ 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);
+ }
+ }
+}
+
+/*
+ * 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)
+ {
+ /* use pool ifconfig address(es) */
+ mi->context.c2.push_ifconfig_local = remote;
+ if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN)
+ {
+ if (mi->context.options.ifconfig_pool_linear)
+ mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
+ else
+ mi->context.c2.push_ifconfig_remote_netmask = local;
+ mi->context.c2.push_ifconfig_defined = true;
+ }
+ else if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TAP)
+ {
+ 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;
+ 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)
+ {
+ 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 (mi->context.c1.tuntap) == 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);
+ }
+ else if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TAP)
+ {
+ setenv_in_addr_t (mi->context.c2.es,
+ "ifconfig_pool_netmask",
+ mi->context.c2.push_ifconfig_remote_netmask,
+ SA_SET_IF_NONZERO);
+ }
+ }
+}
+
+/*
+ * Called after client-connect script or plug-in 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);
+ }
+}
+
+/*
+ * 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_authenticated (mi->context.c2.tls_multi))
+ {
+ 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;
+ int cc_succeeded = true; /* client connect script status */
+ int cc_succeeded_count = 0;
+
+ ASSERT (mi->context.c1.tuntap);
+
+ /* lock down the common name so it can't change during future TLS renegotiations */
+ tls_lock_common_name (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);
+
+ /* 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);
+
+ /*
+ * Call client-connect plug-in.
+ */
+ if (plugin_defined (m->top.c1.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
+ {
+ const char *dc_file = create_temp_filename (mi->context.options.tmp_dir, &gc);
+
+ delete_file (dc_file);
+
+ if (plugin_call (m->top.c1.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, dc_file, mi->context.c2.es))
+ {
+ 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;
+ }
+ }
+
+ /*
+ * Run --client-connect script.
+ */
+ if (mi->context.options.client_connect_script && cc_succeeded)
+ {
+ struct buffer cmd = alloc_buf_gc (256, &gc);
+ 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, &gc);
+
+ delete_file (dc_file);
+
+ buf_printf (&cmd, "%s %s",
+ mi->context.options.client_connect_script,
+ dc_file);
+
+ if (system_check (BSTR (&cmd), 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;
+ }
+
+ /*
+ * 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));
+ }
+
+ /*
+ * 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);
+ 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;
+
+ 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.
+ */
+void
+multi_bcast (struct multi_context *m,
+ const struct buffer *buf,
+ struct multi_instance *omit)
+{
+ 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 != omit && !mi->halt)
+ 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 (!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,
+ &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);
+ }
+ 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)
+ {
+ multi_unicast (m, &c->c2.to_tun, mi);
+ register_activity (c);
+ c->c2.to_tun.len = 0;
+ }
+ }
+ }
+ }
+ else if (TUNNEL_TYPE (m->top.c1.tuntap) == DEV_TYPE_TAP)
+ {
+ /* extract packet source and dest addresses */
+ mroute_flags = mroute_extract_addr_from_packet (&src,
+ &dest,
+ &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);
+ }
+ 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)
+ {
+ multi_unicast (m, &c->c2.to_tun, mi);
+ register_activity (c);
+ c->c2.to_tun.len = 0;
+ }
+ }
+ }
+ }
+ 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 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,
+ &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 */
+ multi_bcast (m, &m->top.c2.buf, NULL);
+ }
+ 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);
+
+ 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_clear (&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);
+
+ 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_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 sockaddr_in saddr;
+ struct mroute_addr maddr;
+ int count = 0;
+
+ CLEAR (saddr);
+ saddr.sin_family = AF_INET;
+ saddr.sin_addr.s_addr = htonl (addr);
+ saddr.sin_port = htons (port);
+ if (mroute_extract_sockaddr_in (&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
+
+void
+init_management_callback_multi (struct multi_context *m)
+{
+#ifdef ENABLE_MANAGEMENT
+ if (management)
+ {
+ struct management_callback cb;
+ CLEAR (cb);
+ cb.arg = m;
+ 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;
+ 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.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 */