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