Browse code

Add per session pseudo-random jitter to --reneg-sec intervals

While we were suffering from the "TLS Renegotiation Slowdown" bug here
https://community.openvpn.net/openvpn/ticket/854 we realized that there is
still room for improvement in our use case.

It appears that TLS renegotiation is getting more and more expensive in
terms of CPU cycles with recent changes for more security. To make things
worse, we realized that most renegotiation procedures took place at almost
the same time and increased the CPU load too much during these periods.
That's especially true on large, multi-instance openvpn setups.

I've created attached patch to add a per session pseudo-random component to
the --reneg-sec intervals so that renegotiation is evenly spread over time.
It is configured by simply adding a second value to --reneg-sec as
described in the --help text:

--reneg-sec max [min] : Renegotiate data chan. key after at most max
(default=3600) and at least min (default 90% of max on
servers and equal to max on clients).

The jitter is only enabled by default on servers, because the actual reneg
time is min(reneg_server, reneg_client). Introducing jitter at both ends
would bias the actual reneg time to the min value.

Note that the patch also slightly changes the log output to show the sec
value in the same way as the bytes/pkts values:

TLS: soft reset sec=3084/3084 bytes=279897/-1 pkts=1370/0

The idea and first versions of this patch are from Simon Matter. Steffan
Karger later incorporated the mailing list comments into this patch. So
credits go to Simon, and all bugs are Steffan's fault ;-)

Signed-off-by: Simon Matter <simon.matter@invoca.ch>
Signed-off-by: Steffan Karger <steffan@karger.me>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20171116140958.12847-1-steffan@karger.me>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg15888.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Simon Matter authored on 2017/11/16 23:09:58
Showing 5 changed files
... ...
@@ -33,7 +33,7 @@
33 33
 .\" .ft -- normal face
34 34
 .\" .in +|-{n} -- indent
35 35
 .\"
36
-.TH openvpn 8 "25 August 2016"
36
+.TH openvpn 8 "04 April 2017"
37 37
 .\"*********************************************************
38 38
 .SH NAME
39 39
 openvpn \- secure IP tunnel daemon.
... ...
@@ -4957,10 +4957,26 @@ Renegotiate data channel key after
4957 4957
 packets sent and received (disabled by default).
4958 4958
 .\"*********************************************************
4959 4959
 .TP
4960
-.B \-\-reneg\-sec n
4961
-Renegotiate data channel key after
4962
-.B n
4963
-seconds (default=3600).
4960
+.B \-\-reneg\-sec max [min]
4961
+Renegotiate data channel key after at most
4962
+.B max
4963
+seconds (default=3600) and at least
4964
+.B min
4965
+seconds (default is 90% of
4966
+.B max
4967
+for servers, and equal to
4968
+.B max
4969
+for clients).
4970
+
4971
+The effective
4972
+.B reneg\-sec
4973
+value used is per session pseudo-uniform-randomized between
4974
+.B min
4975
+and
4976
+.B max\fR.
4977
+
4978
+With the default value of 3600 this results in an effective per session value
4979
+in the range of 3240..3600 seconds for servers, or just 3600 for clients.
4964 4980
 
4965 4981
 When using dual\-factor authentication, note that this default value may
4966 4982
 cause the end user to be challenged to reauthorize once per hour.
... ...
@@ -2693,7 +2693,20 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
2693 2693
     to.packet_timeout = options->tls_timeout;
2694 2694
     to.renegotiate_bytes = options->renegotiate_bytes;
2695 2695
     to.renegotiate_packets = options->renegotiate_packets;
2696
-    to.renegotiate_seconds = options->renegotiate_seconds;
2696
+    if (options->renegotiate_seconds_min < 0)
2697
+    {
2698
+        /* Add 10% jitter to reneg-sec by default (server side only) */
2699
+        int auto_jitter = options->mode != MODE_SERVER ? 0 :
2700
+                get_random() % max_int(options->renegotiate_seconds / 10, 1);
2701
+        to.renegotiate_seconds = options->renegotiate_seconds - auto_jitter;
2702
+    }
2703
+    else
2704
+    {
2705
+        /* Add user-specified jitter to reneg-sec */
2706
+        to.renegotiate_seconds = options->renegotiate_seconds -
2707
+                (get_random() % max_int(options->renegotiate_seconds
2708
+                                        - options->renegotiate_seconds_min, 1));
2709
+    }
2697 2710
     to.single_session = options->single_session;
2698 2711
     to.mode = options->mode;
2699 2712
     to.pull = options->pull;
... ...
@@ -603,7 +603,9 @@ static const char usage_message[] =
603 603
     "                  if no ACK from remote within n seconds (default=%d).\n"
604 604
     "--reneg-bytes n : Renegotiate data chan. key after n bytes sent and recvd.\n"
605 605
     "--reneg-pkts n  : Renegotiate data chan. key after n packets sent and recvd.\n"
606
-    "--reneg-sec n   : Renegotiate data chan. key after n seconds (default=%d).\n"
606
+    "--reneg-sec max [min] : Renegotiate data chan. key after at most max (default=%d)\n"
607
+    "                  and at least min (defaults to 90%% of max on servers and equal\n"
608
+    "                  to max on clients).\n"
607 609
     "--hand-window n : Data channel key exchange must finalize within n seconds\n"
608 610
     "                  of handshake initiation by any peer (default=%d).\n"
609 611
     "--tran-window n : Transition window -- old key can live this many seconds\n"
... ...
@@ -870,6 +872,7 @@ init_options(struct options *o, const bool init_gc)
870 870
     o->tls_timeout = 2;
871 871
     o->renegotiate_bytes = -1;
872 872
     o->renegotiate_seconds = 3600;
873
+    o->renegotiate_seconds_min = -1;
873 874
     o->handshake_window = 60;
874 875
     o->transition_window = 3600;
875 876
     o->ecdh_curve = NULL;
... ...
@@ -8001,10 +8004,14 @@ add_option(struct options *options,
8001 8001
         VERIFY_PERMISSION(OPT_P_TLS_PARMS);
8002 8002
         options->renegotiate_packets = positive_atoi(p[1]);
8003 8003
     }
8004
-    else if (streq(p[0], "reneg-sec") && p[1] && !p[2])
8004
+    else if (streq(p[0], "reneg-sec") && p[1] && !p[3])
8005 8005
     {
8006 8006
         VERIFY_PERMISSION(OPT_P_TLS_PARMS);
8007 8007
         options->renegotiate_seconds = positive_atoi(p[1]);
8008
+        if (p[2])
8009
+        {
8010
+            options->renegotiate_seconds_min = positive_atoi(p[2]);
8011
+        }
8008 8012
     }
8009 8013
     else if (streq(p[0], "hand-window") && p[1] && !p[2])
8010 8014
     {
... ...
@@ -548,6 +548,7 @@ struct options
548 548
     int renegotiate_bytes;
549 549
     int renegotiate_packets;
550 550
     int renegotiate_seconds;
551
+    int renegotiate_seconds_min;
551 552
 
552 553
     /* Data channel key handshake must finalize
553 554
      * within n seconds of handshake initiation. */
... ...
@@ -2732,9 +2732,9 @@ tls_process(struct tls_multi *multi,
2732 2732
                 && ks->n_packets >= session->opt->renegotiate_packets)
2733 2733
             || (packet_id_close_to_wrapping(&ks->crypto_options.packet_id.send))))
2734 2734
     {
2735
-        msg(D_TLS_DEBUG_LOW,
2736
-            "TLS: soft reset sec=%d bytes=" counter_format "/%d pkts=" counter_format "/%d",
2737
-            (int)(ks->established + session->opt->renegotiate_seconds - now),
2735
+        msg(D_TLS_DEBUG_LOW, "TLS: soft reset sec=%d/%d bytes=" counter_format
2736
+            "/%d pkts=" counter_format "/%d",
2737
+            (int) (now - ks->established), session->opt->renegotiate_seconds,
2738 2738
             ks->n_bytes, session->opt->renegotiate_bytes,
2739 2739
             ks->n_packets, session->opt->renegotiate_packets);
2740 2740
         key_state_soft_reset(session);