blob: 4f8bd0f21b9ae7812f4cfbfa7272d0c4f0d618ee [file] [log] [blame]
/*
* 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.
*
* Copyright (C) 2002-2021 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"
#ifdef ENABLE_FRAGMENT
#include "crypto.h"
#include "misc.h"
#include "fragment.h"
#include "integer.h"
#include "memdbg.h"
#define FRAG_ERR(s) { errmsg = s; goto error; }
static void
fragment_list_buf_init(struct fragment_list *list, const struct frame *frame)
{
int i;
for (i = 0; i < N_FRAG_BUF; ++i)
{
list->fragments[i].buf = alloc_buf(BUF_SIZE(frame));
}
}
static void
fragment_list_buf_free(struct fragment_list *list)
{
int i;
for (i = 0; i < N_FRAG_BUF; ++i)
{
free_buf(&list->fragments[i].buf);
}
}
/*
* Given a sequence ID number, get a fragment buffer. Use a sliding window,
* similar to packet_id code.
*/
static struct fragment *
fragment_list_get_buf(struct fragment_list *list, int seq_id)
{
int diff;
if (abs(diff = modulo_subtract(seq_id, list->seq_id, N_SEQ_ID)) >= N_FRAG_BUF)
{
int i;
for (i = 0; i < N_FRAG_BUF; ++i)
{
list->fragments[i].defined = false;
}
list->index = 0;
list->seq_id = seq_id;
diff = 0;
}
while (diff > 0)
{
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;
}
return &list->fragments[modulo_add(list->index, diff, N_FRAG_BUF)];
}
struct fragment_master *
fragment_init(struct frame *frame)
{
struct fragment_master *ret;
/* code that initializes other parts of
* fragment_master assume an initial CLEAR */
ALLOC_OBJ_CLEAR(ret, struct fragment_master);
/* add in the size of our contribution to the expanded frame size */
frame_add_to_extra_frame(frame, sizeof(fragment_header_type));
/*
* 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);
event_timeout_init(&ret->wakeup, FRAG_WAKEUP_INTERVAL, now);
return ret;
}
void
fragment_free(struct fragment_master *f)
{
fragment_list_buf_free(&f->incoming);
free_buf(&f->outgoing);
free_buf(&f->outgoing_return);
free(f);
}
void
fragment_frame_init(struct fragment_master *f, const struct frame *frame)
{
fragment_list_buf_init(&f->incoming, frame);
f->outgoing = alloc_buf(BUF_SIZE(frame));
f->outgoing_return = alloc_buf(BUF_SIZE(frame));
}
/*
* 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
fragment_incoming(struct fragment_master *f, struct buffer *buf,
const struct frame *frame)
{
const char *errmsg = NULL;
fragment_header_type flags = 0;
int frag_type = 0;
if (buf->len > 0)
{
/* 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);
/* get fragment type from flags */
frag_type = ((flags >> FRAG_TYPE_SHIFT) & FRAG_TYPE_MASK);
#if 0
/*
* 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)
{
}
#endif
/* 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("spurious 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? */
if (!frag->defined || frag->max_frag_size != size)
{
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");
}
}
return;
error:
if (errmsg)
{
msg(D_FRAG_ERRORS, "FRAG_IN error flags=" fragment_header_format ": %s", flags, errmsg);
}
buf->len = 0;
return;
}
/* pack fragment parms into a uint32_t and prepend to buffer */
static void
fragment_prepend_flags(struct buffer *buf,
int type,
int seq_id,
int frag_id,
int frag_size)
{
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);
if (type == FRAG_WHOLE || type == FRAG_YES_NOTLAST)
{
/*
* 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);
}
else
{
flags |= (((frag_size >> FRAG_SIZE_ROUND_SHIFT) & FRAG_SIZE_MASK) << FRAG_SIZE_SHIFT);
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);
}
flags = hton_fragment_header_type(flags);
ASSERT(buf_write_prepend(buf, &flags, sizeof(flags)));
}
/*
* 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
optimal_fragment_size(int len, int max_frag_size)
{
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;
}
}
/* process an outgoing datagram, possibly breaking it up into fragments */
void
fragment_outgoing(struct fragment_master *f, struct buffer *buf,
const struct frame *frame)
{
const char *errmsg = NULL;
if (buf->len > 0)
{
/* 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);
}
}
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;
}
/* return true (and set buf) if we have an outgoing fragment which is ready to send */
bool
fragment_ready_to_send(struct fragment_master *f, struct buffer *buf,
const struct frame *frame)
{
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
{
return false;
}
}
static void
fragment_ttl_reap(struct fragment_master *f)
{
int i;
for (i = 0; i < N_FRAG_BUF; ++i)
{
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;
}
}
}
/* called every FRAG_WAKEUP_INTERVAL seconds */
void
fragment_wakeup(struct fragment_master *f, struct frame *frame)
{
/* delete fragments with expired TTLs */
fragment_ttl_reap(f);
}
#else /* ifdef ENABLE_FRAGMENT */
static void
dummy(void)
{
}
#endif /* ifdef ENABLE_FRAGMENT */