diff --git a/Changes.rst b/Changes.rst
index 47933ae0..187d03fc 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -92,6 +92,10 @@ Cookie based handshake for UDP server
shake. The tls-crypt-v2 option allows controlling if older clients are
accepted.
+ By default the rate of initial packet responses is limited to 100 per 10s
+ interval to avoid OpenVPN servers being abused in reflection attacks
+ (see ``--connect-freq-initial``).
+
Data channel offloading with ovpn-dco
2.6.0+ implements support for data-channel offloading where the data packets
are directly processed and forwarded in kernel space thanks to the ovpn-dco
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index 99263fff..dbe35d6e 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -178,12 +178,36 @@ fast hardware. SSL/TLS authentication must be used in this mode.
with connection requests using certificates which will ultimately fail
to authenticate.
+ This limit applies after ``--connect-freq-initial`` and
+ only applies to client that have completed the three-way handshake
+ or client that use ``--tls-crypt-v2`` without cookie support
+ (``allow-noncookie`` argument to ``--tls-crypt-v2``).
+
This is an imperfect solution however, because in a real DoS scenario,
legitimate connections might also be refused.
For the best protection against DoS attacks in server mode, use
``--proto udp`` and either ``--tls-auth`` or ``--tls-crypt``.
+--connect-freq-initial args
+ (UDP only) Allow a maximum of ``n`` initial connection packet responses
+ per ``sec`` seconds from the OpenVPN server to clients.
+
+ Valid syntax:
+ ::
+
+ connect-freq-initial n sec
+
+ OpenVPN starting at 2.6 is very efficient in responding to initial
+ connection packets. When not limiting the initial responses
+ an OpenVPN daemon can be abused in reflection attacks.
+ This option is designed to limit the rate OpenVPN will respond to initial
+ attacks.
+
+ Connection attempts that complete the initial three-way handshake
+ will not be counted against the limit. The default is to allow
+ 100 initial connection per 10s.
+
--duplicate-cn
Allow multiple clients with the same common name to concurrently
connect. In the absence of this option, OpenVPN will disconnect a client
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index e80a35ab..35d60a65 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -113,6 +113,7 @@ openvpn_SOURCES = \
ps.c ps.h \
push.c push.h \
pushlist.h \
+ reflect_filter.c reflect_filter.h \
reliable.c reliable.h \
route.c route.h \
run_command.c run_command.h \
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index c27c6da5..77560425 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -82,6 +82,16 @@ do_pre_decrypt_check(struct multi_context *m,
struct openvpn_sockaddr *from = &m->top.c2.from.dest;
int handwindow = m->top.options.handshake_window;
+ if (verdict == VERDICT_VALID_RESET_V3 || verdict == VERDICT_VALID_RESET_V2)
+ {
+ /* Check if we are still below our limit for sending out
+ * responses */
+ if (!reflect_filter_rate_limit_check(m->initial_rate_limiter))
+ {
+ return false;
+ }
+ }
+
if (verdict == VERDICT_VALID_RESET_V3)
{
/* Extract the packet id to check if it has the special format that
@@ -244,6 +254,10 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated)
if (frequency_limit_event_allowed(m->new_connection_limiter))
{
+ /* a successful three-way handshake only counts against
+ * connect-freq but not against connect-freq-initial */
+ reflect_filter_rate_limit_decrease(m->initial_rate_limiter);
+
mi = multi_create_instance(m, &real);
if (mi)
{
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 186e8819..26904859 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -52,6 +52,7 @@
#include "crypto_backend.h"
#include "ssl_util.h"
#include "dco.h"
+#include "reflect_filter.h"
/*#define MULTI_DEBUG_EVENT_LOOP*/
@@ -368,6 +369,8 @@ multi_init(struct multi_context *m, struct context *t, bool tcp_mode)
*/
m->new_connection_limiter = frequency_limit_init(t->options.cf_max,
t->options.cf_per);
+ m->initial_rate_limiter = initial_rate_limit_init(t->options.cf_initial_max,
+ t->options.cf_initial_per);
/*
* Allocate broadcast/multicast buffer list
@@ -729,6 +732,7 @@ multi_uninit(struct multi_context *m)
mbuf_free(m->mbuf);
ifconfig_pool_free(m->ifconfig_pool);
frequency_limit_free(m->new_connection_limiter);
+ initial_rate_limit_free(m->initial_rate_limiter);
multi_reap_free(m->reaper);
mroute_helper_free(m->route_helper);
multi_tcp_free(m->mtcp);
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 370d795c..713c63ee 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -39,6 +39,7 @@
#include "mtcp.h"
#include "perf.h"
#include "vlan.h"
+#include "reflect_filter.h"
#define MULTI_PREFIX_MAX_LENGTH 256
@@ -170,6 +171,7 @@ struct multi_context {
* as external transport. */
struct ifconfig_pool *ifconfig_pool;
struct frequency_limit *new_connection_limiter;
+ struct initial_packet_rate_limit *initial_rate_limiter;
struct mroute_helper *route_helper;
struct multi_reap *reaper;
struct mroute_addr local;
diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj
index 13755dae..97baf678 100644
--- a/src/openvpn/openvpn.vcxproj
+++ b/src/openvpn/openvpn.vcxproj
@@ -322,6 +322,7 @@
+
@@ -417,6 +418,7 @@
+
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index ee378304..e756af94 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -480,6 +480,7 @@ static const char usage_message[] =
" as well as pushes it to connecting clients.\n"
"--learn-address cmd : Run command cmd to validate client virtual addresses.\n"
"--connect-freq n s : Allow a maximum of n new connections per s seconds.\n"
+ "--connect-freq-initial n s : Allow a maximum of n replies for initial connections attempts per s seconds.\n"
"--max-clients n : Allow a maximum of n simultaneously connected clients.\n"
"--max-routes-per-client n : Allow a maximum of n internal routes per client.\n"
"--stale-routes-check n [t] : Remove routes with a last activity timestamp\n"
@@ -864,6 +865,8 @@ init_options(struct options *o, const bool init_gc)
o->n_bcast_buf = 256;
o->tcp_queue_limit = 64;
o->max_clients = 1024;
+ o->cf_initial_per = 10;
+ o->cf_initial_max = 100;
o->max_routes_per_client = 256;
o->stale_routes_check_interval = 0;
o->ifconfig_pool_persist_refresh_freq = 600;
@@ -1555,6 +1558,8 @@ show_p2mp_parms(const struct options *o)
SHOW_BOOL(duplicate_cn);
SHOW_INT(cf_max);
SHOW_INT(cf_per);
+ SHOW_INT(cf_initial_max);
+ SHOW_INT(cf_initial_per);
SHOW_INT(max_clients);
SHOW_INT(max_routes_per_client);
SHOW_STR(auth_user_pass_verify_script);
@@ -7452,6 +7457,22 @@ add_option(struct options *options,
options->cf_max = cf_max;
options->cf_per = cf_per;
}
+ else if (streq(p[0], "connect-freq-initial") && p[1] && p[2] && !p[3])
+ {
+ long cf_max, cf_per;
+
+ VERIFY_PERMISSION(OPT_P_GENERAL);
+ char *e1, *e2;
+ cf_max = strtol(p[1], &e1, 10);
+ cf_per = strtol(p[2], &e2, 10);
+ if (cf_max < 0 || cf_per < 0 || *e1 != '\0' || *e2 != '\0')
+ {
+ msg(msglevel, "--connect-freq-initial parameters must be integers and >= 0");
+ goto err;
+ }
+ options->cf_initial_max = cf_max;
+ options->cf_initial_per = cf_per;
+ }
else if (streq(p[0], "max-clients") && p[1] && !p[2])
{
int max_clients;
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index fec1eace..48315b10 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -513,8 +513,13 @@ struct options
bool push_ifconfig_ipv6_blocked; /* IPv6 */
bool enable_c2c;
bool duplicate_cn;
+
int cf_max;
int cf_per;
+
+ int cf_initial_max;
+ int cf_initial_per;
+
int max_clients;
int max_routes_per_client;
int stale_routes_check_interval;
diff --git a/src/openvpn/reflect_filter.c b/src/openvpn/reflect_filter.c
new file mode 100644
index 00000000..323184cb
--- /dev/null
+++ b/src/openvpn/reflect_filter.c
@@ -0,0 +1,107 @@
+/*
+ * 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) 2022 OpenVPN 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; 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"
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "crypto.h"
+#include "reflect_filter.h"
+
+
+bool
+reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl)
+{
+ if (now > irl->last_period_reset + irl->period_length)
+ {
+ int64_t dropped = irl->curr_period_counter - irl->max_per_period;
+ if (dropped > 0)
+ {
+ msg(D_TLS_DEBUG_LOW, "Dropped %" PRId64 " initial handshake packets"
+ " due to --connect-freq-initial %" PRId64 " %d", dropped,
+ irl->max_per_period, irl->period_length);
+
+ }
+ irl->last_period_reset = now;
+ irl->curr_period_counter = 0;
+ irl->warning_displayed = false;
+ }
+
+ irl->curr_period_counter++;
+
+ bool over_limit = irl->curr_period_counter > irl->max_per_period;
+
+ if (over_limit && !irl->warning_displayed)
+ {
+ msg(M_WARN, "Note: --connect-freq-initial %" PRId64 " %d rate limit "
+ "exceeded, dropping initial handshake packets for the next %d "
+ "seconds", irl->max_per_period, irl->period_length,
+ (int)(irl->last_period_reset + irl->period_length - now));
+ irl->warning_displayed = true;
+ }
+ return !over_limit;
+}
+
+void
+reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl)
+{
+ if (irl->curr_period_counter > 0)
+ {
+ irl->curr_period_counter--;
+ }
+}
+
+
+struct initial_packet_rate_limit *
+initial_rate_limit_init(int max_per_period, int period_length)
+{
+ struct initial_packet_rate_limit *irl;
+
+
+ ALLOC_OBJ(irl, struct initial_packet_rate_limit);
+
+ irl->max_per_period = max_per_period;
+ irl->period_length = period_length;
+ irl->curr_period_counter = 0;
+ irl->last_period_reset = 0;
+
+ return irl;
+}
+
+void
+initial_rate_limit_free(struct initial_packet_rate_limit *irl)
+{
+ free(irl);
+}
diff --git a/src/openvpn/reflect_filter.h b/src/openvpn/reflect_filter.h
new file mode 100644
index 00000000..6dfa8755
--- /dev/null
+++ b/src/openvpn/reflect_filter.h
@@ -0,0 +1,75 @@
+/*
+ * 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) 2022 OpenVPN 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; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef REFLECT_FILTER_H
+#define REFLECT_FILTER_H
+
+#include
+
+/** struct that handles all the rate limiting logic for initial
+ * responses */
+struct initial_packet_rate_limit {
+ /** This is a hard limit for packets per seconds. */
+ int64_t max_per_period;
+
+ /** period length in seconds */
+ int period_length;
+
+ /** Number of packets in the current period. We use int64_t here
+ * to avoid any potiential issues with overflow */
+ int64_t curr_period_counter;
+
+ /* Last time we reset our timer */
+ time_t last_period_reset;
+
+ /* we want to warn once per period that packets are being started to
+ * be dropped */
+ bool warning_displayed;
+};
+
+
+/**
+ * checks if the connection is still allowed to connect under the rate
+ * limit. This also increases the internal counter at the same time
+ */
+bool
+reflect_filter_rate_limit_check(struct initial_packet_rate_limit *irl);
+
+/**
+ * decreases the counter of initial packets seen, so connections that
+ * successfully completed the three-way handshake do not count against
+ * the counter of initial connection attempts
+ */
+void
+reflect_filter_rate_limit_decrease(struct initial_packet_rate_limit *irl);
+
+/**
+ * allocate and initialize the initial-packet rate limiter structure
+ */
+struct initial_packet_rate_limit *
+initial_rate_limit_init(int max_per_period, int period_length);
+
+/**
+ * free the initial-packet rate limiter structure
+ */
+void initial_rate_limit_free(struct initial_packet_rate_limit *irl);
+#endif /* ifndef REFLECT_FILTER_H */