[Openvpn-devel,v2] Fix broken fragment/mssfix with NCP

Message ID 1548101094-4449-1-git-send-email-lstipakov@gmail.com
State Accepted
Headers show
Series [Openvpn-devel,v2] Fix broken fragment/mssfix with NCP | expand

Commit Message

Lev Stipakov Jan. 21, 2019, 9:04 a.m. UTC
From: Lev Stipakov <lev@openvpn.net>

NCP negotiation replaces worst cast crypto overhead
with actual one in data channel frame. That frame
params are used by mssfix.

Fragment frame still contains worst case overhead.
Because of that TCP packets are fragmented, since
MSS value exceeds max fragment size.

Fix by replacing worst case crypto overhead with
actual one for fragment frame, as it is done for data
channel frame.

Trac #1140

Signed-off-by: Lev Stipakov <lstipakov@gmail.com>
---

v2: fix --disable-fragment build

 src/openvpn/forward.c |  3 +++
 src/openvpn/init.c    | 12 +++++++++++-
 src/openvpn/openvpn.h |  1 +
 src/openvpn/push.c    |  9 ++++++++-
 src/openvpn/ssl.c     | 19 ++++++++++++++++++-
 src/openvpn/ssl.h     | 13 ++++++++-----
 6 files changed, 49 insertions(+), 8 deletions(-)

Comments

Arne Schwabe May 8, 2019, 4:19 a.m. UTC | #1
Am 21.01.19 um 21:04 schrieb Lev Stipakov:
> From: Lev Stipakov <lev@openvpn.net>
> 
> NCP negotiation replaces worst cast crypto overhead
> with actual one in data channel frame. That frame
> params are used by mssfix.
> 
> Fragment frame still contains worst case overhead.
> Because of that TCP packets are fragmented, since
> MSS value exceeds max fragment size.
> 
> Fix by replacing worst case crypto overhead with
> actual one for fragment frame, as it is done for data
> channel frame.

The change looks good and is tested.

Acked-By: Arne Schwabe <arne@openvpn.net>
Steffan Karger July 20, 2019, 12:25 a.m. UTC | #2
On 08-05-19 16:19, Arne Schwabe wrote:
> Am 21.01.19 um 21:04 schrieb Lev Stipakov:
>> From: Lev Stipakov <lev@openvpn.net>
>>
>> NCP negotiation replaces worst cast crypto overhead
>> with actual one in data channel frame. That frame
>> params are used by mssfix.
>>
>> Fragment frame still contains worst case overhead.
>> Because of that TCP packets are fragmented, since
>> MSS value exceeds max fragment size.
>>
>> Fix by replacing worst case crypto overhead with
>> actual one for fragment frame, as it is done for data
>> channel frame.
> 
> The change looks good and is tested.
> 
> Acked-By: Arne Schwabe <arne@openvpn.net>

Since Arne agrees, I'm completely convinced now too:

Acked-by: Steffan Karger <steffan@karger.me>
Gert Doering July 22, 2019, 8:44 a.m. UTC | #3
Your patch has been applied to the master branch.

I have not tried to understand the actual change, but the general
explanation makes sense and with two ACKs already... I did run t_client
tests, though :-)  (and there is a fragment test).

I did try to apply it to 2.4, because it's a bugfix (so "can I have it
for release/2.4, please!") and while it *applied* fine, it did not 
compile :-( - the function signature for crypto_adjust_frame_parameters() 
is different in 2.4 - "use_iv" still exists.  Can you have a look, 
please, and send a 2.4 version?

commit d22ba6b2c551fa83d23b5cf668e08a08fde446bc (master)
Author: Lev Stipakov
Date:   Mon Jan 21 22:04:54 2019 +0200

     Fix broken fragment/mssfix with NCP

     Signed-off-by: Lev Stipakov <lstipakov@gmail.com>
     Acked-by: Arne Schwabe <arne@rfc2549.org>
     Acked-by: Steffan Karger <steffan.karger@fox-it.com>
     Message-Id: <1548101094-4449-1-git-send-email-lstipakov@gmail.com>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg18135.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering

Patch

diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 4076f64..0dd599a 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -1093,6 +1093,9 @@  process_incoming_link_part1(struct context *c, struct link_socket_info *lsi, boo
                 if (is_hard_reset(opcode, c->options.key_method))
                 {
                     c->c2.frame = c->c2.frame_initial;
+#ifdef ENABLE_FRAGMENT
+                    c->c2.frame_fragment = c->c2.frame_fragment_initial;
+#endif
                 }
 
                 interval_action(&c->c2.tmp_int);
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 560d87d..b1a91af 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -2298,9 +2298,18 @@  do_deferred_options(struct context *c, const unsigned int found)
         {
             tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername);
         }
+        struct frame *frame_fragment = NULL;
+#ifdef ENABLE_FRAGMENT
+        if (c->options.ce.fragment)
+        {
+            frame_fragment = &c->c2.frame_fragment;
+        }
+#endif
+
         /* Do not regenerate keys if server sends an extra push reply */
         if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized
-            && !tls_session_update_crypto_params(session, &c->options, &c->c2.frame))
+            && !tls_session_update_crypto_params(session, &c->options, &c->c2.frame,
+                                                 frame_fragment))
         {
             msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to import crypto options");
             return false;
@@ -3082,6 +3091,7 @@  do_init_frame(struct context *c)
      */
     c->c2.frame_fragment = c->c2.frame;
     frame_subtract_extra(&c->c2.frame_fragment, &c->c2.frame_fragment_omit);
+    c->c2.frame_fragment_initial = c->c2.frame_fragment;
 #endif
 
 #if defined(ENABLE_FRAGMENT) && defined(ENABLE_OCC)
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index d11f61d..a5d250f 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -265,6 +265,7 @@  struct context_2
     /* Object to handle advanced MTU negotiation and datagram fragmentation */
     struct fragment_master *fragment;
     struct frame frame_fragment;
+    struct frame frame_fragment_initial;
     struct frame frame_fragment_omit;
 #endif
 
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 8befc6f..248c2e4 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -287,11 +287,18 @@  incoming_push_message(struct context *c, const struct buffer *buffer)
     {
         if (c->options.mode == MODE_SERVER)
         {
+            struct frame *frame_fragment = NULL;
+#ifdef ENABLE_FRAGMENT
+            if (c->options.ce.fragment)
+            {
+                frame_fragment = &c->c2.frame_fragment;
+            }
+#endif
             struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
             /* Do not regenerate keys if client send a second push request */
             if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized
                 && !tls_session_update_crypto_params(session, &c->options,
-                                                     &c->c2.frame))
+                                                     &c->c2.frame, frame_fragment))
             {
                 msg(D_TLS_ERRORS, "TLS Error: initializing data channel failed");
                 goto error;
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index e9927eb..ea9fd5f 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1990,7 +1990,8 @@  cleanup:
 
 bool
 tls_session_update_crypto_params(struct tls_session *session,
-                                 struct options *options, struct frame *frame)
+                                 struct options *options, struct frame *frame,
+                                 struct frame *frame_fragment)
 {
     if (!session->opt->server
         && 0 != strcmp(options->ciphername, session->opt->config_ciphername)
@@ -2034,6 +2035,22 @@  tls_session_update_crypto_params(struct tls_session *session,
     frame_init_mssfix(frame, options);
     frame_print(frame, D_MTU_INFO, "Data Channel MTU parms");
 
+    /*
+     * mssfix uses data channel framing, which at this point contains
+     * actual overhead. Fragmentation logic uses frame_fragment, which
+     * still contains worst case overhead. Replace it with actual overhead
+     * to prevent unneeded fragmentation.
+     */
+
+    if (frame_fragment)
+    {
+        frame_remove_from_extra_frame(frame_fragment, crypto_max_overhead());
+        crypto_adjust_frame_parameters(frame_fragment, &session->opt->key_type,
+	                               options->replay, packet_id_long_form);
+        frame_set_mtu_dynamic(frame_fragment, options->ce.fragment, SET_MTU_UPPER_BOUND);
+        frame_print(frame_fragment, D_MTU_INFO, "Fragmentation MTU parms");
+    }
+
     return tls_session_generate_data_channel_keys(session);
 }
 
diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h
index eafb235..a6b18d0 100644
--- a/src/openvpn/ssl.h
+++ b/src/openvpn/ssl.h
@@ -476,15 +476,18 @@  void tls_update_remote_addr(struct tls_multi *multi,
  * Update TLS session crypto parameters (cipher and auth) and derive data
  * channel keys based on the supplied options.
  *
- * @param session       The TLS session to update.
- * @param options       The options to use when updating session.
- * @param frame         The frame options for this session (frame overhead is
- *                      adjusted based on the selected cipher/auth).
+ * @param session         The TLS session to update.
+ * @param options         The options to use when updating session.
+ * @param frame           The frame options for this session (frame overhead is
+ *                        adjusted based on the selected cipher/auth).
+ * @param frame_fragment  The fragment frame options.
  *
  * @return true if updating succeeded, false otherwise.
  */
 bool tls_session_update_crypto_params(struct tls_session *session,
-                                      struct options *options, struct frame *frame);
+                                      struct options *options,
+                                      struct frame *frame,
+                                      struct frame *frame_fragment);
 
 /**
  * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.