1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  *
  26  * Copyright 2017 Jason King.
  27  * Copyright 2017 Joyent, Inc.
  28  */
  29 
  30 #include <pthread.h>
  31 #include <errno.h>
  32 #include <port.h>
  33 #include <string.h>
  34 #include <sys/types.h>
  35 #include <sys/debug.h>
  36 #include <umem.h>
  37 #include <stddef.h>
  38 #include <locale.h>
  39 #include <libuutil.h>
  40 #include <ipsec_util.h>
  41 #include <note.h>
  42 #include <ucontext.h>
  43 
  44 #include "defs.h"
  45 #include "timer.h"
  46 
  47 typedef struct tevent_s {
  48         uu_list_node_t  node;
  49 
  50         hrtime_t        time;   /* When does the event go off */
  51         te_event_t      type;   /* Event type */
  52 
  53         tevent_cb_fn    fn;
  54         void            *arg;
  55 } tevent_t;
  56 
  57 static uu_list_pool_t           *timer_pools;
  58 static pthread_key_t            timer_key = PTHREAD_ONCE_KEY_NP;
  59 static umem_cache_t             *evt_cache;
  60 
  61 static int te_compare(const void *, const void *, void *);
  62 
  63 static tevent_t *tevent_alloc(te_event_t, hrtime_t, tevent_cb_fn, void *);
  64 static void timer_fini(void *);
  65 static void tevent_free(tevent_t *);
  66 static int evt_ctor(void *, void *, int);
  67 static void evt_dtor(void *, void *);
  68 static const char *te_str(te_event_t);
  69 
  70 void
  71 ike_timer_init(void)
  72 {
  73 
  74         uint32_t flg = 0;
  75 
  76 #ifdef DEBUG
  77         flg |= UU_LIST_POOL_DEBUG;
  78 #endif
  79 
  80         /* better be single threaded here! */
  81         ASSERT(pthread_self() == 1);
  82 
  83         timer_pools = uu_list_pool_create("timer_event_list",
  84             sizeof (tevent_t), offsetof(tevent_t, node), te_compare,
  85             flg);
  86         if (timer_pools == NULL)
  87                 errx(EXIT_FAILURE, "Unable to allocate memory for timer event "
  88                     "lists");
  89 
  90         evt_cache = umem_cache_create("timer events", sizeof (tevent_t), 0,
  91             evt_ctor, evt_dtor, NULL, NULL, NULL, 0);
  92         if (evt_cache == NULL)
  93                 errx(EXIT_FAILURE, "Unable to allocate memory for timer event "
  94                     "entries");
  95 
  96         PTH(pthread_key_create_once_np(&timer_key, timer_fini));
  97 }
  98 
  99 /*
 100  * Called for each new worker thread
 101  */
 102 void
 103 ike_timer_thread_init(void)
 104 {
 105         uu_list_t *list = NULL;
 106         uint32_t flg = UU_LIST_SORTED;
 107 
 108 #ifdef DEBUG
 109         flg |= UU_LIST_DEBUG;
 110 #endif
 111 
 112         if ((list = uu_list_create(timer_pools, NULL, flg)) == NULL)
 113                 errx(EXIT_FAILURE, "Unable to allocate timer event lists");
 114 
 115         PTH(pthread_setspecific(timer_key, list));
 116 }
 117 
 118 static int dispatch_cb(void *, void *);
 119 
 120 static inline uu_list_t *
 121 timer_list(void)
 122 {
 123         return (pthread_getspecific(timer_key));
 124 }
 125 
 126 static inline tevent_t *
 127 timer_head(void)
 128 {
 129         return (uu_list_first(timer_list()));
 130 }
 131 
 132 struct dispatch_arg {
 133         bunyan_logger_t *log;
 134         hrtime_t now;
 135 };
 136 
 137 void
 138 process_timer(timespec_t *next_time, bunyan_logger_t *tlog)
 139 {
 140         struct dispatch_arg data = { 0 };
 141         tevent_t *te;
 142         hrtime_t now;
 143 
 144         ASSERT(timer_is_init);
 145         ASSERT(timer_thr_is_init);
 146 
 147         (void) bunyan_trace(tlog, "Checking for timeout events",
 148             BUNYAN_T_END);
 149 
 150         /*CONSTCOND*/
 151         while (1) {
 152                 /*
 153                  * since dispatching takes a non-zero amount of time, it is
 154                  * possible that by the time we're done dispatching, new
 155                  * events are due.  Eventually the list will either drain
 156                  * or we are left with an event far enough in the future
 157                  * that it's still pending after we're done dispatching
 158                  */
 159                 now = gethrtime();
 160 
 161                 if ((te = timer_head()) == NULL) {
 162                         next_time->tv_sec = 0;
 163                         next_time->tv_nsec = 0;
 164                         (void) bunyan_trace(tlog, "Event list empty; returning",
 165                             BUNYAN_T_END);
 166                         return;
 167                 }
 168 
 169                 if (te->time > now) {
 170                         /* no events to run */
 171                         hrtime_t delta = te->time - now;
 172 
 173                         next_time->tv_sec = NSEC2SEC(delta);
 174                         next_time->tv_nsec = delta % (hrtime_t)NANOSEC;
 175                         (void) bunyan_debug(tlog,
 176                             "No events ready to dispatch",
 177                             BUNYAN_T_UINT32, "events queued",
 178                             (uint32_t)uu_list_numnodes(timer_list()),
 179                             BUNYAN_T_UINT64, "ns until next event", delta,
 180                             BUNYAN_T_END);
 181                         return;
 182                 }
 183 
 184                 /* dispatch timeouts */
 185                 data.now = now;
 186                 data.log = tlog;
 187                 uu_list_walk(timer_list(), dispatch_cb, &data, UU_WALK_ROBUST);
 188         }
 189 }
 190 
 191 static int
 192 dispatch_cb(void *elem, void *arg)
 193 {
 194         tevent_t *te = (tevent_t *)elem;
 195         struct dispatch_arg *data = arg;
 196 
 197         if (te->time > data->now)
 198                 return (UU_WALK_DONE);
 199 
 200         char buf[128] = { 0 };
 201         (void) addrtosymstr(te->fn, buf, sizeof (buf));
 202         (void) bunyan_debug(data->log, "Dispatching timer event",
 203             BUNYAN_T_STRING, "event", te_str(te->type),
 204             BUNYAN_T_UINT32, "event num", (uint32_t)te->type,
 205             BUNYAN_T_POINTER, "fn", te->fn,
 206             BUNYAN_T_STRING, "fnname", buf,
 207             BUNYAN_T_POINTER, "arg", te->arg,
 208             BUNYAN_T_END);
 209 
 210         te->fn(te->type, te->arg);
 211         uu_list_remove(timer_list(), elem);
 212         tevent_free(te);
 213 
 214         return (UU_WALK_NEXT);
 215 }
 216 
 217 typedef struct cancel_arg_s {
 218         bunyan_logger_t *log;
 219         te_event_t      type;
 220         void            *arg;
 221         size_t          n;
 222 } cancel_arg_t;
 223 
 224 static int cancel_cb(void *, void *);
 225 
 226 int
 227 cancel_timeout(te_event_t type, void *arg, bunyan_logger_t *tlog)
 228 {
 229         cancel_arg_t carg;
 230 
 231         ASSERT(timer_is_init);
 232         ASSERT(timer_thr_is_init);
 233 
 234         (void) bunyan_trace(tlog, "Cancelling timeouts",
 235             BUNYAN_T_STRING, "event", te_str(type),
 236             BUNYAN_T_UINT32, "event num", (uint32_t)type,
 237             BUNYAN_T_POINTER, "arg", arg,
 238             BUNYAN_T_END);
 239 
 240         carg.log = tlog;
 241         carg.type = type;
 242         carg.arg = arg;
 243         carg.n = 0;
 244 
 245         (void) uu_list_walk(timer_list(), cancel_cb, &carg, UU_WALK_ROBUST);
 246         return (carg.n);
 247 }
 248 
 249 static int
 250 cancel_cb(void *elem, void *arg)
 251 {
 252         tevent_t *te = (tevent_t *)elem;
 253         cancel_arg_t *carg = (cancel_arg_t *)arg;
 254 
 255         if (carg->type == TE_ANY ||
 256             ((carg->type == te->type) && (carg->arg == te->arg))) {
 257                 uu_list_remove(timer_list(), elem);
 258 
 259                 (void) bunyan_debug(carg->log, "Removed timeout",
 260                     BUNYAN_T_STRING, "event", te_str(te->type),
 261                     BUNYAN_T_UINT32, "event num", (uint32_t)te->type,
 262                     BUNYAN_T_POINTER, "arg", te->arg,
 263                     BUNYAN_T_END);
 264 
 265                 tevent_free(te);
 266                 carg->n++;
 267         }
 268         return (UU_WALK_NEXT);
 269 }
 270 
 271 boolean_t
 272 schedule_timeout(te_event_t type, tevent_cb_fn fn, void *arg, hrtime_t val)
 273 {
 274         uu_list_t *list = timer_list();
 275         tevent_t *te = tevent_alloc(type, val, fn, arg);
 276         uu_list_index_t idx;
 277 
 278         ASSERT(timer_is_init);
 279         ASSERT(timer_thr_is_init);
 280 
 281         VERIFY(te != TE_ANY);
 282 
 283         if ((te = tevent_alloc(type, val, fn, arg)) == NULL)
 284                 return (B_FALSE);
 285 
 286         (void) uu_list_find(list, te, NULL, &idx);
 287         uu_list_insert(list, te, idx);
 288         return (B_TRUE);
 289 }
 290 
 291 static int
 292 te_compare(const void *la, const void *ra, void *dummy)
 293 {
 294         NOTE(ARGUNUSED(dummy))
 295         const tevent_t *l = (tevent_t *)la;
 296         const tevent_t *r = (tevent_t *)ra;
 297 
 298         if (l->time > r->time)
 299                 return (1);
 300         if (l->time < r->time)
 301                 return (-1);
 302         return (0);
 303 }
 304 
 305 static tevent_t *
 306 tevent_alloc(te_event_t type, hrtime_t dur, tevent_cb_fn fn, void *arg)
 307 {
 308         tevent_t *te = umem_cache_alloc(evt_cache, UMEM_DEFAULT);
 309 
 310         if (te == NULL)
 311                 return (NULL);
 312 
 313         te->time = gethrtime() + dur;
 314         te->type = type;
 315         te->fn = fn;
 316         te->arg = arg;
 317 
 318         return (te);
 319 }
 320 
 321 static void
 322 timer_fini(void *arg)
 323 {
 324         uu_list_t *list = arg;
 325 
 326         (void) cancel_timeout(TE_ANY, NULL, log);
 327         uu_list_destroy(list);
 328 }
 329 
 330 static void
 331 tevent_free(tevent_t *te)
 332 {
 333         if (te == NULL)
 334                 return;
 335 
 336         evt_dtor(te, NULL);
 337         evt_ctor(te, NULL, 0);
 338         umem_cache_free(evt_cache, te);
 339 }
 340 
 341 static int
 342 evt_ctor(void *buf, void *cb, int flags)
 343 {
 344         tevent_t *te = (tevent_t *)buf;
 345 
 346         (void) memset(te, 0, sizeof (*te));
 347         uu_list_node_init(buf, &te->node, timer_pools);
 348         return (0);
 349 }
 350 
 351 static void
 352 evt_dtor(void *buf, void *cb)
 353 {
 354         tevent_t *te = (tevent_t *)buf;
 355 
 356         uu_list_node_fini(buf, &te->node, timer_pools);
 357 }
 358 
 359 static const char *
 360 te_str(te_event_t te)
 361 {
 362 #define STR(x) case x: return (#x)
 363         switch (te) {
 364         STR(TE_TEST);
 365         STR(TE_ANY);
 366         STR(TE_SA_EXPIRE);
 367         STR(TE_COOKIE_GEN);
 368         STR(TE_TRANSMIT);
 369         STR(TE_PFKEY);
 370         default:
 371                 return ("UNKNOWN");
 372         }
 373 }