src/openvpn/fragment.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
6fbf66fa
  *
  *  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.
  *
caa54ac3
  *  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.
6fbf66fa
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
6fbf66fa
 #include "syshead.h"
 
 #ifdef ENABLE_FRAGMENT
 
 #include "misc.h"
 #include "fragment.h"
 #include "integer.h"
 #include "memdbg.h"
 
 #define FRAG_ERR(s) { errmsg = s; goto error; }
 
 static void
81d882d5
 fragment_list_buf_init(struct fragment_list *list, const struct frame *frame)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < N_FRAG_BUF; ++i)
4cd4899e
     {
81d882d5
         list->fragments[i].buf = alloc_buf(BUF_SIZE(frame));
4cd4899e
     }
6fbf66fa
 }
 
 static void
81d882d5
 fragment_list_buf_free(struct fragment_list *list)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < N_FRAG_BUF; ++i)
4cd4899e
     {
81d882d5
         free_buf(&list->fragments[i].buf);
4cd4899e
     }
6fbf66fa
 }
 
 /*
  * Given a sequence ID number, get a fragment buffer.  Use a sliding window,
  * similar to packet_id code.
  */
 static struct fragment *
81d882d5
 fragment_list_get_buf(struct fragment_list *list, int seq_id)
6fbf66fa
 {
81d882d5
     int diff;
     if (abs(diff = modulo_subtract(seq_id, list->seq_id, N_SEQ_ID)) >= N_FRAG_BUF)
6fbf66fa
     {
81d882d5
         int i;
         for (i = 0; i < N_FRAG_BUF; ++i)
4cd4899e
         {
81d882d5
             list->fragments[i].defined = false;
4cd4899e
         }
81d882d5
         list->index = 0;
         list->seq_id = seq_id;
         diff = 0;
6fbf66fa
     }
81d882d5
     while (diff > 0)
6fbf66fa
     {
81d882d5
         list->fragments[list->index = modulo_add(list->index, 1, N_FRAG_BUF)].defined = false;
         list->seq_id = modulo_add(list->seq_id, 1, N_SEQ_ID);
         --diff;
6fbf66fa
     }
81d882d5
     return &list->fragments[modulo_add(list->index, diff, N_FRAG_BUF)];
6fbf66fa
 }
 
 struct fragment_master *
81d882d5
 fragment_init(struct frame *frame)
6fbf66fa
 {
81d882d5
     struct fragment_master *ret;
6fbf66fa
 
81d882d5
     /* code that initializes other parts of
      * fragment_master assume an initial CLEAR */
     ALLOC_OBJ_CLEAR(ret, struct fragment_master);
6fbf66fa
 
81d882d5
     /* add in the size of our contribution to the expanded frame size */
     frame_add_to_extra_frame(frame, sizeof(fragment_header_type));
6fbf66fa
 
81d882d5
     /*
      * Outgoing sequence ID is randomized to reduce
      * the probability of sequence number collisions
      * when openvpn sessions are restarted.  This is
      * not done out of any need for security, as all
      * fragmentation control information resides
      * inside of the encrypted/authenticated envelope.
      */
     ret->outgoing_seq_id = (int)get_random() & (N_SEQ_ID - 1);
6fbf66fa
 
81d882d5
     event_timeout_init(&ret->wakeup, FRAG_WAKEUP_INTERVAL, now);
6fbf66fa
 
81d882d5
     return ret;
6fbf66fa
 }
 
 void
81d882d5
 fragment_free(struct fragment_master *f)
6fbf66fa
 {
81d882d5
     fragment_list_buf_free(&f->incoming);
     free_buf(&f->outgoing);
     free_buf(&f->outgoing_return);
     free(f);
6fbf66fa
 }
 
 void
81d882d5
 fragment_frame_init(struct fragment_master *f, const struct frame *frame)
6fbf66fa
 {
81d882d5
     fragment_list_buf_init(&f->incoming, frame);
     f->outgoing = alloc_buf(BUF_SIZE(frame));
     f->outgoing_return = alloc_buf(BUF_SIZE(frame));
6fbf66fa
 }
 
 /*
  * Accept an incoming datagram (which may be a fragment) from remote.
  * If the datagram is whole (i.e not a fragment), pass through.
  * If the datagram is a fragment, join with other fragments received so far.
  * If a fragment fully completes the datagram, return the datagram.
  */
 void
81d882d5
 fragment_incoming(struct fragment_master *f, struct buffer *buf,
                   const struct frame *frame)
6fbf66fa
 {
81d882d5
     const char *errmsg = NULL;
     fragment_header_type flags = 0;
     int frag_type = 0;
6fbf66fa
 
81d882d5
     if (buf->len > 0)
6fbf66fa
     {
81d882d5
         /* get flags from packet head */
         if (!buf_read(buf, &flags, sizeof(flags)))
         {
             FRAG_ERR("flags not found in packet");
         }
         flags = ntoh_fragment_header_type(flags);
6fbf66fa
 
81d882d5
         /* get fragment type from flags */
         frag_type = ((flags >> FRAG_TYPE_SHIFT) & FRAG_TYPE_MASK);
6fbf66fa
 
 #if 0
81d882d5
         /*
          * If you want to extract FRAG_EXTRA_MASK/FRAG_EXTRA_SHIFT bits,
          * do it here.
          */
         if (frag_type == FRAG_WHOLE || frag_type == FRAG_YES_NOTLAST)
         {
         }
6fbf66fa
 #endif
 
81d882d5
         /* handle the fragment type */
         if (frag_type == FRAG_WHOLE)
         {
             dmsg(D_FRAG_DEBUG,
                  "FRAG_IN buf->len=%d type=FRAG_WHOLE flags="
                  fragment_header_format,
                  buf->len,
                  flags);
 
             if (flags & (FRAG_SEQ_ID_MASK | FRAG_ID_MASK))
             {
                 FRAG_ERR("spurrious FRAG_WHOLE flags");
             }
         }
         else if (frag_type == FRAG_YES_NOTLAST || frag_type == FRAG_YES_LAST)
         {
             const int seq_id = ((flags >> FRAG_SEQ_ID_SHIFT) & FRAG_SEQ_ID_MASK);
             const int n = ((flags >> FRAG_ID_SHIFT) & FRAG_ID_MASK);
             const int size = ((frag_type == FRAG_YES_LAST)
                               ? (int)(((flags >> FRAG_SIZE_SHIFT) & FRAG_SIZE_MASK) << FRAG_SIZE_ROUND_SHIFT)
                               : buf->len);
 
             /* get the appropriate fragment buffer based on received seq_id */
             struct fragment *frag = fragment_list_get_buf(&f->incoming, seq_id);
 
             dmsg(D_FRAG_DEBUG,
                  "FRAG_IN len=%d type=%d seq_id=%d frag_id=%d size=%d flags="
                  fragment_header_format,
                  buf->len,
                  frag_type,
                  seq_id,
                  n,
                  size,
                  flags);
 
             /* make sure that size is an even multiple of 1<<FRAG_SIZE_ROUND_SHIFT */
             if (size & FRAG_SIZE_ROUND_MASK)
             {
                 FRAG_ERR("bad fragment size");
             }
 
             /* is this the first fragment for our sequence number? */
10ae9ed5
             if (!frag->defined || frag->max_frag_size != size)
81d882d5
             {
                 frag->defined = true;
                 frag->max_frag_size = size;
                 frag->map = 0;
                 ASSERT(buf_init(&frag->buf, FRAME_HEADROOM_ADJ(frame, FRAME_HEADROOM_MARKER_FRAGMENT)));
             }
 
             /* copy the data to fragment buffer */
             if (!buf_copy_range(&frag->buf, n * size, buf, 0, buf->len))
             {
                 FRAG_ERR("fragment buffer overflow");
             }
 
             /* set elements in bit array to reflect which fragments have been received */
             frag->map |= (((frag_type == FRAG_YES_LAST) ? FRAG_MAP_MASK : 1) << n);
 
             /* update timestamp on partially built datagram */
             frag->timestamp = now;
 
             /* received full datagram? */
             if ((frag->map & FRAG_MAP_MASK) == FRAG_MAP_MASK)
             {
                 frag->defined = false;
                 *buf = frag->buf;
             }
             else
             {
                 buf->len = 0;
             }
         }
         else if (frag_type == FRAG_TEST)
         {
             FRAG_ERR("FRAG_TEST not implemented");
         }
         else
         {
             FRAG_ERR("unknown fragment type");
         }
6fbf66fa
     }
 
81d882d5
     return;
6fbf66fa
 
81d882d5
 error:
     if (errmsg)
     {
         msg(D_FRAG_ERRORS, "FRAG_IN error flags=" fragment_header_format ": %s", flags, errmsg);
     }
     buf->len = 0;
     return;
6fbf66fa
 }
 
 /* pack fragment parms into a uint32_t and prepend to buffer */
 static void
81d882d5
 fragment_prepend_flags(struct buffer *buf,
                        int type,
                        int seq_id,
                        int frag_id,
                        int frag_size)
6fbf66fa
 {
81d882d5
     fragment_header_type flags = ((type & FRAG_TYPE_MASK) << FRAG_TYPE_SHIFT)
                                  | ((seq_id & FRAG_SEQ_ID_MASK) << FRAG_SEQ_ID_SHIFT)
                                  | ((frag_id & FRAG_ID_MASK) << FRAG_ID_SHIFT);
6fbf66fa
 
81d882d5
     if (type == FRAG_WHOLE || type == FRAG_YES_NOTLAST)
6fbf66fa
     {
81d882d5
         /*
          * If you want to set FRAG_EXTRA_MASK/FRAG_EXTRA_SHIFT bits,
          * do it here.
          */
         dmsg(D_FRAG_DEBUG,
              "FRAG_OUT len=%d type=%d seq_id=%d frag_id=%d frag_size=%d flags="
              fragment_header_format,
              buf->len, type, seq_id, frag_id, frag_size, flags);
6fbf66fa
     }
81d882d5
     else
6fbf66fa
     {
81d882d5
         flags |= (((frag_size >> FRAG_SIZE_ROUND_SHIFT) & FRAG_SIZE_MASK) << FRAG_SIZE_SHIFT);
6fbf66fa
 
81d882d5
         dmsg(D_FRAG_DEBUG,
              "FRAG_OUT len=%d type=%d seq_id=%d frag_id=%d frag_size=%d flags="
              fragment_header_format,
              buf->len, type, seq_id, frag_id, frag_size, flags);
6fbf66fa
     }
 
81d882d5
     flags = hton_fragment_header_type(flags);
     ASSERT(buf_write_prepend(buf, &flags, sizeof(flags)));
6fbf66fa
 }
 
 /*
  * Without changing the number of fragments, return a possibly smaller
  * max fragment size that will allow for the last fragment to be of
  * similar size as previous fragments.
  */
 static inline int
81d882d5
 optimal_fragment_size(int len, int max_frag_size)
6fbf66fa
 {
81d882d5
     const int mfs_aligned = (max_frag_size & ~FRAG_SIZE_ROUND_MASK);
     const int div = len / mfs_aligned;
     const int mod = len % mfs_aligned;
 
     if (div > 0 && mod > 0 && mod < mfs_aligned * 3 / 4)
     {
         return min_int(mfs_aligned, (max_frag_size - ((max_frag_size - mod) / (div + 1))
                                      + FRAG_SIZE_ROUND_MASK) & ~FRAG_SIZE_ROUND_MASK);
     }
     else
     {
         return mfs_aligned;
     }
6fbf66fa
 }
 
 /* process an outgoing datagram, possibly breaking it up into fragments */
 void
81d882d5
 fragment_outgoing(struct fragment_master *f, struct buffer *buf,
                   const struct frame *frame)
6fbf66fa
 {
81d882d5
     const char *errmsg = NULL;
     if (buf->len > 0)
6fbf66fa
     {
81d882d5
         /* The outgoing buffer should be empty so we can put new data in it */
         if (f->outgoing.len)
         {
             msg(D_FRAG_ERRORS, "FRAG: outgoing buffer is not empty, len=[%d,%d]",
                 buf->len, f->outgoing.len);
         }
         if (buf->len > PAYLOAD_SIZE_DYNAMIC(frame)) /* should we fragment? */
         {
             /*
              * Send the datagram as a series of 2 or more fragments.
              */
             f->outgoing_frag_size = optimal_fragment_size(buf->len, PAYLOAD_SIZE_DYNAMIC(frame));
             if (buf->len > f->outgoing_frag_size * MAX_FRAGS)
             {
                 FRAG_ERR("too many fragments would be required to send datagram");
             }
             ASSERT(buf_init(&f->outgoing, FRAME_HEADROOM(frame)));
             ASSERT(buf_copy(&f->outgoing, buf));
             f->outgoing_seq_id = modulo_add(f->outgoing_seq_id, 1, N_SEQ_ID);
             f->outgoing_frag_id = 0;
             buf->len = 0;
             ASSERT(fragment_ready_to_send(f, buf, frame));
         }
         else
         {
             /*
              * Send the datagram whole.
              */
             fragment_prepend_flags(buf,
                                    FRAG_WHOLE,
                                    0,
                                    0,
                                    0);
         }
6fbf66fa
     }
81d882d5
     return;
 
 error:
     if (errmsg)
     {
         msg(D_FRAG_ERRORS, "FRAG_OUT error, len=%d frag_size=%d MAX_FRAGS=%d: %s",
             buf->len, f->outgoing_frag_size, MAX_FRAGS, errmsg);
     }
     buf->len = 0;
     return;
6fbf66fa
 }
 
 /* return true (and set buf) if we have an outgoing fragment which is ready to send */
 bool
81d882d5
 fragment_ready_to_send(struct fragment_master *f, struct buffer *buf,
                        const struct frame *frame)
6fbf66fa
 {
81d882d5
     if (fragment_outgoing_defined(f))
     {
         /* get fragment size, and determine if it is the last fragment */
         int size = f->outgoing_frag_size;
         int last = false;
         if (f->outgoing.len <= size)
         {
             size = f->outgoing.len;
             last = true;
         }
 
         /* initialize return buffer */
         *buf = f->outgoing_return;
         ASSERT(buf_init(buf, FRAME_HEADROOM(frame)));
         ASSERT(buf_copy_n(buf, &f->outgoing, size));
 
         /* fragment flags differ based on whether or not we are sending the last fragment */
         fragment_prepend_flags(buf,
                                last ? FRAG_YES_LAST : FRAG_YES_NOTLAST,
                                f->outgoing_seq_id,
                                f->outgoing_frag_id++,
                                f->outgoing_frag_size);
 
         ASSERT(!last || !f->outgoing.len); /* outgoing buffer length should be zero after last fragment sent */
 
         return true;
     }
     else
6fbf66fa
     {
81d882d5
         return false;
6fbf66fa
     }
 }
 
 static void
81d882d5
 fragment_ttl_reap(struct fragment_master *f)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < N_FRAG_BUF; ++i)
6fbf66fa
     {
81d882d5
         struct fragment *frag = &f->incoming.fragments[i];
         if (frag->defined && frag->timestamp + FRAG_TTL_SEC <= now)
         {
             msg(D_FRAG_ERRORS, "FRAG TTL expired i=%d", i);
             frag->defined = false;
         }
6fbf66fa
     }
 }
 
 /* called every FRAG_WAKEUP_INTERVAL seconds */
 void
81d882d5
 fragment_wakeup(struct fragment_master *f, struct frame *frame)
6fbf66fa
 {
81d882d5
     /* delete fragments with expired TTLs */
     fragment_ttl_reap(f);
6fbf66fa
 }
 
81d882d5
 #else  /* ifdef ENABLE_FRAGMENT */
 static void
4cd4899e
 dummy(void)
 {
81d882d5
 }
 #endif /* ifdef ENABLE_FRAGMENT */