Drop overlapping segments only if we have no active SAs on it
[strongswan.git] / src / charon / plugins / ha / ha_segments.c
1 /*
2 * Copyright (C) 2008 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "ha_segments.h"
17
18 #include <pthread.h>
19
20 #include <utils/mutex.h>
21 #include <utils/linked_list.h>
22 #include <processing/jobs/callback_job.h>
23
24 #define HEARTBEAT_DELAY 1000
25 #define HEARTBEAT_TIMEOUT 2100
26
27 typedef struct private_ha_segments_t private_ha_segments_t;
28
29 /**
30 * Private data of an ha_segments_t object.
31 */
32 struct private_ha_segments_t {
33
34 /**
35 * Public ha_segments_t interface.
36 */
37 ha_segments_t public;
38
39 /**
40 * communication socket
41 */
42 ha_socket_t *socket;
43
44 /**
45 * Sync tunnel, if any
46 */
47 ha_tunnel_t *tunnel;
48
49 /**
50 * Interface to control segments at kernel level
51 */
52 ha_kernel_t *kernel;
53
54 /**
55 * Mutex to lock segment manipulation
56 */
57 mutex_t *mutex;
58
59 /**
60 * Condvar to wait for heartbeats
61 */
62 condvar_t *condvar;
63
64 /**
65 * Job checking for heartbeats
66 */
67 callback_job_t *job;
68
69 /**
70 * Total number of ClusterIP segments
71 */
72 u_int count;
73
74 /**
75 * mask of active segments
76 */
77 segment_mask_t active;
78
79 /**
80 * Are we the master node handling segment assignement?
81 */
82 bool master;
83 };
84
85 /**
86 * Log currently active segments
87 */
88 static void log_segments(private_ha_segments_t *this, bool activated,
89 u_int segment)
90 {
91 char buf[64] = "none", *pos = buf;
92 int i;
93 bool first = TRUE;
94
95 for (i = 1; i <= this->count; i++)
96 {
97 if (this->active & SEGMENTS_BIT(i))
98 {
99 if (first)
100 {
101 first = FALSE;
102 }
103 else
104 {
105 pos += snprintf(pos, buf + sizeof(buf) - pos, ",");
106 }
107 pos += snprintf(pos, buf + sizeof(buf) - pos, "%d", i);
108 }
109 }
110 DBG1(DBG_CFG, "HA segment %d %sactivated, now active: %s",
111 segment, activated ? "" : "de", buf);
112 }
113
114 /**
115 * Enable/Disable a specific segment
116 */
117 static void enable_disable(private_ha_segments_t *this, u_int segment,
118 bool enable, bool notify)
119 {
120 ike_sa_t *ike_sa;
121 enumerator_t *enumerator;
122 ike_sa_state_t old, new;
123 ha_message_t *message = NULL;
124 ha_message_type_t type;
125 bool changes = FALSE;
126
127 if (segment > this->count)
128 {
129 return;
130 }
131
132 if (enable)
133 {
134 old = IKE_PASSIVE;
135 new = IKE_ESTABLISHED;
136 type = HA_SEGMENT_TAKE;
137 if (!(this->active & SEGMENTS_BIT(segment)))
138 {
139 this->active |= SEGMENTS_BIT(segment);
140 this->kernel->activate(this->kernel, segment);
141 changes = TRUE;
142 }
143 }
144 else
145 {
146 old = IKE_ESTABLISHED;
147 new = IKE_PASSIVE;
148 type = HA_SEGMENT_DROP;
149 if (this->active & SEGMENTS_BIT(segment))
150 {
151 this->active &= ~SEGMENTS_BIT(segment);
152 this->kernel->deactivate(this->kernel, segment);
153 changes = TRUE;
154 }
155 }
156
157 if (changes)
158 {
159 enumerator = charon->ike_sa_manager->create_enumerator(charon->ike_sa_manager);
160 while (enumerator->enumerate(enumerator, &ike_sa))
161 {
162 if (ike_sa->get_state(ike_sa) != old)
163 {
164 continue;
165 }
166 if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa))
167 {
168 continue;
169 }
170 if (this->kernel->in_segment(this->kernel,
171 ike_sa->get_other_host(ike_sa), segment))
172 {
173 ike_sa->set_state(ike_sa, new);
174 }
175 }
176 enumerator->destroy(enumerator);
177 log_segments(this, enable, segment);
178 }
179
180 if (notify)
181 {
182 message = ha_message_create(type);
183 message->add_attribute(message, HA_SEGMENT, segment);
184 this->socket->push(this->socket, message);
185 }
186 }
187
188 /**
189 * Enable/Disable all or a specific segment, do locking
190 */
191 static void enable_disable_all(private_ha_segments_t *this, u_int segment,
192 bool enable, bool notify)
193 {
194 int i;
195
196 this->mutex->lock(this->mutex);
197 if (segment == 0)
198 {
199 for (i = 1; i <= this->count; i++)
200 {
201 enable_disable(this, i, enable, notify);
202 }
203 }
204 else
205 {
206 enable_disable(this, segment, enable, notify);
207 }
208 this->mutex->unlock(this->mutex);
209 }
210
211 /**
212 * Implementation of ha_segments_t.activate
213 */
214 static void activate(private_ha_segments_t *this, u_int segment, bool notify)
215 {
216 enable_disable_all(this, segment, TRUE, notify);
217 }
218
219 /**
220 * Implementation of ha_segments_t.deactivate
221 */
222 static void deactivate(private_ha_segments_t *this, u_int segment, bool notify)
223 {
224 enable_disable_all(this, segment, FALSE, notify);
225 }
226
227 /**
228 * Rekey all children of an IKE_SA
229 */
230 static status_t rekey_children(ike_sa_t *ike_sa)
231 {
232 iterator_t *iterator;
233 child_sa_t *child_sa;
234 status_t status = SUCCESS;
235
236 iterator = ike_sa->create_child_sa_iterator(ike_sa);
237 while (iterator->iterate(iterator, (void**)&child_sa))
238 {
239 DBG1(DBG_CFG, "resyncing CHILD_SA");
240 status = ike_sa->rekey_child_sa(ike_sa, child_sa->get_protocol(child_sa),
241 child_sa->get_spi(child_sa, TRUE));
242 if (status == DESTROY_ME)
243 {
244 break;
245 }
246 }
247 iterator->destroy(iterator);
248 return status;
249 }
250
251 /**
252 * Implementation of ha_segments_t.resync
253 */
254 static void resync(private_ha_segments_t *this, u_int segment)
255 {
256 ike_sa_t *ike_sa;
257 enumerator_t *enumerator;
258 linked_list_t *list;
259 ike_sa_id_t *id;
260 u_int16_t mask = SEGMENTS_BIT(segment);
261
262 list = linked_list_create();
263 this->mutex->lock(this->mutex);
264
265 if (segment > 0 && segment <= this->count && (this->active & mask))
266 {
267 this->active &= ~mask;
268
269 DBG1(DBG_CFG, "resyncing HA segment %d", segment);
270
271 /* we do the actual rekeying in a seperate loop to avoid rekeying
272 * an SA twice. */
273 enumerator = charon->ike_sa_manager->create_enumerator(
274 charon->ike_sa_manager);
275 while (enumerator->enumerate(enumerator, &ike_sa))
276 {
277 if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
278 this->kernel->in_segment(this->kernel,
279 ike_sa->get_other_host(ike_sa), segment))
280 {
281 id = ike_sa->get_id(ike_sa);
282 list->insert_last(list, id->clone(id));
283 }
284 }
285 enumerator->destroy(enumerator);
286 }
287 this->mutex->unlock(this->mutex);
288
289 while (list->remove_last(list, (void**)&id) == SUCCESS)
290 {
291 ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id);
292 id->destroy(id);
293 if (ike_sa)
294 {
295 DBG1(DBG_CFG, "resyncing IKE_SA");
296 if (ike_sa->rekey(ike_sa) != DESTROY_ME)
297 {
298 if (rekey_children(ike_sa) != DESTROY_ME)
299 {
300 charon->ike_sa_manager->checkin(
301 charon->ike_sa_manager, ike_sa);
302 continue;
303 }
304 }
305 charon->ike_sa_manager->checkin_and_destroy(
306 charon->ike_sa_manager, ike_sa);
307 }
308 }
309 list->destroy(list);
310 }
311
312 /**
313 * Implementation of listener_t.alert
314 */
315 static bool alert_hook(private_ha_segments_t *this, ike_sa_t *ike_sa,
316 alert_t alert, va_list args)
317 {
318 if (alert == ALERT_SHUTDOWN_SIGNAL)
319 {
320 deactivate(this, 0, TRUE);
321 }
322 return TRUE;
323 }
324
325 /**
326 * Get the number of SAs in a segment.
327 */
328 static u_int get_sa_count(private_ha_segments_t *this)
329 {
330 enumerator_t *enumerator;
331 ike_sa_t *ike_sa;
332 u_int count = 0;
333
334 enumerator = charon->ike_sa_manager->create_enumerator(charon->ike_sa_manager);
335 while (enumerator->enumerate(enumerator, &ike_sa))
336 {
337 if (ike_sa->get_state(ike_sa) != IKE_ESTABLISHED)
338 {
339 continue;
340 }
341 if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa))
342 {
343 continue;
344 }
345 count++;
346 }
347 enumerator->destroy(enumerator);
348 return count;
349 }
350
351 /**
352 * Implementation of ha_segments_t.handle_status
353 */
354 static void handle_status(private_ha_segments_t *this, segment_mask_t mask)
355 {
356 segment_mask_t missing, overlap;
357 int i;
358
359 this->mutex->lock(this->mutex);
360
361 missing = ~(this->active | mask);
362 overlap = this->active & mask;
363
364 /* Activate any missing segment. The master will disable overlapping
365 * segments if both nodes activate the missing segments simultaneously. */
366 for (i = 1; i <= this->count; i++)
367 {
368 if (missing & SEGMENTS_BIT(i))
369 {
370 DBG1(DBG_CFG, "HA segment %d was not handled", i);
371 enable_disable(this, i, TRUE, TRUE);
372 }
373 }
374 if (this->master && overlap)
375 {
376 /* Disable overlapping segment on one node, controlled by master */
377 for (i = 1; i <= this->count; i++)
378 {
379 if (overlap & SEGMENTS_BIT(i))
380 {
381 if (get_sa_count(this))
382 {
383 DBG1(DBG_CFG, "HA segment %d overlaps, taking over", i);
384 enable_disable(this, i, TRUE, TRUE);
385 }
386 else
387 {
388 DBG1(DBG_CFG, "HA segment %d overlaps, dropping", i);
389 enable_disable(this, i, FALSE, TRUE);
390 }
391 }
392 }
393 }
394 this->mutex->unlock(this->mutex);
395 this->condvar->signal(this->condvar);
396 }
397
398 /**
399 * Send a status message with our active segments
400 */
401 static job_requeue_t send_status(private_ha_segments_t *this)
402 {
403 ha_message_t *message;
404 int i;
405
406 message = ha_message_create(HA_STATUS);
407
408 for (i = 1; i <= this->count; i++)
409 {
410 if (this->active & SEGMENTS_BIT(i))
411 {
412 message->add_attribute(message, HA_SEGMENT, i);
413 }
414 }
415
416 this->socket->push(this->socket, message);
417
418 /* schedule next invocation */
419 charon->scheduler->schedule_job_ms(charon->scheduler, (job_t*)
420 callback_job_create((callback_job_cb_t)
421 send_status, this, NULL, NULL),
422 HEARTBEAT_DELAY);
423
424 return JOB_REQUEUE_NONE;
425 }
426
427 /**
428 * Monitor heartbeat activity of remote node
429 */
430 static job_requeue_t watchdog(private_ha_segments_t *this)
431 {
432 int oldstate;
433 bool timeout;
434
435 this->mutex->lock(this->mutex);
436 pthread_cleanup_push((void*)this->mutex->unlock, this->mutex);
437 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
438 timeout = this->condvar->timed_wait(this->condvar, this->mutex,
439 HEARTBEAT_TIMEOUT);
440 pthread_setcancelstate(oldstate, NULL);
441 pthread_cleanup_pop(TRUE);
442 if (timeout)
443 { /* didn't get a heartbeat, take all segments */
444 activate(this, 0, TRUE);
445 }
446 return JOB_REQUEUE_DIRECT;
447 }
448
449 /**
450 * Implementation of ha_segments_t.destroy.
451 */
452 static void destroy(private_ha_segments_t *this)
453 {
454 this->job->cancel(this->job);
455 this->mutex->destroy(this->mutex);
456 this->condvar->destroy(this->condvar);
457 free(this);
458 }
459
460 /**
461 * See header
462 */
463 ha_segments_t *ha_segments_create(ha_socket_t *socket, ha_kernel_t *kernel,
464 ha_tunnel_t *tunnel, char *local, char *remote, u_int count)
465 {
466 private_ha_segments_t *this = malloc_thing(private_ha_segments_t);
467 int i;
468
469 memset(&this->public.listener, 0, sizeof(listener_t));
470 this->public.listener.alert = (bool(*)(listener_t*, ike_sa_t *, alert_t, va_list))alert_hook;
471 this->public.activate = (void(*)(ha_segments_t*, u_int segment,bool))activate;
472 this->public.deactivate = (void(*)(ha_segments_t*, u_int segment,bool))deactivate;
473 this->public.resync = (void(*)(ha_segments_t*, u_int segment))resync;
474 this->public.handle_status = (void(*)(ha_segments_t*, segment_mask_t mask))handle_status;
475 this->public.destroy = (void(*)(ha_segments_t*))destroy;
476
477 this->socket = socket;
478 this->tunnel = tunnel;
479 this->kernel = kernel;
480 this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
481 this->condvar = condvar_create(CONDVAR_TYPE_DEFAULT);
482 this->count = count;
483 this->master = strcmp(local, remote) > 0;
484
485 /* initially all segments are active */
486 this->active = 0;
487 for (i = 1; i <= count; i++)
488 {
489 this->active |= SEGMENTS_BIT(i);
490 }
491
492 send_status(this);
493
494 /* start heartbeat detection thread */
495 this->job = callback_job_create((callback_job_cb_t)watchdog,
496 this, NULL, NULL);
497 charon->processor->queue_job(charon->processor, (job_t*)this->job);
498
499 return &this->public;
500 }
501