blob: 4f6365480d00b539fbed5759e482858ff5da7ab7 [file] [log] [blame]
/*
* 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-2018 OpenVPN Inc <sales@openvpn.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#if P2MP_SERVER
#include "multi.h"
#include <inttypes.h>
#include "forward-inline.h"
#include "memdbg.h"
#ifdef HAVE_SYS_INOTIFY_H
#include <sys/inotify.h>
#endif
/*
* Get a client instance based on real address. If
* the instance doesn't exist, create it while
* maintaining real address hash table atomicity.
*/
struct multi_instance *
multi_get_create_instance_udp(struct multi_context *m, bool *floated)
{
struct gc_arena gc = gc_new();
struct mroute_addr real;
struct multi_instance *mi = NULL;
struct hash *hash = m->hash;
if (mroute_extract_openvpn_sockaddr(&real, &m->top.c2.from.dest, true)
&& m->top.c2.buf.len > 0)
{
struct hash_element *he;
const uint32_t hv = hash_value(hash, &real);
struct hash_bucket *bucket = hash_bucket(hash, hv);
uint8_t *ptr = BPTR(&m->top.c2.buf);
uint8_t op = ptr[0] >> P_OPCODE_SHIFT;
bool v2 = (op == P_DATA_V2) && (m->top.c2.buf.len >= (1 + 3));
bool peer_id_disabled = false;
/* make sure buffer has enough length to read opcode (1 byte) and peer-id (3 bytes) */
if (v2)
{
uint32_t peer_id = ntohl(*(uint32_t *)ptr) & 0xFFFFFF;
peer_id_disabled = (peer_id == MAX_PEER_ID);
if (!peer_id_disabled && (peer_id < m->max_clients) && (m->instances[peer_id]))
{
mi = m->instances[peer_id];
*floated = !link_socket_actual_match(&mi->context.c2.from, &m->top.c2.from);
if (*floated)
{
/* reset prefix, since here we are not sure peer is the one it claims to be */
ungenerate_prefix(mi);
msg(D_MULTI_MEDIUM, "Float requested for peer %" PRIu32 " to %s", peer_id,
mroute_addr_print(&real, &gc));
}
}
}
if (!v2 || peer_id_disabled)
{
he = hash_lookup_fast(hash, bucket, &real, hv);
if (he)
{
mi = (struct multi_instance *) he->value;
}
}
if (!mi)
{
if (!m->top.c2.tls_auth_standalone
|| tls_pre_decrypt_lite(m->top.c2.tls_auth_standalone, &m->top.c2.from, &m->top.c2.buf))
{
if (frequency_limit_event_allowed(m->new_connection_limiter))
{
mi = multi_create_instance(m, &real);
if (mi)
{
int i;
hash_add_fast(hash, bucket, &mi->real, hv, mi);
mi->did_real_hash = true;
/* max_clients must be less then max peer-id value */
ASSERT(m->max_clients < MAX_PEER_ID);
for (i = 0; i < m->max_clients; ++i)
{
if (!m->instances[i])
{
mi->context.c2.tls_multi->peer_id = i;
m->instances[i] = mi;
break;
}
}
/* should not really end up here, since multi_create_instance returns null
* if amount of clients exceeds max_clients */
ASSERT(i < m->max_clients);
}
}
else
{
msg(D_MULTI_ERRORS,
"MULTI: Connection from %s would exceed new connection frequency limit as controlled by --connect-freq",
mroute_addr_print(&real, &gc));
}
}
}
#ifdef ENABLE_DEBUG
if (check_debug_level(D_MULTI_DEBUG))
{
const char *status = mi ? "[ok]" : "[failed]";
dmsg(D_MULTI_DEBUG, "GET INST BY REAL: %s %s",
mroute_addr_print(&real, &gc),
status);
}
#endif
}
gc_free(&gc);
ASSERT(!(mi && mi->halt));
return mi;
}
/*
* Send a packet to TCP/UDP socket.
*/
static inline void
multi_process_outgoing_link(struct multi_context *m, const unsigned int mpp_flags)
{
struct multi_instance *mi = multi_process_outgoing_link_pre(m);
if (mi)
{
multi_process_outgoing_link_dowork(m, mi, mpp_flags);
}
}
/*
* Process an I/O event.
*/
static void
multi_process_io_udp(struct multi_context *m)
{
const unsigned int status = m->top.c2.event_set_status;
const unsigned int mpp_flags = m->top.c2.fast_io
? (MPP_CONDITIONAL_PRE_SELECT | MPP_CLOSE_ON_SIGNAL)
: (MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
#ifdef MULTI_DEBUG_EVENT_LOOP
char buf[16];
buf[0] = 0;
if (status & SOCKET_READ)
{
strcat(buf, "SR/");
}
else if (status & SOCKET_WRITE)
{
strcat(buf, "SW/");
}
else if (status & TUN_READ)
{
strcat(buf, "TR/");
}
else if (status & TUN_WRITE)
{
strcat(buf, "TW/");
}
#ifdef ENABLE_ASYNC_PUSH
else if (status & FILE_CLOSED)
{
strcat(buf, "FC/");
}
#endif
printf("IO %s\n", buf);
#endif /* ifdef MULTI_DEBUG_EVENT_LOOP */
#ifdef ENABLE_MANAGEMENT
if (status & (MANAGEMENT_READ|MANAGEMENT_WRITE))
{
ASSERT(management);
management_io(management);
}
#endif
/* UDP port ready to accept write */
if (status & SOCKET_WRITE)
{
multi_process_outgoing_link(m, mpp_flags);
}
/* TUN device ready to accept write */
else if (status & TUN_WRITE)
{
multi_process_outgoing_tun(m, mpp_flags);
}
/* Incoming data on UDP port */
else if (status & SOCKET_READ)
{
read_incoming_link(&m->top);
if (!IS_SIG(&m->top))
{
multi_process_incoming_link(m, NULL, mpp_flags);
}
}
/* Incoming data on TUN device */
else if (status & TUN_READ)
{
read_incoming_tun(&m->top);
if (!IS_SIG(&m->top))
{
multi_process_incoming_tun(m, mpp_flags);
}
}
#ifdef ENABLE_ASYNC_PUSH
/* INOTIFY callback */
else if (status & FILE_CLOSED)
{
multi_process_file_closed(m, mpp_flags);
}
#endif
}
/*
* Return the io_wait() flags appropriate for
* a point-to-multipoint tunnel.
*/
static inline unsigned int
p2mp_iow_flags(const struct multi_context *m)
{
unsigned int flags = IOW_WAIT_SIGNAL;
if (m->pending)
{
if (TUN_OUT(&m->pending->context))
{
flags |= IOW_TO_TUN;
}
if (LINK_OUT(&m->pending->context))
{
flags |= IOW_TO_LINK;
}
}
else if (mbuf_defined(m->mbuf))
{
flags |= IOW_MBUF;
}
else
{
flags |= IOW_READ;
}
return flags;
}
/**************************************************************************/
/**
* Main event loop for OpenVPN in UDP server mode.
* @ingroup eventloop
*
* This function implements OpenVPN's main event loop for UDP server mode.
* At this time, OpenVPN does not yet support multithreading. This
* function's name is therefore slightly misleading.
*
* @param top - Top-level context structure.
*/
static void
tunnel_server_udp_single_threaded(struct context *top)
{
struct multi_context multi;
top->mode = CM_TOP;
context_clear_2(top);
/* initialize top-tunnel instance */
init_instance_handle_signals(top, top->es, CC_HARD_USR1_TO_HUP);
if (IS_SIG(top))
{
return;
}
/* initialize global multi_context object */
multi_init(&multi, top, false, MC_SINGLE_THREADED);
/* initialize our cloned top object */
multi_top_init(&multi, top);
/* initialize management interface */
init_management_callback_multi(&multi);
/* finished with initialization */
initialization_sequence_completed(top, ISC_SERVER); /* --mode server --proto udp */
#ifdef ENABLE_ASYNC_PUSH
multi.top.c2.inotify_fd = inotify_init();
if (multi.top.c2.inotify_fd < 0)
{
msg(D_MULTI_ERRORS | M_ERRNO, "MULTI: inotify_init error");
}
#endif
/* per-packet event loop */
while (true)
{
perf_push(PERF_EVENT_LOOP);
/* set up and do the io_wait() */
multi_get_timeout(&multi, &multi.top.c2.timeval);
io_wait(&multi.top, p2mp_iow_flags(&multi));
MULTI_CHECK_SIG(&multi);
/* check on status of coarse timers */
multi_process_per_second_timers(&multi);
/* timeout? */
if (multi.top.c2.event_set_status == ES_TIMEOUT)
{
multi_process_timeout(&multi, MPP_PRE_SELECT|MPP_CLOSE_ON_SIGNAL);
}
else
{
/* process I/O */
multi_process_io_udp(&multi);
MULTI_CHECK_SIG(&multi);
}
perf_pop();
}
#ifdef ENABLE_ASYNC_PUSH
close(top->c2.inotify_fd);
#endif
/* shut down management interface */
uninit_management_callback();
/* save ifconfig-pool */
multi_ifconfig_pool_persist(&multi, true);
/* tear down tunnel instance (unless --persist-tun) */
multi_uninit(&multi);
multi_top_free(&multi);
close_instance(top);
}
void
tunnel_server_udp(struct context *top)
{
tunnel_server_udp_single_threaded(top);
}
#endif /* if P2MP_SERVER */