/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "syshead.h" #if P2MP_SERVER #include "multi.h" #include "push.h" #include "misc.h" #include "otime.h" #include "gremlin.h" #include "memdbg.h" #include "forward-inline.h" #include "pf-inline.h" /*#define MULTI_DEBUG_EVENT_LOOP*/ #ifdef MULTI_DEBUG_EVENT_LOOP static const char * id (struct multi_instance *mi) { if (mi) return tls_common_name (mi->context.c2.tls_multi, false); else return "NULL"; } #endif #ifdef MANAGEMENT_DEF_AUTH static void set_cc_config (struct multi_instance *mi, struct buffer_list *cc_config) { if (mi->cc_config) buffer_list_free (mi->cc_config); mi->cc_config = cc_config; } #endif static bool learn_address_script (const struct multi_context *m, const struct multi_instance *mi, const char *op, const struct mroute_addr *addr) { struct gc_arena gc = gc_new (); struct env_set *es; bool ret = true; struct plugin_list *plugins; /* get environmental variable source */ if (mi && mi->context.c2.es) es = mi->context.c2.es; else es = env_set_create (&gc); /* get plugin source */ if (mi) plugins = mi->context.plugins; else plugins = m->top.plugins; if (plugin_defined (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS)) { struct argv argv = argv_new (); argv_printf (&argv, "%s %s", op, mroute_addr_print (addr, &gc)); if (mi) argv_printf_cat (&argv, "%s", tls_common_name (mi->context.c2.tls_multi, false)); if (plugin_call (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) { msg (M_WARN, "WARNING: learn-address plugin call failed"); ret = false; } argv_reset (&argv); } if (m->top.options.learn_address_script) { struct argv argv = argv_new (); setenv_str (es, "script_type", "learn-address"); argv_printf (&argv, "%sc %s %s", m->top.options.learn_address_script, op, mroute_addr_print (addr, &gc)); if (mi) argv_printf_cat (&argv, "%s", tls_common_name (mi->context.c2.tls_multi, false)); if (!openvpn_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) { if (dev == DEV_TYPE_TAP) { m->ifconfig_pool = ifconfig_pool_init (IFCONFIG_POOL_INDIV, t->options.ifconfig_pool_start, t->options.ifconfig_pool_end, t->options.duplicate_cn); } else if (dev == DEV_TYPE_TUN) { m->ifconfig_pool = ifconfig_pool_init ( (t->options.topology == TOP_NET30) ? IFCONFIG_POOL_30NET : IFCONFIG_POOL_INDIV, t->options.ifconfig_pool_start, t->options.ifconfig_pool_end, t->options.duplicate_cn); } else { ASSERT (0); } /* reload pool data from file */ if (t->c1.ifconfig_pool_persist) ifconfig_pool_read (t->c1.ifconfig_pool_persist, m->ifconfig_pool); } /* * Help us keep track of routing table. */ m->route_helper = mroute_helper_init (MULTI_CACHE_ROUTE_TTL); /* * Initialize route and instance reaper. */ m->reaper = multi_reap_new (reap_buckets_per_pass (t->options.virtual_hash_size)); /* * Get local ifconfig address */ CLEAR (m->local); ASSERT (t->c1.tuntap); mroute_extract_in_addr_t (&m->local, t->c1.tuntap->local); /* * Per-client limits */ m->max_clients = t->options.max_clients; /* * Initialize multi-socket TCP I/O wait object */ if (tcp_mode) m->mtcp = multi_tcp_init (t->options.max_clients, &m->max_clients); m->tcp_queue_limit = t->options.tcp_queue_limit; /* * Allow client <-> client communication, without going through * tun/tap interface and network stack? */ m->enable_c2c = t->options.enable_c2c; } const char * multi_instance_string (const struct multi_instance *mi, bool null, struct gc_arena *gc) { if (mi) { struct buffer out = alloc_buf_gc (256, gc); const char *cn = tls_common_name (mi->context.c2.tls_multi, true); if (cn) buf_printf (&out, "%s/", cn); buf_printf (&out, "%s", mroute_addr_print (&mi->real, gc)); return BSTR (&out); } else if (null) return NULL; else return "UNDEF"; } void generate_prefix (struct multi_instance *mi) { mi->msg_prefix = multi_instance_string (mi, true, &mi->gc); set_prefix (mi); } void ungenerate_prefix (struct multi_instance *mi) { mi->msg_prefix = NULL; set_prefix (mi); } static const char * mi_prefix (const struct multi_instance *mi) { if (mi && mi->msg_prefix) return mi->msg_prefix; else return "UNDEF_I"; } /* * Tell the route helper about deleted iroutes so * that it can update its mask of currently used * CIDR netlengths. */ static void multi_del_iroutes (struct multi_context *m, struct multi_instance *mi) { const struct iroute *ir; if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN) { for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next) mroute_helper_del_iroute (m->route_helper, ir); } } static void setenv_stats (struct context *c) { setenv_counter (c->c2.es, "bytes_received", c->c2.link_read_bytes); setenv_counter (c->c2.es, "bytes_sent", c->c2.link_write_bytes); } static void multi_client_disconnect_setenv (struct multi_context *m, struct multi_instance *mi) { /* setenv client real IP address */ setenv_trusted (mi->context.c2.es, get_link_socket_info (&mi->context)); /* setenv stats */ setenv_stats (&mi->context); /* setenv connection duration */ { const unsigned int duration = (unsigned int) now - mi->created; setenv_unsigned (mi->context.c2.es, "time_duration", duration); } } static void multi_client_disconnect_script (struct multi_context *m, struct multi_instance *mi) { if ((mi->context.c2.context_auth == CAS_SUCCEEDED && mi->connection_established_flag) || mi->context.c2.context_auth == CAS_PARTIAL) { multi_client_disconnect_setenv (m, mi); if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT)) { if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS) msg (M_WARN, "WARNING: client-disconnect plugin call failed"); } if (mi->context.options.client_disconnect_script) { struct argv argv = argv_new (); setenv_str (mi->context.c2.es, "script_type", "client-disconnect"); argv_printf (&argv, "%sc", mi->context.options.client_disconnect_script); openvpn_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; 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_LOW, "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)", 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", sep, tls_common_name (mi->context.c2.tls_multi, false), sep, mroute_addr_print (&mi->real, &gc), sep, print_in_addr_t (mi->reporting_addr, IA_EMPTY_IF_UNDEF, &gc), sep, mi->context.c2.link_read_bytes, sep, mi->context.c2.link_write_bytes, sep, time_string (mi->created, 0, false, &gc), sep, (unsigned int)mi->created); } gc_free (&gc); } hash_iterator_free (&hi); status_printf (so, "HEADER%cROUTING_TABLE%cVirtual Address%cCommon Name%cReal Address%cLast Ref%cLast Ref (time_t)", sep, sep, sep, sep, sep, sep); hash_iterator_init (m->vhash, &hi); 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.sa.sin_family = AF_INET; remote_si.sa.sin_addr.s_addr = htonl (a); ASSERT (mroute_extract_openvpn_sockaddr (&addr, &remote_si, false)); if (netbits >= 0) { addr.type |= MR_WITH_NETBITS; addr.netbits = (uint8_t) netbits; } { struct multi_instance *owner = multi_learn_addr (m, mi, &addr, 0); #ifdef MANAGEMENT_DEF_AUTH if (management && owner) management_learn_addr (management, &mi->context.c2.mda_context, &addr, primary); #endif return owner; } } /* * A new client has connected, add routes (server -> client) * to internal routing table. */ static void multi_add_iroutes (struct multi_context *m, struct multi_instance *mi) { struct gc_arena gc = gc_new (); const struct iroute *ir; if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN) { mi->did_iroutes = true; for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next) { if (ir->netbits >= 0) msg (D_MULTI_LOW, "MULTI: internal route %s/%d -> %s", print_in_addr_t (ir->network, 0, &gc), ir->netbits, multi_instance_string (mi, false, &gc)); else msg (D_MULTI_LOW, "MULTI: internal route %s -> %s", print_in_addr_t (ir->network, 0, &gc), multi_instance_string (mi, false, &gc)); mroute_helper_add_iroute (m->route_helper, ir); multi_learn_in_addr_t (m, mi, ir->network, ir->netbits, false); } } gc_free (&gc); } /* * Given an instance (new_mi), delete all other instances which use the * same common name. */ static void multi_delete_dup (struct multi_context *m, struct multi_instance *new_mi) { if (new_mi) { const char *new_cn = tls_common_name (new_mi->context.c2.tls_multi, true); if (new_cn) { struct hash_iterator hi; struct hash_element *he; int count = 0; hash_iterator_init (m->iter, &hi); while ((he = hash_iterator_next (&hi))) { struct multi_instance *mi = (struct multi_instance *) he->value; if (mi != new_mi && !mi->halt) { const char *cn = tls_common_name (mi->context.c2.tls_multi, true); if (cn && !strcmp (cn, new_cn)) { mi->did_iter = false; multi_close_instance (m, mi, false); hash_iterator_delete_element (&hi); ++count; } } } hash_iterator_free (&hi); if (count) msg (D_MULTI_LOW, "MULTI: new connection by client '%s' will cause previous active sessions by this client to be dropped. Remember to use the --duplicate-cn option if you want multiple clients using the same certificate or username to concurrently connect.", new_cn); } } } /* * Ensure that endpoint to be pushed to client * complies with --ifconfig-push-constraint directive. */ static bool ifconfig_push_constraint_satisfied (const struct context *c) { const struct options *o = &c->options; if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined) return (o->push_ifconfig_constraint_netmask & c->c2.push_ifconfig_local) == o->push_ifconfig_constraint_network; else return true; } /* * Select a virtual address for a new client instance. * Use an --ifconfig-push directive, if given (static IP). * Otherwise use an --ifconfig-pool address (dynamic IP). */ static void multi_select_virtual_addr (struct multi_context *m, struct multi_instance *mi) { struct gc_arena gc = gc_new (); /* * If ifconfig addresses were set by dynamic config file, * release pool addresses, otherwise keep them. */ if (mi->context.options.push_ifconfig_defined) { /* ifconfig addresses were set statically, release dynamic allocation */ if (mi->vaddr_handle >= 0) { ifconfig_pool_release (m->ifconfig_pool, mi->vaddr_handle, true); mi->vaddr_handle = -1; } mi->context.c2.push_ifconfig_defined = true; mi->context.c2.push_ifconfig_local = mi->context.options.push_ifconfig_local; mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.push_ifconfig_remote_netmask; } else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a pool address */ { in_addr_t local=0, remote=0; const char *cn = NULL; if (!mi->context.options.duplicate_cn) cn = tls_common_name (mi->context.c2.tls_multi, true); mi->vaddr_handle = ifconfig_pool_acquire (m->ifconfig_pool, &local, &remote, cn); if (mi->vaddr_handle >= 0) { const int tunnel_type = TUNNEL_TYPE (mi->context.c1.tuntap); const int tunnel_topology = TUNNEL_TOPOLOGY (mi->context.c1.tuntap); /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */ mi->context.c2.push_ifconfig_local = remote; if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET)) { mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask; if (!mi->context.c2.push_ifconfig_remote_netmask) mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask; } else if (tunnel_type == DEV_TYPE_TUN) { if (tunnel_topology == TOP_P2P) mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local; else if (tunnel_topology == TOP_NET30) mi->context.c2.push_ifconfig_remote_netmask = local; } if (mi->context.c2.push_ifconfig_remote_netmask) mi->context.c2.push_ifconfig_defined = true; else msg (D_MULTI_ERRORS, "MULTI: no --ifconfig-pool netmask parameter is available to push to %s", multi_instance_string (mi, false, &gc)); } else { msg (D_MULTI_ERRORS, "MULTI: no free --ifconfig-pool addresses are available"); } } gc_free (&gc); } /* * Set virtual address environmental variables. */ static void multi_set_virtual_addr_env (struct multi_context *m, struct multi_instance *mi) { setenv_del (mi->context.c2.es, "ifconfig_pool_local_ip"); setenv_del (mi->context.c2.es, "ifconfig_pool_remote_ip"); setenv_del (mi->context.c2.es, "ifconfig_pool_netmask"); if (mi->context.c2.push_ifconfig_defined) { const int tunnel_type = TUNNEL_TYPE (mi->context.c1.tuntap); const int tunnel_topology = TUNNEL_TOPOLOGY (mi->context.c1.tuntap); setenv_in_addr_t (mi->context.c2.es, "ifconfig_pool_remote_ip", mi->context.c2.push_ifconfig_local, SA_SET_IF_NONZERO); if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET)) { setenv_in_addr_t (mi->context.c2.es, "ifconfig_pool_netmask", mi->context.c2.push_ifconfig_remote_netmask, SA_SET_IF_NONZERO); } else if (tunnel_type == DEV_TYPE_TUN) { setenv_in_addr_t (mi->context.c2.es, "ifconfig_pool_local_ip", mi->context.c2.push_ifconfig_remote_netmask, SA_SET_IF_NONZERO); } } } /* * Called after client-connect script is called */ static void multi_client_connect_post (struct multi_context *m, struct multi_instance *mi, const char *dc_file, unsigned int option_permissions_mask, unsigned int *option_types_found) { /* Did script generate a dynamic config file? */ if (test_file (dc_file)) { options_server_import (&mi->context.options, dc_file, D_IMPORT_ERRORS|M_OPTERR, option_permissions_mask, option_types_found, mi->context.c2.es); if (!delete_file (dc_file)) msg (D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s", dc_file); /* * If the --client-connect script generates a config file * with an --ifconfig-push directive, it will override any * --ifconfig-push directive from the --client-config-dir * directory or any --ifconfig-pool dynamic address. */ multi_select_virtual_addr (m, mi); multi_set_virtual_addr_env (m, mi); } } #ifdef ENABLE_PLUGIN /* * Called after client-connect plug-in is called */ static void multi_client_connect_post_plugin (struct multi_context *m, struct multi_instance *mi, const struct plugin_return *pr, unsigned int option_permissions_mask, unsigned int *option_types_found) { struct plugin_return config; plugin_return_get_column (pr, &config, "config"); /* Did script generate a dynamic config file? */ if (plugin_return_defined (&config)) { int i; for (i = 0; i < config.n; ++i) { if (config.list[i] && config.list[i]->value) options_string_import (&mi->context.options, config.list[i]->value, D_IMPORT_ERRORS|M_OPTERR, option_permissions_mask, option_types_found, mi->context.c2.es); } /* * If the --client-connect script generates a config file * with an --ifconfig-push directive, it will override any * --ifconfig-push directive from the --client-config-dir * directory or any --ifconfig-pool dynamic address. */ multi_select_virtual_addr (m, mi); multi_set_virtual_addr_env (m, mi); } } #endif #ifdef MANAGEMENT_DEF_AUTH /* * Called to load management-derived client-connect config */ static void multi_client_connect_mda (struct multi_context *m, struct multi_instance *mi, const struct buffer_list *config, unsigned int option_permissions_mask, unsigned int *option_types_found) { if (config) { struct buffer_entry *be; for (be = config->head; be != NULL; be = be->next) { const char *opt = BSTR(&be->buf); options_string_import (&mi->context.options, opt, D_IMPORT_ERRORS|M_OPTERR, option_permissions_mask, option_types_found, mi->context.c2.es); } /* * If the --client-connect script generates a config file * with an --ifconfig-push directive, it will override any * --ifconfig-push directive from the --client-config-dir * directory or any --ifconfig-pool dynamic address. */ multi_select_virtual_addr (m, mi); multi_set_virtual_addr_env (m, mi); } } #endif static void multi_client_connect_setenv (struct multi_context *m, struct multi_instance *mi) { struct gc_arena gc = gc_new (); /* setenv incoming cert common name for script */ setenv_str (mi->context.c2.es, "common_name", tls_common_name (mi->context.c2.tls_multi, true)); /* setenv client real IP address */ setenv_trusted (mi->context.c2.es, get_link_socket_info (&mi->context)); /* setenv client virtual IP address */ multi_set_virtual_addr_env (m, mi); /* setenv connection time */ { const char *created_ascii = time_string (mi->created, 0, false, &gc); setenv_str (mi->context.c2.es, "time_ascii", created_ascii); setenv_unsigned (mi->context.c2.es, "time_unix", (unsigned int)mi->created); } gc_free (&gc); } /* * Called as soon as the SSL/TLS connection authenticates. * * Instance-specific directives to be processed: * * iroute start-ip end-ip * ifconfig-push local remote-netmask * push */ static void multi_connection_established (struct multi_context *m, struct multi_instance *mi) { if (tls_authentication_status (mi->context.c2.tls_multi, 0) == TLS_AUTHENTICATION_SUCCEEDED) { struct gc_arena gc = gc_new (); unsigned int option_types_found = 0; const unsigned int option_permissions_mask = OPT_P_INSTANCE | OPT_P_INHERIT | OPT_P_PUSH | OPT_P_TIMER | OPT_P_CONFIG | OPT_P_ECHO | OPT_P_COMP | OPT_P_SOCKFLAGS; int cc_succeeded = true; /* client connect script status */ int cc_succeeded_count = 0; ASSERT (mi->context.c1.tuntap); /* lock down the common name and cert hashes so they can't change during future TLS renegotiations */ tls_lock_common_name (mi->context.c2.tls_multi); tls_lock_cert_hash_set (mi->context.c2.tls_multi); /* generate a msg() prefix for this client instance */ generate_prefix (mi); /* delete instances of previous clients with same common-name */ if (!mi->context.options.duplicate_cn) multi_delete_dup (m, mi); /* reset pool handle to null */ mi->vaddr_handle = -1; /* * Try to source a dynamic config file from the * --client-config-dir directory. */ if (mi->context.options.client_config_dir) { const char *ccd_file; ccd_file = gen_path (mi->context.options.client_config_dir, tls_common_name (mi->context.c2.tls_multi, false), &gc); /* try common-name file */ if (test_file (ccd_file)) { options_server_import (&mi->context.options, ccd_file, D_IMPORT_ERRORS|M_OPTERR, option_permissions_mask, &option_types_found, mi->context.c2.es); } else /* try default file */ { ccd_file = gen_path (mi->context.options.client_config_dir, CCD_DEFAULT, &gc); if (test_file (ccd_file)) { options_server_import (&mi->context.options, ccd_file, D_IMPORT_ERRORS|M_OPTERR, option_permissions_mask, &option_types_found, mi->context.c2.es); } } } /* * Select a virtual address from either --ifconfig-push in --client-config-dir file * or --ifconfig-pool. */ multi_select_virtual_addr (m, mi); /* do --client-connect setenvs */ multi_client_connect_setenv (m, mi); #ifdef ENABLE_PLUGIN /* * Call client-connect plug-in. */ /* deprecated callback, use a file for passing back return info */ if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT)) { struct argv argv = argv_new (); const char *dc_file = create_temp_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)); } /* add routes locally, pointing to new client, if --iroute options have been specified */ multi_add_iroutes (m, mi); /* * iroutes represent subnets which are "owned" by a particular * client. Therefore, do not actually push a route to a client * if it matches one of the client's iroutes. */ remove_iroutes_from_push_route_list (&mi->context.options); } else if (mi->context.options.iroutes) { msg (D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute only works with tun-style tunnels", multi_instance_string (mi, false, &gc)); } /* set our client's VPN endpoint for status reporting purposes */ mi->reporting_addr = mi->context.c2.push_ifconfig_local; /* set context-level authentication flag */ mi->context.c2.context_auth = CAS_SUCCEEDED; } else { /* set context-level authentication flag */ mi->context.c2.context_auth = cc_succeeded_count ? CAS_PARTIAL : CAS_FAILED; } /* set flag so we don't get called again */ mi->connection_established_flag = true; /* increment number of current authenticated clients */ ++m->n_clients; --mi->n_clients_delta; #ifdef MANAGEMENT_DEF_AUTH if (management) management_connection_established (management, &mi->context.c2.mda_context, mi->context.c2.es); #endif gc_free (&gc); } /* * Reply now to client's PUSH_REQUEST query */ mi->context.c2.push_reply_deferred = false; } /* * Add a mbuf buffer to a particular * instance. */ void multi_add_mbuf (struct multi_context *m, struct multi_instance *mi, struct mbuf_buffer *mb) { if (multi_output_queue_ready (m, mi)) { struct mbuf_item item; item.buffer = mb; item.instance = mi; mbuf_add_item (m->mbuf, &item); } else { msg (D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_add_mbuf)"); } } /* * Add a packet to a client instance output queue. */ static inline void multi_unicast (struct multi_context *m, const struct buffer *buf, struct multi_instance *mi) { struct mbuf_buffer *mb; if (BLEN (buf) > 0) { mb = mbuf_alloc_buf (buf); mb->flags = MF_UNICAST; multi_add_mbuf (m, mi, mb); mbuf_free_buf (mb); } } /* * Broadcast a packet to all clients. */ static void multi_bcast (struct multi_context *m, const struct buffer *buf, const struct multi_instance *sender_instance, const struct mroute_addr *sender_addr) { struct hash_iterator hi; struct hash_element *he; struct multi_instance *mi; struct mbuf_buffer *mb; if (BLEN (buf) > 0) { perf_push (PERF_MULTI_BCAST); #ifdef MULTI_DEBUG_EVENT_LOOP printf ("BCAST len=%d\n", BLEN (buf)); #endif mb = mbuf_alloc_buf (buf); hash_iterator_init (m->iter, &hi); 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 /* * Process timers in the top-level context */ void multi_process_per_second_timers_dowork (struct multi_context *m) { /* possibly reap instances/routes in vhash */ multi_reap_process (m); /* possibly print to status log */ if (m->top.c1.status_output) { if (status_trigger (m->top.c1.status_output)) multi_print_status (m, m->top.c1.status_output, m->status_file_version); } /* possibly flush ifconfig-pool file */ multi_ifconfig_pool_persist (m, false); #ifdef ENABLE_DEBUG gremlin_flood_clients (m); #endif } void multi_top_init (struct multi_context *m, const struct context *top, const bool alloc_buffers) { inherit_context_top (&m->top, top); m->top.c2.buffers = NULL; if (alloc_buffers) m->top.c2.buffers = init_context_buffers (&top->c2.frame); } void multi_top_free (struct multi_context *m) { close_context (&m->top, -1, CC_GC_FREE); free_context_buffers (m->top.c2.buffers); } /* * Return true if event loop should break, * false if it should continue. */ bool multi_process_signal (struct multi_context *m) { if (m->top.sig->signal_received == SIGUSR2) { struct status_output *so = status_open (NULL, 0, M_INFO, NULL, 0); multi_print_status (m, so, m->status_file_version); status_close (so); m->top.sig->signal_received = 0; return false; } return true; } /* * Called when an instance should be closed due to the * reception of a soft signal. */ void multi_close_instance_on_signal (struct multi_context *m, struct multi_instance *mi) { remap_signal (&mi->context); set_prefix (mi); print_signal (mi->context.sig, "client-instance", D_MULTI_LOW); clear_prefix (); multi_close_instance (m, mi, false); } static void multi_signal_instance (struct multi_context *m, struct multi_instance *mi, const int sig) { mi->context.sig->signal_received = sig; multi_close_instance_on_signal (m, mi); } /* * Management subsystem callbacks */ #ifdef ENABLE_MANAGEMENT static void management_callback_status (void *arg, const int version, struct status_output *so) { struct multi_context *m = (struct multi_context *) arg; if (!version) multi_print_status (m, so, m->status_file_version); else multi_print_status (m, so, version); } static int management_callback_n_clients (void *arg) { struct multi_context *m = (struct multi_context *) arg; return m->n_clients; } static int management_callback_kill_by_cn (void *arg, const char *del_cn) { struct multi_context *m = (struct multi_context *) arg; struct hash_iterator hi; struct hash_element *he; int count = 0; hash_iterator_init (m->iter, &hi); while ((he = hash_iterator_next (&hi))) { struct multi_instance *mi = (struct multi_instance *) he->value; if (!mi->halt) { const char *cn = tls_common_name (mi->context.c2.tls_multi, false); if (cn && !strcmp (cn, del_cn)) { multi_signal_instance (m, mi, SIGTERM); ++count; } } } hash_iterator_free (&hi); return count; } static int management_callback_kill_by_addr (void *arg, const in_addr_t addr, const int port) { struct multi_context *m = (struct multi_context *) arg; struct hash_iterator hi; struct hash_element *he; struct openvpn_sockaddr saddr; struct mroute_addr maddr; int count = 0; CLEAR (saddr); saddr.sa.sin_family = AF_INET; saddr.sa.sin_addr.s_addr = htonl (addr); saddr.sa.sin_port = htons (port); if (mroute_extract_openvpn_sockaddr (&maddr, &saddr, true)) { hash_iterator_init (m->iter, &hi); while ((he = hash_iterator_next (&hi))) { struct multi_instance *mi = (struct multi_instance *) he->value; if (!mi->halt && mroute_addr_equal (&maddr, &mi->real)) { multi_signal_instance (m, mi, SIGTERM); ++count; } } hash_iterator_free (&hi); } return count; } static void management_delete_event (void *arg, event_t event) { struct multi_context *m = (struct multi_context *) arg; if (m->mtcp) multi_tcp_delete_event (m->mtcp, event); } #endif #ifdef MANAGEMENT_DEF_AUTH static struct multi_instance * lookup_by_cid (struct multi_context *m, const unsigned long cid) { if (m) { struct multi_instance *mi = (struct multi_instance *) hash_lookup (m->cid_hash, &cid); if (mi && !mi->halt) return mi; } return NULL; } static bool management_kill_by_cid (void *arg, const unsigned long cid) { struct multi_context *m = (struct multi_context *) arg; struct multi_instance *mi = lookup_by_cid (m, cid); if (mi) { send_restart (&mi->context); /* was: multi_signal_instance (m, mi, SIGTERM); */ return true; } else return false; } static bool management_client_auth (void *arg, const unsigned long cid, const unsigned int mda_key_id, const bool auth, const char *reason, const char *client_reason, struct buffer_list *cc_config) /* ownership transferred */ { struct multi_context *m = (struct multi_context *) arg; struct multi_instance *mi = lookup_by_cid (m, cid); bool cc_config_owned = true; bool ret = false; if (mi) { ret = tls_authenticate_key (mi->context.c2.tls_multi, mda_key_id, auth, client_reason); if (ret) { if (auth) { 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); switch (top->options.ce.proto) { case PROTO_UDPv4: tunnel_server_udp (top); break; case PROTO_TCPv4_SERVER: tunnel_server_tcp (top); break; default: ASSERT (0); } } #else static void dummy(void) {} #endif /* P2MP_SERVER */