1 /*-
2  * Copyright (c) 2015-2016 Varnish Software AS
3  * Copyright 2017-2019 UPLEX - Nils Goroll Systemoptimierung
4  * All rights reserved.
5  *
6  * Authors: Dridi Boukelmoune <dridi.boukelmoune@gmail.com>
7  *	    Nils Goroll <nils.goroll@uplex.de>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  * Support for SRV records.
31  *
32  * For now, this code is basically a copy of large extents of vmod_dynamic.c as
33  * a first iteration. Consolidation is left for later.
34  */
35 
36 #include "config.h"
37 
38 #include <arpa/inet.h>
39 
40 #include <sys/socket.h>
41 #include <sys/types.h>
42 
43 #include <errno.h>
44 #include <netdb.h>
45 #include <pthread.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 
50 #include <cache/cache.h>
51 
52 #include <vtim.h>
53 #include <vrnd.h>
54 
55 #include "vcc_dynamic_if.h"
56 #include "dyn_resolver.h"
57 #include "vmod_dynamic.h"
58 #include "vmb.h"
59 
60 #define LOG(ctx, slt, srv, fmt, ...)				\
61 	do {							\
62 		if ((ctx)->vsl != NULL)				\
63 			VSLb((ctx)->vsl, slt,			\
64 			    "vmod-dynamic: %s %s %s " fmt,	\
65 			    (srv)->obj->vcl_conf,		\
66 			    (srv)->obj->vcl_name,		\
67 			    (srv)->service, __VA_ARGS__);	\
68 		else						\
69 			VSL(slt, 0,				\
70 			    "vmod-dynamic: %s %s %s " fmt,	\
71 			    (srv)->obj->vcl_conf,		\
72 			    (srv)->obj->vcl_name,		\
73 			    (srv)->service, __VA_ARGS__);	\
74 	} while (0)
75 
76 #define DBG(ctx, srv, fmt, ...)						\
77 	do {								\
78 		if ((srv)->obj->debug)					\
79 			LOG(ctx, SLT_Debug, srv, fmt, __VA_ARGS__);	\
80 	} while (0)
81 
82 static VCL_BACKEND v_matchproto_(vdi_resolve_f)
83 service_resolve(VRT_CTX, VCL_BACKEND);
84 static VCL_BOOL v_matchproto_(vdi_healthy_f)
85 service_healthy(VRT_CTX, VCL_BACKEND, VCL_TIME *);
86 
87 static const struct vdi_methods vmod_dynamic_service_methods[1] = {{
88 	.magic =	VDI_METHODS_MAGIC,
89 	.type =		"dynamic service",
90 	.healthy =	service_healthy,
91 	.resolve =	service_resolve
92 }};
93 
94 /*--------------------------------------------------------------------
95  * Service director implementation
96  */
97 
98 /* select healthy backends */
99 struct backend_select {
100 	VCL_BACKEND	d;
101 	uint32_t	w;
102 };
103 
v_matchproto_(vdi_resolve_f)104 static VCL_BACKEND v_matchproto_(vdi_resolve_f)
105 service_resolve(VRT_CTX, VCL_BACKEND d)
106 {
107 	struct dynamic_service *srv;
108 	const struct service_prios *prios;
109 	const struct service_prio *p;
110 	const struct service_target *t;
111 	double deadline;
112 	int ret;
113 
114 	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
115 	CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC);
116 	CAST_OBJ_NOTNULL(srv, d->priv, DYNAMIC_SERVICE_MAGIC);
117 
118 	Lck_Lock(&srv->mtx);
119 
120 	if (srv->status < DYNAMIC_ST_ACTIVE) {
121 		deadline = VTIM_real() + srv->obj->first_lookup_tmo;
122 		ret = Lck_CondWait(&srv->resolve, &srv->mtx, deadline);
123 		assert(ret == 0 || ret == ETIMEDOUT);
124 	}
125 
126 	if (srv->status > DYNAMIC_ST_ACTIVE) {
127 		Lck_Unlock(&srv->mtx);
128 		return (NULL);
129 	}
130 	Lck_Unlock(&srv->mtx);
131 
132 	VRMB();
133 	prios = srv->prios;
134 
135 	if (prios == NULL)
136 		return (NULL);
137 
138 	struct backend_select	h[prios->max_targets];
139 	unsigned i, n, w;
140 	long r;
141 
142 	memset(h, 0, sizeof h);
143 
144 	n = w = 0;
145 	VTAILQ_FOREACH(p, &prios->head, list) {
146 		CHECK_OBJ_NOTNULL(p, SERVICE_PRIO_MAGIC);
147 		n = w = 0;
148 		VTAILQ_FOREACH(t, &p->targets, list) {
149 			CHECK_OBJ_NOTNULL(t, SERVICE_TARGET_MAGIC);
150 			CHECK_OBJ_NOTNULL(t->dom, DYNAMIC_DOMAIN_MAGIC);
151 			if (! VRT_Healthy(ctx, t->dom->dir, NULL))
152 				continue;
153 			h[n].d = t->dom->dir;
154 			h[n].w = t->weight;
155 			w += t->weight;
156 			n++;
157 		}
158 		assert(n <= prios->max_targets);
159 		if (n > 0)
160 			break;
161 	}
162 	if (n == 0)
163 		return (NULL);
164 	if (n == 1)
165 		return (h[0].d);
166 	// fixup zero weight
167 	if (w == 0) {
168 		for (i = 0; i < n; i++)
169 			h[i].w = 1;
170 		w = n;
171 	}
172 	r = VRND_RandomTestable() % w;
173 	w = 0;
174 	for (i = 0; i < n; i++) {
175 		w += h[i].w;
176 		if (r < w)
177 			return (h[i].d);
178 	}
179 	WRONG("");
180 }
181 
v_matchproto_(vdi_healthy_f)182 static VCL_BOOL v_matchproto_(vdi_healthy_f)
183 service_healthy(VRT_CTX, VCL_BACKEND d, VCL_TIME *changed)
184 {
185 	struct dynamic_service *srv;
186 	const struct service_prios *prios;
187 	const struct service_prio *p;
188 	const struct service_target *t;
189 	VCL_TIME c;
190 	VCL_BOOL ret = 0;
191 
192 	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
193 	CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC);
194 	CAST_OBJ_NOTNULL(srv, d->priv, DYNAMIC_SERVICE_MAGIC);
195 
196 	VRMB();
197 	prios = srv->prios;
198 
199 	if (prios == NULL)
200 		return (0);
201 
202 	if (changed != NULL)
203 		*changed = 0;
204 
205 	VTAILQ_FOREACH(p, &prios->head, list) {
206 		CHECK_OBJ_NOTNULL(p, SERVICE_PRIO_MAGIC);
207 		VTAILQ_FOREACH(t, &p->targets, list) {
208 			CHECK_OBJ_NOTNULL(t, SERVICE_TARGET_MAGIC);
209 			CHECK_OBJ_NOTNULL(t->dom, DYNAMIC_DOMAIN_MAGIC);
210 			ret |= VRT_Healthy(ctx, t->dom->dir, &c);
211 			if (changed != NULL && c > *changed)
212 				*changed = c;
213 		}
214 	}
215 
216 	return (ret);
217 }
218 
219 /*--------------------------------------------------------------------
220  * Background job
221  */
222 
223 /* add all the dom objects an ensure they are active */
224 
225 static void
service_doms(VRT_CTX,struct vmod_dynamic_director * obj,struct service_prios * prios)226 service_doms(VRT_CTX, struct vmod_dynamic_director *obj,
227     struct service_prios *prios)
228 {
229 	struct dynamic_domain *dom;
230 	struct service_prio *p;
231 	struct service_target *t;
232 	char portbuf[6];
233 	unsigned n;
234 	int ret;
235 	double deadline;
236 
237 	CHECK_OBJ_NOTNULL(prios, SERVICE_PRIOS_MAGIC);
238 
239 	Lck_Lock(&obj->mtx);
240 	VTAILQ_FOREACH(p, &prios->head, list) {
241 		CHECK_OBJ_NOTNULL(p, SERVICE_PRIO_MAGIC);
242 		n = 0;
243 		VTAILQ_FOREACH(t, &p->targets, list) {
244 			CHECK_OBJ_NOTNULL(t, SERVICE_TARGET_MAGIC);
245 			bprintf(portbuf, "%u", t->port);
246 			t->dom = dynamic_get(ctx, obj, t->target, portbuf);
247 			AN(t->dom);
248 			t->dom->last_used = ctx->now;
249 			n++;
250 		}
251 		p->n_targets = n;
252 		if (n > prios->max_targets)
253 			prios->max_targets = n;
254 	}
255 	Lck_Unlock(&obj->mtx);
256 
257 	VTAILQ_FOREACH(p, &prios->head, list) {
258 		CHECK_OBJ_NOTNULL(p, SERVICE_PRIO_MAGIC);
259 		VTAILQ_FOREACH(t, &p->targets, list) {
260 			CHECK_OBJ_NOTNULL(t, SERVICE_TARGET_MAGIC);
261 			dom = t->dom;
262 			CHECK_OBJ_NOTNULL(dom, DYNAMIC_DOMAIN_MAGIC);
263 			if (dom->status >= DYNAMIC_ST_ACTIVE)
264 				continue;
265 			Lck_Lock(&dom->mtx);
266 			while (dom->status < DYNAMIC_ST_ACTIVE) {
267 				deadline = VTIM_real() +
268 				    dom->obj->first_lookup_tmo;
269 				ret = Lck_CondWait(&dom->resolve, &dom->mtx,
270 				    deadline);
271 				assert(ret == 0 || ret == ETIMEDOUT);
272 			}
273 			Lck_Unlock(&dom->mtx);
274 		}
275 	}
276 }
277 
278 static void
service_prios_free(struct service_prios ** priosp)279 service_prios_free(struct service_prios **priosp)
280 {
281 	struct service_prios *prios = *priosp;
282 	struct service_prio *p, *pt;
283 	struct service_target *t, *tt;
284 
285 	TAKE_OBJ_NOTNULL(prios, priosp, SERVICE_PRIOS_MAGIC);
286 	VTAILQ_FOREACH_SAFE(p, &prios->head, list, pt) {
287 		CHECK_OBJ_NOTNULL(p, SERVICE_PRIO_MAGIC);
288 		VTAILQ_FOREACH_SAFE(t, &p->targets, list, tt) {
289 			CHECK_OBJ_NOTNULL(t, SERVICE_TARGET_MAGIC);
290 			AN(t->target);
291 			free(t->target);
292 			FREE_OBJ(t);
293 		}
294 		FREE_OBJ(p);
295 	}
296 	FREE_OBJ(prios);
297 }
298 
299 static struct service_prio *
service_prio(struct service_prios * prios,uint32_t priority)300 service_prio(struct service_prios *prios, uint32_t priority)
301 {
302 	struct service_prio *p, *prio;
303 
304 	VTAILQ_FOREACH(p, &prios->head, list) {
305 		if (p->priority == priority)
306 			return (p);
307 		if (p->priority > priority)
308 			break;
309 	}
310 
311 	ALLOC_OBJ(prio, SERVICE_PRIO_MAGIC);
312 	AN(prio);
313 	prio->priority = priority;
314 	VTAILQ_INIT(&prio->targets);
315 
316 	if (p)
317 		VTAILQ_INSERT_BEFORE(p, prio, list);
318 	else
319 		VTAILQ_INSERT_TAIL(&prios->head, prio, list);
320 
321 	return (prio);
322 }
323 
324 /* we order targets for deterministic testing by target and port.
325  * being at it, we also just add the weight for identical target:port
326  */
327 
328 static int
target_cmp(const struct service_target * t,const struct srv_info * i)329 target_cmp(const struct service_target *t, const struct srv_info *i)
330 {
331 	int ret;
332 
333 	CHECK_OBJ_NOTNULL(t, SERVICE_TARGET_MAGIC);
334 	AN(i);
335 
336 	ret = strcmp(t->target, i->target);
337 	if (ret != 0)
338 		return (ret);
339 	if (t->port == i->port)
340 		return (0);
341 	return (t->port < i->port ? -1 : 1);
342 }
343 
344 static struct service_target *
service_target(struct service_prio * prio,const struct srv_info * i)345 service_target(struct service_prio *prio, const struct srv_info *i)
346 {
347 	struct service_target *t, *target;
348 	int cmp;
349 
350 	VTAILQ_FOREACH(t, &prio->targets, list) {
351 		cmp = target_cmp(t, i);
352 		if (cmp == 0)
353 			return (t);
354 		if (cmp > 0)
355 			break;
356 	}
357 
358 	ALLOC_OBJ(target, SERVICE_TARGET_MAGIC);
359 	AN(target);
360 
361 	if (t)
362 		VTAILQ_INSERT_BEFORE(t, target, list);
363 	else
364 		VTAILQ_INSERT_TAIL(&prio->targets, target, list);
365 
366 	return (target);
367 }
368 
369 static void
service_update(struct dynamic_service * srv,const struct res_cb * res,void * priv,vtim_real now)370 service_update(struct dynamic_service *srv, const struct res_cb *res,
371     void *priv, vtim_real now)
372 {
373 	struct vrt_ctx ctx;
374 	struct srv_info ibuf[1] = {{ 0 }};
375 	struct srv_info *info;
376 	void *state = NULL;
377 	vtim_dur ttl = NAN;
378 	struct service_prios *prios;
379 	struct service_prio *prio = NULL;
380 	struct service_target *target;
381 
382 	INIT_OBJ(&ctx, VRT_CTX_MAGIC);
383 	ctx.vcl = srv->obj->vcl;
384 	ctx.now = now;
385 
386 	/*
387 	 * we free any cold prio/target tree, create a new one, then swap
388 	 * the single head pointer after a membar
389 	 *
390 	 * director resolve races us
391 	 */
392 
393 	ALLOC_OBJ(prios, SERVICE_PRIOS_MAGIC);
394 	AN(prios);
395 	VTAILQ_INIT(&prios->head);
396 	while ((info = res->srv_result(ibuf, priv, &state)) != NULL) {
397 		DBG(&ctx, srv, "DNS SRV %s:%d priority %d weight %d ttl %d",
398 		    info->target, info->port, info->priority,
399 		    info->weight, info->ttl);
400 
401 		if (prio != NULL && prio->priority != info->priority)
402 			prio = NULL;
403 		if (prio == NULL)
404 			prio = service_prio(prios, info->priority);
405 
406 		target = service_target(prio, info);
407 
408 		if (target->target != NULL) {
409 			// existing
410 			assert(target->port == info->port);
411 			target->weight += info->weight;
412 			free(info->target);
413 			info->target = NULL;
414 		} else {
415 			target->port = info->port;
416 			target->weight = info->weight;
417 			// target is malloc'ed - take it
418 			target->target = info->target;
419 			info->target = NULL;
420 		}
421 
422 		if (info->ttl != 0 && (isnan(ttl) || info->ttl < ttl))
423 			ttl = info->ttl;
424 
425 		DBG(&ctx, srv, "target %s:%d priority %d weight %d ttl %f",
426 		    target->target, target->port, prio->priority,
427 		    target->weight, ttl);
428 	}
429 
430 	service_doms(&ctx, srv->obj, prios);
431 
432 	if (srv->prios_cold != NULL)
433 		service_prios_free(&srv->prios_cold);
434 
435 	VWMB();
436 	AZ(srv->prios_cold);
437 	srv->prios_cold = srv->prios;
438 	srv->prios = prios;
439 
440 	if (isnan(ttl)) {
441 		ttl = srv->obj->ttl;
442 	} else if (srv->obj->ttl_from == cfg) {
443 		ttl = srv->obj->ttl;
444 	} else if (srv->obj->ttl_from == min) {
445 		if (srv->obj->ttl < ttl)
446 			ttl = srv->obj->ttl;
447 	} else if (srv->obj->ttl_from == max) {
448 		if (srv->obj->ttl > ttl)
449 			ttl = srv->obj->ttl;
450 	} else {
451 		assert(srv->obj->ttl_from == dns);
452 	}
453 	srv->deadline = now + ttl;
454 
455 	DBG(&ctx, srv, "deadline %f ttl %f", srv->deadline, ttl);
456 }
457 
458 static void
service_timestamp(struct dynamic_service * srv,const char * event,double start,double dfirst,double dprev)459 service_timestamp(struct dynamic_service *srv, const char *event, double start,
460     double dfirst, double dprev)
461 {
462 
463 	VSL(SLT_Timestamp, 0, "vmod-dynamic %s.%s(srv %s) %s: %.6f %.6f %.6f",
464 	    srv->obj->vcl_conf, srv->obj->vcl_name, srv->service,
465 	    event, start, dfirst, dprev);
466 }
467 
468 static void*
service_lookup_thread(void * priv)469 service_lookup_thread(void *priv)
470 {
471 	struct vmod_dynamic_director *obj;
472 	struct dynamic_service *srv;
473 	struct vrt_ctx ctx;
474 	vtim_real lookup, results, update;
475 	const struct res_cb *res;
476 	void *res_priv = NULL;
477 	int ret;
478 
479 	CAST_OBJ_NOTNULL(srv, priv, DYNAMIC_SERVICE_MAGIC);
480 	INIT_OBJ(&ctx, VRT_CTX_MAGIC);
481 
482 	obj = srv->obj;
483 	res = obj->resolver;
484 
485 	AN(res->srv_lookup);
486 	AN(res->srv_result);
487 	AN(res->srv_fini);
488 
489 	while (obj->active && srv->status <= DYNAMIC_ST_ACTIVE) {
490 
491 		lookup = VTIM_real();
492 		service_timestamp(srv, "Lookup", lookup, 0., 0.);
493 
494 		ret = res->srv_lookup(obj->resolver_inst, srv->service,
495 		    &res_priv);
496 
497 		results = VTIM_real();
498 		service_timestamp(srv, "Results", results, results - lookup,
499 		    results - lookup);
500 
501 		if (ret == 0) {
502 			service_update(srv, res, res_priv, results);
503 			update = VTIM_real();
504 			service_timestamp(srv, "Update", update,
505 			    update - lookup, update - results);
506 			// minimum update delay for lockless safety
507 			update += 0.01;
508 			if (srv->deadline < update)
509 				srv->deadline = update;
510 			// maximum update delay
511 			if (obj->domain_usage_tmo > 0) {
512 				update += obj->domain_usage_tmo / 2;
513 				if (srv->deadline > update)
514 					srv->deadline = update;
515 			}
516 		} else {
517 			LOG(&ctx, SLT_Error, srv, "%s %d (%s)",
518 			    res->name, ret, res->strerror(ret));
519 			srv->deadline = results + obj->retry_after;
520 			dbg_res_details(NULL, srv->obj, res, res_priv);
521 		}
522 
523 		res->srv_fini(&res_priv);
524 		AZ(res_priv);
525 
526 		Lck_Lock(&srv->mtx);
527 
528 		if (srv->status == DYNAMIC_ST_READY) {
529 			AZ(pthread_cond_broadcast(&srv->resolve));
530 			srv->status = DYNAMIC_ST_ACTIVE;
531 		}
532 
533 		/* Check status again after the blocking call */
534 		if (obj->active && srv->status <= DYNAMIC_ST_ACTIVE) {
535 			ret = Lck_CondWait(&srv->cond, &srv->mtx,
536 			    srv->deadline);
537 			assert(ret == 0 || ret == ETIMEDOUT);
538 		}
539 
540 		Lck_Unlock(&srv->mtx);
541 	}
542 
543 	srv->status = DYNAMIC_ST_DONE;
544 	service_timestamp(srv, "Done", VTIM_real(), 0., 0.);
545 
546 	return (NULL);
547 }
548 
549 static void
service_free(VRT_CTX,struct dynamic_service * srv)550 service_free(VRT_CTX, struct dynamic_service *srv)
551 {
552 	CHECK_OBJ_ORNULL(ctx, VRT_CTX_MAGIC);
553 	CHECK_OBJ_NOTNULL(srv, DYNAMIC_SERVICE_MAGIC);
554 	AZ(srv->thread);
555 	assert(srv->status == DYNAMIC_ST_READY);
556 
557 	VRT_DelDirector(&srv->dir);
558 
559 	if (ctx != NULL) {
560 		Lck_AssertHeld(&srv->obj->mtx);
561 		LOG(ctx, SLT_VCL_Log, srv, "%s", "deleted");
562 	}
563 
564 	if (srv->prios_cold != NULL)
565 		service_prios_free(&srv->prios_cold);
566 	if (srv->prios != NULL)
567 		service_prios_free(&srv->prios);
568 	AZ(srv->prios_cold);
569 	AZ(srv->prios);
570 
571 	AZ(pthread_cond_destroy(&srv->resolve));
572 	AZ(pthread_cond_destroy(&srv->cond));
573 	Lck_Delete(&srv->mtx);
574 	REPLACE(srv->service, NULL);
575 	FREE_OBJ(srv);
576 }
577 
578 static void
service_join(struct dynamic_service * srv)579 service_join(struct dynamic_service *srv)
580 {
581 	CHECK_OBJ_NOTNULL(srv, DYNAMIC_SERVICE_MAGIC);
582 	AN(srv->thread);
583 	AZ(pthread_join(srv->thread, NULL));
584 	assert(srv->status == DYNAMIC_ST_DONE);
585 	srv->thread = 0;
586 	srv->status = DYNAMIC_ST_READY;
587 }
588 
589 // called from dynamic_stop
590 void
service_stop(struct vmod_dynamic_director * obj)591 service_stop(struct vmod_dynamic_director *obj)
592 {
593 	struct dynamic_service *srv, *s2;
594 
595 	CHECK_OBJ_NOTNULL(obj, VMOD_DYNAMIC_DIRECTOR_MAGIC);
596 
597 	VTAILQ_FOREACH(srv, &obj->active_services, list) {
598 		CHECK_OBJ_NOTNULL(srv, DYNAMIC_SERVICE_MAGIC);
599 		Lck_Lock(&srv->mtx);
600 		AN(srv->thread);
601 		AZ(pthread_cond_signal(&srv->cond));
602 		Lck_Unlock(&srv->mtx);
603 	}
604 	VTAILQ_FOREACH(srv, &obj->active_services, list)
605 		service_join(srv);
606 
607 	VTAILQ_FOREACH_SAFE(srv, &obj->purged_services, list, s2) {
608 		CHECK_OBJ_NOTNULL(srv, DYNAMIC_SERVICE_MAGIC);
609 		assert(srv->status == DYNAMIC_ST_STALE ||
610 		    srv->status == DYNAMIC_ST_DONE);
611 		service_join(srv);
612 		VTAILQ_REMOVE(&obj->purged_services, srv, list);
613 		service_free(NULL, srv);
614 	}
615 }
616 
617 // called from dynamic_start
618 void
service_start(VRT_CTX,struct vmod_dynamic_director * obj)619 service_start(VRT_CTX, struct vmod_dynamic_director *obj)
620 {
621 	struct dynamic_service *srv;
622 
623 	(void) ctx;
624 	Lck_AssertHeld(&obj->mtx);
625 
626 	VTAILQ_FOREACH(srv, &obj->active_services, list) {
627 		CHECK_OBJ_NOTNULL(srv, DYNAMIC_SERVICE_MAGIC);
628 		assert(srv->status == DYNAMIC_ST_READY);
629 		AZ(srv->thread);
630 		AZ(pthread_create(&srv->thread, NULL, service_lookup_thread,
631 		    srv));
632 	}
633 }
634 
635 // calledn from vmod_director__fini
636 void
service_fini(struct vmod_dynamic_director * obj)637 service_fini(struct vmod_dynamic_director *obj)
638 {
639 	struct dynamic_service *srv, *s2;
640 
641 	CHECK_OBJ_NOTNULL(obj, VMOD_DYNAMIC_DIRECTOR_MAGIC);
642 
643 	VTAILQ_FOREACH_SAFE(srv, &obj->purged_services, list, s2) {
644 		VTAILQ_REMOVE(&obj->purged_services, srv, list);
645 		service_free(NULL, srv);
646 	}
647 
648 	VTAILQ_FOREACH_SAFE(srv, &obj->active_services, list, s2) {
649 		VTAILQ_REMOVE(&obj->active_services, srv, list);
650 		service_free(NULL, srv);
651 	}
652 
653 }
654 
655 static struct dynamic_service *
service_search(VRT_CTX,struct vmod_dynamic_director * obj,const char * service)656 service_search(VRT_CTX, struct vmod_dynamic_director *obj, const char *service)
657 {
658 	struct dynamic_service *srv, *s, *s2;
659 
660 	CHECK_OBJ_NOTNULL(obj, VMOD_DYNAMIC_DIRECTOR_MAGIC);
661 	Lck_AssertHeld(&obj->mtx);
662 	AN(service);
663 
664 	srv = NULL;
665 	VTAILQ_FOREACH_SAFE(s, &obj->active_services, list, s2) {
666 		CHECK_OBJ_NOTNULL(s, DYNAMIC_SERVICE_MAGIC);
667 		if (strcmp(s->service, service) == 0)
668 			srv = s;
669 		if (srv != s && s->status == DYNAMIC_ST_ACTIVE &&
670 		    obj->domain_usage_tmo > 0 &&
671 		    ctx->now - s->last_used > obj->domain_usage_tmo) {
672 			LOG(ctx, SLT_VCL_Log, s, "%s", "timeout");
673 			Lck_Lock(&s->mtx);
674 			s->status = DYNAMIC_ST_STALE;
675 			AZ(pthread_cond_signal(&s->cond));
676 			Lck_Unlock(&s->mtx);
677 			VTAILQ_REMOVE(&obj->active_services, s, list);
678 			VTAILQ_INSERT_TAIL(&obj->purged_services, s, list);
679 		}
680 	}
681 
682 	VTAILQ_FOREACH_SAFE(s, &obj->purged_services, list, s2) {
683 		CHECK_OBJ_NOTNULL(s, DYNAMIC_SERVICE_MAGIC);
684 		if (s->status == DYNAMIC_ST_DONE) {
685 			service_join(s);
686 			VTAILQ_REMOVE(&obj->purged_services, s, list);
687 			service_free(ctx, s);
688 		}
689 	}
690 
691 	return (srv);
692 }
693 
694 static struct dynamic_service *
service_get(VRT_CTX,struct vmod_dynamic_director * obj,const char * service)695 service_get(VRT_CTX, struct vmod_dynamic_director *obj, const char *service)
696 {
697 	struct dynamic_service *srv;
698 
699 	CHECK_OBJ_NOTNULL(obj, VMOD_DYNAMIC_DIRECTOR_MAGIC);
700 	Lck_AssertHeld(&obj->mtx);
701 	AN(service);
702 
703 	srv = service_search(ctx, obj, service);
704 	if (srv != NULL)
705 		return (srv);
706 
707 	ALLOC_OBJ(srv, DYNAMIC_SERVICE_MAGIC);
708 	AN(srv);
709 
710 	REPLACE(srv->service, service);
711 
712 	srv->obj = obj;
713 
714 	srv->dir = VRT_AddDirector(ctx, vmod_dynamic_service_methods, srv,
715 	    "%s(%s)", obj->vcl_name, service);
716 
717 	Lck_New(&srv->mtx, lck_be);
718 	AZ(pthread_cond_init(&srv->cond, NULL));
719 	AZ(pthread_cond_init(&srv->resolve, NULL));
720 
721 	AZ(pthread_create(&srv->thread, NULL, service_lookup_thread, srv));
722 
723 	VTAILQ_INSERT_TAIL(&obj->active_services, srv, list);
724 
725 	return (srv);
726 }
727 
728 
v_matchproto_(td_dynamic_director_service)729 VCL_BACKEND v_matchproto_(td_dynamic_director_service)
730 vmod_director_service(VRT_CTX, struct VPFX(dynamic_director) *obj,
731     VCL_STRING service) {
732 	struct dynamic_service *srv;
733 	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
734 	CHECK_OBJ_NOTNULL(obj, VMOD_DYNAMIC_DIRECTOR_MAGIC);
735 
736 	if (obj->resolver_inst == NULL) {
737 		VRT_fail(ctx, "xdynamic.service(): Only supported "
738 		    "with a resolver");
739 		return (NULL);
740 	}
741 
742 	Lck_Lock(&obj->mtx);
743 	srv = service_get(ctx, obj, service);
744 	AN(srv);
745 	srv->last_used = ctx->now;
746 	Lck_Unlock(&obj->mtx);
747 
748 	return (srv->dir);
749 }
750