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 }