1 /*	$Id: arms_event_loop.c 20955 2012-01-31 04:03:18Z m-oki $	*/
2 
3 /*
4  * Copyright (c) 2012, Internet Initiative Japan, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "config.h"
31 
32 #ifdef HAVE_SIGNAL
33 #include <signal.h>
34 #endif
35 #include <inttypes.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <time.h>
39 #include <sys/queue.h>
40 #include <sys/socket.h> /* for AF_INET */
41 #include <netdb.h>
42 
43 #include <libarms.h>
44 #include <libarms_log.h>
45 #include <lsconfig.h>
46 #include <libarms_param.h>
47 #include <libarms_resource.h>
48 
49 #include <libarms/time.h>
50 #include <libarms/ssl.h>
51 #include <libarms/malloc.h>
52 #include <scheduler/scheduler.h>
53 #include <armsd_conf.h> /* for ACMI */
54 #include <transaction/transaction.h>
55 #include <transaction/ssltunnel.h>
56 #include <protocol/arms_methods.h>
57 #include <server/server.h>
58 #include <http/http.h>
59 
60 #include "compat.h"
61 
62 static struct arms_schedule *app_event_obj = NULL;
63 static struct arms_schedule *heartbeat_obj = NULL;
64 
65 const struct timeval *
arms_get_app_event_interval(arms_context_t * res)66 arms_get_app_event_interval(arms_context_t *res)
67 {
68 	if (res == NULL) {
69 		return NULL;
70 	}
71 	return &res->app_timeout;
72 }
73 
74 void
arms_hb_stop(arms_context_t * res)75 arms_hb_stop(arms_context_t *res)
76 {
77 	if (res == NULL)
78 		return;
79 	res->hb_running = 0;
80 }
81 
82 void
arms_hb_start(arms_context_t * res)83 arms_hb_start(arms_context_t *res)
84 {
85 	if (res == NULL)
86 		return;
87 	res->hb_running = 1;
88 }
89 
90 int
arms_hb_is_running(arms_context_t * res)91 arms_hb_is_running(arms_context_t *res)
92 {
93 	return (res != NULL && res->hb_running && heartbeat_obj != NULL);
94 }
95 
96 int
arms_set_app_event_interval(arms_context_t * res,const struct timeval * timo)97 arms_set_app_event_interval(arms_context_t *res, const struct timeval *timo)
98 {
99 	/* check value */
100 	if (res == NULL) {
101 		return -1;
102 	}
103 	if (timo == NULL) {
104 		return -1;
105 	}
106 	if (timo->tv_sec < 0 || timo->tv_sec > 600) {
107 		return -1;
108 	}
109 	if (timo->tv_usec < 0 || timo->tv_usec >= 1000 * 1000) {
110 		return -1;
111 	}
112 	/* at least 0.1 sec. */
113 	if (timo->tv_sec == 0 && timo->tv_usec < 100 * 1000) {
114 		return -1;
115 	}
116 	if (timo->tv_sec == 600 && timo->tv_usec > 0) {
117 		return -1;
118 	}
119 
120 	/* store new timeout value. */
121 	res->app_timeout = *timo;
122 	if (app_event_obj != NULL) {
123 		arms_get_timeval_remaining(&app_event_obj->timeout, timo);
124 	}
125 
126 	return 0;
127 }
128 
129 static int
arms_app_event(struct arms_schedule * obj,int event)130 arms_app_event(struct arms_schedule *obj, int event)
131 {
132 	arms_context_t *res = arms_get_context();
133 	int rv;
134 
135 	switch (event) {
136 	case EVENT_TYPE_TIMEOUT:
137 		/* call only if not running configure */
138 		rv = res->callbacks.app_event_cb(res->udata);
139 		if (rv == ARMS_EPULL) {
140 			res->result = ARMS_EPULL;
141 			return SCHED_FINISHED_SCHEDULER;
142 		}
143 		if (!arms_is_running_configure(res)) {
144 			if (rv != 0) {
145 				res->result = ARMS_EPUSH;
146 				return SCHED_FINISHED_SCHEDULER;
147 			}
148 		}
149 		break;
150 	case EVENT_TYPE_FINISH:
151 		/* nothing to free resource yet */
152 		app_event_obj = NULL;
153 		return SCHED_FINISHED_THIS;
154 	default:
155 		break;
156 	}
157 	arms_get_timeval_remaining(&obj->timeout, &res->app_timeout);
158 	return SCHED_CONTINUE_THIS;
159 }
160 
161 static int
arms_heartbeat_event(struct arms_schedule * obj,int event)162 arms_heartbeat_event(struct arms_schedule *obj, int event)
163 {
164 	arms_context_t *res = arms_get_context();
165 	struct timeval now, base, delta, interval, remain;
166 	hb_send_result_t result;
167 	int i, rv;
168 
169 	arms_monotime(&base);
170 
171 	switch (event) {
172 	case EVENT_TYPE_EXEC:
173 	case EVENT_TYPE_TIMEOUT:
174 		if (!arms_is_running_configure(res)) {
175 			/* call only if not running configure */
176 			arms_hb_clear(&res->hb_ctx);
177 			rv = res->callbacks.hb_store_statistics_cb(
178 			    res, res->udata);
179 			if (rv != 0) {
180 				res->result = ARMS_EPUSH;
181 				return SCHED_FINISHED_SCHEDULER;
182 			}
183 			if (!res->hb_running)
184 				break;
185 			/* send heartbeat packet */
186 			arms_hb_send(&res->hb_ctx, res->sa_af, &result);
187 			for (i = 0; i < res->hb_ctx.numsvr; i++) {
188 				if (result.server[i].stage == 0) {
189 					/* log */
190 					libarms_log(ARMS_LOG_DEBUG,
191 					    "Sent heartbeat to %s",
192 					    res->hb_ctx.server[i].host);
193 				}
194 			}
195 		}
196 		break;
197 	case EVENT_TYPE_FINISH:
198 		/* nothing to free resource yet */
199 		heartbeat_obj = NULL;
200 		if (res->hb_running)
201 			libarms_log(ARMS_LOG_IHEARTBEAT_STOP,
202 			    "Stop heartbeat.");
203 		arms_hb_stop(res);
204 		return SCHED_FINISHED_THIS;
205 	default:
206 		break;
207 	}
208 	arms_monotime(&now);
209 	if (timercmp(&now, &base, >)) {
210 		timersub(&now, &base, &delta);
211 	} else {
212 		/* backtime clock_gettime? */
213 		delta.tv_sec = 0;
214 		delta.tv_usec = 0;
215 	}
216 	/* server timeout from hbt_info */
217 	interval.tv_sec = res->hbt_info[0].interval;
218 	interval.tv_usec = 0;
219 	timersub(&interval, &delta, &remain);
220 	arms_get_timeval_remaining(&obj->timeout, &remain);
221 	return SCHED_CONTINUE_THIS;
222 }
223 
224 static void
arms_https_simple_loop(arms_context_t * res,int port)225 arms_https_simple_loop(arms_context_t *res, int port)
226 {
227 	int i, rs, nrs, solrs;
228 
229 	if (res->rs_pull_url[0] == NULL) {
230 		libarms_log(ARMS_LOG_EHOST,
231 			    "RS not found.");
232 		res->trigger = "RS not found";
233 		res->result = ARMS_ETIMEOUT;
234 		return;
235 	}
236 
237 	/* calc number of RS. */
238 	for (nrs = 0; nrs < MAX_RS_INFO; nrs++) {
239 		if (res->rs_pull_url[nrs] == NULL) {
240 			/* no more RS. */
241 			break;
242 		}
243 	}
244 	/*
245 	 * register schedule.
246 	 * use rs index by conf-sol if possible.
247 	 *
248 	 * in current scheduler register routine,
249 	 * use "insert to head" algorithm.
250 	 */
251 	solrs = acmi_get_current_server(res->acmi, ACMI_CONFIG_CONFSOL);
252 	if (nrs == acmi_get_num_server(res->acmi, ACMI_CONFIG_CONFSOL))
253 		res->rs_pull_1st = solrs;
254 	if (res->rs_pull_1st == -1)
255 		res->rs_pull_1st = 0;
256 	for (i = nrs - 1; i >= 0; i--) {
257 		/* calc rs index. */
258 		rs = (i + res->rs_pull_1st) % nrs;
259 		if (new_confirm_start_transaction(res,
260 		    strdistid(&res->dist_id),
261 		    res->rs_pull_url[rs], rs) != 0) {
262 			return;
263 		}
264 	}
265 	/* register server if https-simple */
266 	if (res->sa_af == AF_INET6)
267 		snprintf(res->push_endpoint, sizeof(res->push_endpoint),
268 		         "https://[%s]:%d/", res->sa_address, port);
269 	else
270 		snprintf(res->push_endpoint, sizeof(res->push_endpoint),
271 		         "https://%s:%d/", res->sa_address, port);
272 	res->result = new_arms_server(res->sa_af, port,
273 				      strdistid(&res->dist_id),
274 				      res->rs_preshared_key);
275 	if (res->result != 0)
276 		return;
277 	res->confirm_id = -1;
278 	res->rs_pull_1st = -1;
279 
280 	/* start push-confirmation */
281 	arms_scheduler();
282 	/*
283 	 * res->result
284 	 * 0: success
285 	 * ARMS_ESYSTEM: fatal error
286 	 * ARMS_ETIMEOUT: confirmation timeout
287 	 * ARMS_EPULL:
288 	 * ARMS_EREBOOT:
289 	 * ARMS_EDONTRETRY: don't retry.
290 	 */
291 	if (res->result == ARMS_ETIMEOUT)
292 		libarms_log(ARMS_LOG_IPROTO_CONFIRM_FAILED,
293 			    "Failed push confirmation by simple.");
294 }
295 
296 void
arms_hb_start_loop(arms_context_t * res)297 arms_hb_start_loop(arms_context_t *res)
298 {
299 	if (heartbeat_obj != NULL) {
300 		/* already running.  simply ignore. */
301 		return;
302 	}
303 
304 	/* register heartbeat timer if available */
305 	if (res->callbacks.version >= 7 &&
306 	    res->callbacks.hb_store_statistics_cb != NULL) {
307 		struct timeval timo;
308 		struct addrinfo hints, *re;
309 		int i;
310 
311 		/* log */
312 		libarms_log(ARMS_LOG_IHEARTBEAT_START,
313 			    "Start heartbeat (interval: %d sec)",
314 			    res->hbt_info[0].interval);
315 		memset(&hints, 0, sizeof(hints));
316 		hints.ai_family = res->sa_af;
317 		for (i = 0; i < res->num_of_hbt; i++) {
318 			if (getaddrinfo(res->hbt_info[i].host,
319 					NULL, &hints, &re) == 0) {
320 				libarms_log(ARMS_LOG_IHEARTBEAT_SERVER,
321 				    " heartbeat server: %s",
322 				    res->hbt_info[i].host);
323 				if (re != NULL)
324 					freeaddrinfo(re);
325 			}
326 		}
327 		/* call event function immediately */
328 		arms_get_time_remaining(&timo, 0);
329 		heartbeat_obj = new_arms_schedule(SCHED_TYPE_EXEC, -1,
330 		    &timo, arms_heartbeat_event, NULL);
331 		arms_hb_start(res);
332 	}
333 }
334 
335 int
arms_event_loop(arms_context_t * res,int port,size_t fragment,arms_callback_tbl_t * cb_tbl,void * udata)336 arms_event_loop(arms_context_t *res, int port, size_t fragment,
337 		arms_callback_tbl_t *cb_tbl, void *udata)
338 {
339 #ifdef HAVE_SIGNAL
340 	struct sigaction oldact, newact;
341 #endif
342 	struct timeval timo;
343 	int m, n;
344 
345 	/* check parameter */
346 	if (res == NULL)
347 		return ARMS_EINVAL;
348 	if (cb_tbl == NULL)
349 		return ARMS_EINVAL;
350 	if (port < 0  || port > 65535)
351 		return ARMS_EINVAL;
352 
353 	/* setup */
354 	if (port == 0)
355 		port = 10443;
356 	res->fragment = fragment;
357 	res->udata = udata;
358 	res->server_port = port;
359 	arms_scheduler_init();
360 
361 	/* reset tunnel skip status. */
362 	res->rs_tunnel_1st = -1;
363 
364 #ifdef HAVE_SIGNAL
365 	/* block SIGPIPE */
366 	memset(&newact, 0, sizeof(newact));
367 	memset(&oldact, 0, sizeof(oldact));
368 	newact.sa_handler = SIG_IGN;
369 	sigaction(SIGPIPE, &newact, &oldact);
370 #endif
371 	if (res->callbacks.version < 5) {
372 		/* temporary backward compatibility: method query. */
373 		res->http_preferred_version = 0;
374 		res->result = 0;
375 		arms_push_method_query(res, cb_tbl, udata);
376 	}
377 	if (res->nmethods == 0) {
378 		libarms_log(ARMS_LOG_EHTTP,
379 			    "no push method.");
380 		libarms_log(ARMS_LOG_EHTTP,
381 			    "(forgot to call arms_push_method_query()?)");
382 		res->trigger = "no push method";
383 		return ARMS_EPULL;
384 	}
385 
386 	do {
387 		res->result = 0;
388 
389 		/* HTTP/1.1 */
390 		res->http_preferred_version = 1;
391 
392 		/* try accepted method */
393 		for (m = 0; m < res->nmethods; m++) {
394 			/* register app_event timer if available */
395 			if (res->callbacks.app_event_cb != NULL) {
396 				arms_get_timeval_remaining(&timo,
397 							&res->app_timeout);
398 				app_event_obj = new_arms_schedule(
399 					SCHED_TYPE_TIMER, -1,
400 					&timo, arms_app_event, NULL);
401 			}
402 			res->cur_method = res->method_info[m];
403 			switch (res->cur_method) {
404 			case ARMS_PUSH_METHOD_SIMPLE:
405 				if (res->proxy_is_available) {
406 					libarms_log(ARMS_LOG_DEBUG,
407 					    "Web proxy server available, skip simple method");
408 					break;
409 				}
410 				libarms_log(ARMS_LOG_IPUSH_METHOD_SIMPLE,
411 					    "Push method: simple");
412 				arms_https_simple_loop(res, port);
413 				break;
414 			case ARMS_PUSH_METHOD_TUNNEL:
415 				libarms_log(ARMS_LOG_IPUSH_METHOD_TUNNEL,
416 					    "Push method: tunnel");
417 				for (n = 0; n < MAX_RS_INFO; n++) {
418 					if (res->rs_tunnel_url[n] == NULL)
419 						break;
420 					libarms_log(ARMS_LOG_DEBUG,
421 					    "tunnel#%d: %s",
422 					    n, res->rs_tunnel_url[n]);
423 				}
424 				if (n == 0) {
425 					libarms_log(ARMS_LOG_EHTTP,
426 						    "tunnel destination URL is not found.");
427 					res->trigger = "tunnel URL not found";
428 					res->result = ARMS_ETIMEOUT;
429 					continue;/* try next method */
430 				}
431 				arms_ssltunnel_loop(res, n, res->rs_tunnel_url);
432 				break;
433 			default:
434 				break;
435 			}
436 			if (res->result != ARMS_ETIMEOUT) {
437 				break;
438 			}
439 			/* timeout: try next method */
440 		}
441 		/*
442 		 * failed to push-wait loop by simple and tunnel method.
443 		 */
444 		if (res->result == ARMS_ETIMEOUT)
445 			res->result = ARMS_EPULL;
446 		/*
447 		 * stop scheduler by app_event_cb, pull-config,
448 		 * reboot or rollback failed in configure.
449 		 */
450 		if (res->result == 0)
451 			res->result = ARMS_EPULL;
452 	} while (res->result == ARMS_EPUSH);
453 
454 #ifdef HAVE_SIGNAL
455 	sigaction(SIGPIPE, &oldact, NULL);
456 #endif
457 	libarms_log(ARMS_LOG_DEBUG,
458 		    "end of arms_event_loop (result=%d)", res->result);
459 	res->cur_method = ARMS_PUSH_METHOD_UNKNOWN;
460 
461 	return res->result;
462 }
463