/* * 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) 2002-2018 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" #if P2MP_SERVER #include "buffer.h" #include "misc.h" #include "crypto.h" #include "schedule.h" #include "memdbg.h" #ifdef SCHEDULE_TEST struct status { int sru; int ins; int coll; int lsteps; }; static struct status z; #endif #ifdef ENABLE_DEBUG static void schedule_entry_debug_info(const char *caller, const struct schedule_entry *e) { struct gc_arena gc = gc_new(); if (e) { dmsg(D_SCHEDULER, "SCHEDULE: %s wakeup=[%s] pri=%u", caller, tv_string_abs(&e->tv, &gc), e->pri); } else { dmsg(D_SCHEDULER, "SCHEDULE: %s NULL", caller); } gc_free(&gc); } #endif static inline void schedule_set_pri(struct schedule_entry *e) { e->pri = random(); if (e->pri < 1) { e->pri = 1; } } /* This is the master key comparison routine. A key is * simply a struct timeval containing the absolute time for * an event. The unique treap priority (pri) is used to ensure * that keys do not collide. */ static inline int schedule_entry_compare(const struct schedule_entry *e1, const struct schedule_entry *e2) { if (e1->tv.tv_sec < e2->tv.tv_sec) { return -1; } else if (e1->tv.tv_sec > e2->tv.tv_sec) { return 1; } else { if (e1->tv.tv_usec < e2->tv.tv_usec) { return -1; } else if (e1->tv.tv_usec > e2->tv.tv_usec) { return 1; } else { if (e1->pri < e2->pri) { return -1; } else if (e1->pri > e2->pri) { return 1; } else { return 0; } } } } /* * Detach a btree node from its parent */ static inline void schedule_detach_parent(struct schedule *s, struct schedule_entry *e) { if (e) { if (e->parent) { if (e->parent->lt == e) { e->parent->lt = NULL; } else if (e->parent->gt == e) { e->parent->gt = NULL; } else { /* parent <-> child linkage is corrupted */ ASSERT(0); } e->parent = NULL; } else { if (s->root == e) /* last element deleted, tree is empty */ { s->root = NULL; } } } } /* * * Given a binary search tree, move a node toward the root * while still maintaining the correct ordering relationships * within the tree. This function is the workhorse * of the tree balancer. * * This code will break on key collisions, which shouldn't * happen because the treap priority is considered part of the key * and is guaranteed to be unique. */ static void schedule_rotate_up(struct schedule *s, struct schedule_entry *e) { if (e && e->parent) { struct schedule_entry *lt = e->lt; struct schedule_entry *gt = e->gt; struct schedule_entry *p = e->parent; struct schedule_entry *gp = p->parent; if (gp) /* if grandparent exists, modify its child link */ { if (gp->gt == p) { gp->gt = e; } else if (gp->lt == p) { gp->lt = e; } else { ASSERT(0); } } else /* no grandparent, now we are the root */ { s->root = e; } /* grandparent is now our parent */ e->parent = gp; /* parent is now our child */ p->parent = e; /* reorient former parent's links * to reflect new position in the tree */ if (p->gt == e) { e->lt = p; p->gt = lt; if (lt) { lt->parent = p; } } else if (p->lt == e) { e->gt = p; p->lt = gt; if (gt) { gt->parent = p; } } else { /* parent <-> child linkage is corrupted */ ASSERT(0); } #ifdef SCHEDULE_TEST ++z.sru; #endif } } /* * This is the treap deletion algorithm: * * Rotate lesser-priority children up in the tree * until we are childless. Then delete. */ void schedule_remove_node(struct schedule *s, struct schedule_entry *e) { while (e->lt || e->gt) { if (e->lt) { if (e->gt) { if (e->lt->pri < e->gt->pri) { schedule_rotate_up(s, e->lt); } else { schedule_rotate_up(s, e->gt); } } else { schedule_rotate_up(s, e->lt); } } else if (e->gt) { schedule_rotate_up(s, e->gt); } } schedule_detach_parent(s, e); e->pri = 0; } /* * Trivially add a node to a binary search tree without * regard for balance. */ static void schedule_insert(struct schedule *s, struct schedule_entry *e) { struct schedule_entry *c = s->root; while (true) { const int comp = schedule_entry_compare(e, c); #ifdef SCHEDULE_TEST ++z.ins; #endif if (comp == -1) { if (c->lt) { c = c->lt; continue; } else { c->lt = e; e->parent = c; break; } } else if (comp == 1) { if (c->gt) { c = c->gt; continue; } else { c->gt = e; e->parent = c; break; } } else { /* rare key/priority collision -- no big deal, * just choose another priority and retry */ #ifdef SCHEDULE_TEST ++z.coll; #endif schedule_set_pri(e); /* msg (M_INFO, "PRI COLLISION pri=%u", e->pri); */ c = s->root; continue; } } } /* * Given an element, remove it from the btree if it's already * there and re-insert it based on its current key. */ void schedule_add_modify(struct schedule *s, struct schedule_entry *e) { #ifdef ENABLE_DEBUG if (check_debug_level(D_SCHEDULER)) { schedule_entry_debug_info("schedule_add_modify", e); } #endif /* already in tree, remove */ if (IN_TREE(e)) { schedule_remove_node(s, e); } /* set random priority */ schedule_set_pri(e); if (s->root) { schedule_insert(s, e); /* trivial insert into tree */ } else { s->root = e; /* tree was empty, we are the first element */ } /* This is the magic of the randomized treap algorithm which * keeps the tree balanced. Move the node up the tree until * its own priority is greater than that of its parent */ while (e->parent && e->parent->pri > e->pri) { schedule_rotate_up(s, e); } } /* * Find the earliest event to be scheduled */ struct schedule_entry * schedule_find_least(struct schedule_entry *e) { if (e) { while (e->lt) { #ifdef SCHEDULE_TEST ++z.lsteps; #endif e = e->lt; } } #ifdef ENABLE_DEBUG if (check_debug_level(D_SCHEDULER)) { schedule_entry_debug_info("schedule_find_least", e); } #endif return e; } /* * Public functions below this point */ struct schedule * schedule_init(void) { struct schedule *s; ALLOC_OBJ_CLEAR(s, struct schedule); return s; } void schedule_free(struct schedule *s) { free(s); } void schedule_remove_entry(struct schedule *s, struct schedule_entry *e) { s->earliest_wakeup = NULL; /* invalidate cache */ schedule_remove_node(s, e); } /* * Debug functions below this point */ #ifdef SCHEDULE_TEST static inline struct schedule_entry * schedule_find_earliest_wakeup(struct schedule *s) { return schedule_find_least(s->root); } /* * Recursively check that the treap (btree) is * internally consistent. */ int schedule_debug_entry(const struct schedule_entry *e, int depth, int *count, struct timeval *least, const struct timeval *min, const struct timeval *max) { struct gc_arena gc = gc_new(); int maxdepth = depth; if (e) { int d; ASSERT(e != e->lt); ASSERT(e != e->gt); ASSERT(e != e->parent); ASSERT(!e->parent || e->parent != e->lt); ASSERT(!e->parent || e->parent != e->gt); ASSERT(!e->lt || e->lt != e->gt); if (e->lt) { ASSERT(e->lt->parent == e); ASSERT(schedule_entry_compare(e->lt, e) == -1); ASSERT(e->lt->pri >= e->pri); } if (e->gt) { ASSERT(e->gt->parent == e); ASSERT(schedule_entry_compare(e->gt, e)); ASSERT(e->gt->pri >= e->pri); } ASSERT(tv_le(min, &e->tv)); ASSERT(tv_le(&e->tv, max)); if (count) { ++(*count); } if (least && tv_lt(&e->tv, least)) { *least = e->tv; } d = schedule_debug_entry(e->lt, depth+1, count, least, min, &e->tv); if (d > maxdepth) { maxdepth = d; } d = schedule_debug_entry(e->gt, depth+1, count, least, &e->tv, max); if (d > maxdepth) { maxdepth = d; } } gc_free(&gc); return maxdepth; } int schedule_debug(struct schedule *s, int *count, struct timeval *least) { struct timeval min; struct timeval max; min.tv_sec = 0; min.tv_usec = 0; max.tv_sec = 0x7FFFFFFF; max.tv_usec = 0x7FFFFFFF; if (s->root) { ASSERT(s->root->parent == NULL); } return schedule_debug_entry(s->root, 0, count, least, &min, &max); } #if 1 void tv_randomize(struct timeval *tv) { tv->tv_sec += random() % 100; tv->tv_usec = random() % 100; } #else /* if 1 */ void tv_randomize(struct timeval *tv) { struct gc_arena gc = gc_new(); long int choice = get_random(); if ((choice & 0xFF) == 0) { tv->tv_usec += ((choice >> 8) & 0xFF); } else { prng_bytes((uint8_t *)tv, sizeof(struct timeval)); } gc_free(&gc); } #endif /* if 1 */ void schedule_verify(struct schedule *s) { struct gc_arena gc = gc_new(); struct timeval least; int count; int maxlev; struct schedule_entry *e; const struct status zz = z; least.tv_sec = least.tv_usec = 0x7FFFFFFF; count = 0; maxlev = schedule_debug(s, &count, &least); e = schedule_find_earliest_wakeup(s); if (e) { printf("Verification Phase count=%d maxlev=%d sru=%d ins=%d coll=%d ls=%d l=%s", count, maxlev, zz.sru, zz.ins, zz.coll, zz.lsteps, tv_string(&e->tv, &gc)); if (!tv_eq(&least, &e->tv)) { printf(" [COMPUTED DIFFERENT MIN VALUES!]"); } printf("\n"); } CLEAR(z); gc_free(&gc); } void schedule_randomize_array(struct schedule_entry **array, int size) { int i; for (i = 0; i < size; ++i) { const int src = get_random() % size; struct schedule_entry *tmp = array [i]; if (i != src) { array [i] = array [src]; array [src] = tmp; } } } void schedule_print_work(struct schedule_entry *e, int indent) { struct gc_arena gc = gc_new(); int i; for (i = 0; i < indent; ++i) { printf(" "); } if (e) { printf("%s [%u] e=" ptr_format ", p=" ptr_format " lt=" ptr_format " gt=" ptr_format "\n", tv_string(&e->tv, &gc), e->pri, (ptr_type)e, (ptr_type)e->parent, (ptr_type)e->lt, (ptr_type)e->gt); schedule_print_work(e->lt, indent+1); schedule_print_work(e->gt, indent+1); } else { printf("NULL\n"); } gc_free(&gc); } void schedule_print(struct schedule *s) { printf("*************************\n"); schedule_print_work(s->root, 0); } void schedule_test(void) { struct gc_arena gc = gc_new(); int n = 1000; int n_mod = 25; int i, j; struct schedule_entry **array; struct schedule *s = schedule_init(); struct schedule_entry *e; CLEAR(z); ALLOC_ARRAY(array, struct schedule_entry *, n); printf("Creation/Insertion Phase\n"); for (i = 0; i < n; ++i) { ALLOC_OBJ_CLEAR(array[i], struct schedule_entry); tv_randomize(&array[i]->tv); /*schedule_print (s);*/ /*schedule_verify (s);*/ schedule_add_modify(s, array[i]); } schedule_randomize_array(array, n); /*schedule_print (s);*/ schedule_verify(s); for (j = 1; j <= n_mod; ++j) { printf("Modification Phase Pass %d\n", j); for (i = 0; i < n; ++i) { e = schedule_find_earliest_wakeup(s); /*printf ("BEFORE %s\n", tv_string (&e->tv, &gc));*/ tv_randomize(&e->tv); /*printf ("AFTER %s\n", tv_string (&e->tv, &gc));*/ schedule_add_modify(s, e); /*schedule_verify (s);*/ /*schedule_print (s);*/ } schedule_verify(s); /*schedule_print (s);*/ } /*printf ("INS=%d\n", z.ins);*/ while ((e = schedule_find_earliest_wakeup(s))) { schedule_remove_node(s, e); /*schedule_verify (s);*/ } schedule_verify(s); printf("S->ROOT is %s\n", s->root ? "NOT NULL" : "NULL"); for (i = 0; i < n; ++i) { free(array[i]); } free(array); free(s); gc_free(&gc); } #endif /* ifdef SCHEDULE_TEST */ #endif /* if P2MP_SERVER */