trap-manager: don't check-in nonexisting IKE_SA if acquire fails
[strongswan.git] / src / libcharon / sa / trap_manager.c
1 /*
2 * Copyright (C) 2011-2012 Tobias Brunner
3 * Copyright (C) 2009 Martin Willi
4 * Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "trap_manager.h"
18
19 #include <hydra.h>
20 #include <daemon.h>
21 #include <threading/rwlock.h>
22 #include <collections/linked_list.h>
23
24
25 typedef struct private_trap_manager_t private_trap_manager_t;
26 typedef struct trap_listener_t trap_listener_t;
27
28 /**
29 * listener to track acquires
30 */
31 struct trap_listener_t {
32
33 /**
34 * Implements listener interface
35 */
36 listener_t listener;
37
38 /**
39 * points to trap_manager
40 */
41 private_trap_manager_t *traps;
42 };
43
44 /**
45 * Private data of an trap_manager_t object.
46 */
47 struct private_trap_manager_t {
48
49 /**
50 * Public trap_manager_t interface.
51 */
52 trap_manager_t public;
53
54 /**
55 * Installed traps, as entry_t
56 */
57 linked_list_t *traps;
58
59 /**
60 * read write lock for traps list
61 */
62 rwlock_t *lock;
63
64 /**
65 * listener to track acquiring IKE_SAs
66 */
67 trap_listener_t listener;
68 };
69
70 /**
71 * A installed trap entry
72 */
73 typedef struct {
74 /** ref to peer_cfg to initiate */
75 peer_cfg_t *peer_cfg;
76 /** ref to instanciated CHILD_SA */
77 child_sa_t *child_sa;
78 /** TRUE if an acquire is pending */
79 bool pending;
80 /** pending IKE_SA connecting upon acquire */
81 ike_sa_t *ike_sa;
82 } entry_t;
83
84 /**
85 * actually uninstall and destroy an installed entry
86 */
87 static void destroy_entry(entry_t *entry)
88 {
89 entry->child_sa->destroy(entry->child_sa);
90 entry->peer_cfg->destroy(entry->peer_cfg);
91 free(entry);
92 }
93
94 METHOD(trap_manager_t, install, u_int32_t,
95 private_trap_manager_t *this, peer_cfg_t *peer, child_cfg_t *child)
96 {
97 entry_t *entry, *found = NULL;
98 ike_cfg_t *ike_cfg;
99 child_sa_t *child_sa;
100 host_t *me, *other;
101 linked_list_t *my_ts, *other_ts, *list;
102 enumerator_t *enumerator;
103 status_t status;
104 u_int32_t reqid = 0;
105
106 /* try to resolve addresses */
107 ike_cfg = peer->get_ike_cfg(peer);
108 other = host_create_from_dns(ike_cfg->get_other_addr(ike_cfg, NULL),
109 0, ike_cfg->get_other_port(ike_cfg));
110 if (!other || other->is_anyaddr(other))
111 {
112 DESTROY_IF(other);
113 DBG1(DBG_CFG, "installing trap failed, remote address unknown");
114 return 0;
115 }
116 me = host_create_from_dns(ike_cfg->get_my_addr(ike_cfg, NULL),
117 other->get_family(other), ike_cfg->get_my_port(ike_cfg));
118 if (!me || me->is_anyaddr(me))
119 {
120 DESTROY_IF(me);
121 me = hydra->kernel_interface->get_source_addr(
122 hydra->kernel_interface, other, NULL);
123 if (!me)
124 {
125 DBG1(DBG_CFG, "installing trap failed, local address unknown");
126 other->destroy(other);
127 return 0;
128 }
129 me->set_port(me, ike_cfg->get_my_port(ike_cfg));
130 }
131
132 this->lock->write_lock(this->lock);
133 enumerator = this->traps->create_enumerator(this->traps);
134 while (enumerator->enumerate(enumerator, &entry))
135 {
136 if (streq(entry->child_sa->get_name(entry->child_sa),
137 child->get_name(child)))
138 {
139 this->traps->remove_at(this->traps, enumerator);
140 found = entry;
141 break;
142 }
143 }
144 enumerator->destroy(enumerator);
145 if (found)
146 { /* config might have changed so update everything */
147 DBG1(DBG_CFG, "updating already routed CHILD_SA '%s'",
148 child->get_name(child));
149 reqid = found->child_sa->get_reqid(found->child_sa);
150 }
151
152 /* create and route CHILD_SA */
153 child_sa = child_sa_create(me, other, child, reqid, FALSE);
154
155 list = linked_list_create_with_items(me, NULL);
156 my_ts = child->get_traffic_selectors(child, TRUE, NULL, list);
157 list->destroy_offset(list, offsetof(host_t, destroy));
158
159 list = linked_list_create_with_items(other, NULL);
160 other_ts = child->get_traffic_selectors(child, FALSE, NULL, list);
161 list->destroy_offset(list, offsetof(host_t, destroy));
162
163 /* while we don't know the finally negotiated protocol (ESP|AH), we
164 * could iterate all proposals for a best guess (TODO). But as we
165 * support ESP only for now, we set it here. */
166 child_sa->set_protocol(child_sa, PROTO_ESP);
167 child_sa->set_mode(child_sa, child->get_mode(child));
168 status = child_sa->add_policies(child_sa, my_ts, other_ts);
169 my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
170 other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
171 if (status != SUCCESS)
172 {
173 DBG1(DBG_CFG, "installing trap failed");
174 reqid = 0;
175 /* hold off destroying the CHILD_SA until we released the lock */
176 }
177 else
178 {
179 INIT(entry,
180 .child_sa = child_sa,
181 .peer_cfg = peer->get_ref(peer),
182 );
183 this->traps->insert_last(this->traps, entry);
184 reqid = child_sa->get_reqid(child_sa);
185 }
186 this->lock->unlock(this->lock);
187
188 if (status != SUCCESS)
189 {
190 child_sa->destroy(child_sa);
191 }
192 if (found)
193 {
194 destroy_entry(found);
195 }
196 return reqid;
197 }
198
199 METHOD(trap_manager_t, uninstall, bool,
200 private_trap_manager_t *this, u_int32_t reqid)
201 {
202 enumerator_t *enumerator;
203 entry_t *entry, *found = NULL;
204
205 this->lock->write_lock(this->lock);
206 enumerator = this->traps->create_enumerator(this->traps);
207 while (enumerator->enumerate(enumerator, &entry))
208 {
209 if (entry->child_sa->get_reqid(entry->child_sa) == reqid)
210 {
211 this->traps->remove_at(this->traps, enumerator);
212 found = entry;
213 break;
214 }
215 }
216 enumerator->destroy(enumerator);
217 this->lock->unlock(this->lock);
218
219 if (!found)
220 {
221 DBG1(DBG_CFG, "trap %d not found to uninstall", reqid);
222 return FALSE;
223 }
224
225 destroy_entry(found);
226 return TRUE;
227 }
228
229 /**
230 * convert enumerated entries to peer_cfg, child_sa
231 */
232 static bool trap_filter(rwlock_t *lock, entry_t **entry, peer_cfg_t **peer_cfg,
233 void *none, child_sa_t **child_sa)
234 {
235 if (peer_cfg)
236 {
237 *peer_cfg = (*entry)->peer_cfg;
238 }
239 if (child_sa)
240 {
241 *child_sa = (*entry)->child_sa;
242 }
243 return TRUE;
244 }
245
246 METHOD(trap_manager_t, create_enumerator, enumerator_t*,
247 private_trap_manager_t *this)
248 {
249 this->lock->read_lock(this->lock);
250 return enumerator_create_filter(this->traps->create_enumerator(this->traps),
251 (void*)trap_filter, this->lock,
252 (void*)this->lock->unlock);
253 }
254
255 METHOD(trap_manager_t, acquire, void,
256 private_trap_manager_t *this, u_int32_t reqid,
257 traffic_selector_t *src, traffic_selector_t *dst)
258 {
259 enumerator_t *enumerator;
260 entry_t *entry, *found = NULL;
261 peer_cfg_t *peer;
262 child_cfg_t *child;
263 ike_sa_t *ike_sa;
264
265 this->lock->read_lock(this->lock);
266 enumerator = this->traps->create_enumerator(this->traps);
267 while (enumerator->enumerate(enumerator, &entry))
268 {
269 if (entry->child_sa->get_reqid(entry->child_sa) == reqid)
270 {
271 found = entry;
272 break;
273 }
274 }
275 enumerator->destroy(enumerator);
276
277 if (!found)
278 {
279 DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d",reqid);
280 this->lock->unlock(this->lock);
281 return;
282 }
283 if (!cas_bool(&found->pending, FALSE, TRUE))
284 {
285 DBG1(DBG_CFG, "ignoring acquire, connection attempt pending");
286 this->lock->unlock(this->lock);
287 return;
288 }
289 peer = found->peer_cfg->get_ref(found->peer_cfg);
290 child = found->child_sa->get_config(found->child_sa);
291 child = child->get_ref(child);
292 reqid = found->child_sa->get_reqid(found->child_sa);
293 /* don't hold the lock while checking out the IKE_SA */
294 this->lock->unlock(this->lock);
295
296 ike_sa = charon->ike_sa_manager->checkout_by_config(
297 charon->ike_sa_manager, peer);
298 if (ike_sa)
299 {
300 if (ike_sa->get_peer_cfg(ike_sa) == NULL)
301 {
302 ike_sa->set_peer_cfg(ike_sa, peer);
303 }
304 if (ike_sa->get_version(ike_sa) == IKEV1)
305 { /* in IKEv1, don't prepend the acquiring packet TS, as we only
306 * have a single TS that we can establish in a Quick Mode. */
307 src = dst = NULL;
308 }
309 if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME)
310 {
311 /* make sure the entry is still there */
312 this->lock->read_lock(this->lock);
313 if (this->traps->find_first(this->traps, NULL,
314 (void**)&found) == SUCCESS)
315 {
316 found->ike_sa = ike_sa;
317 }
318 this->lock->unlock(this->lock);
319 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
320 }
321 else
322 {
323 ike_sa->destroy(ike_sa);
324 }
325 }
326 peer->destroy(peer);
327 }
328
329 /**
330 * Complete the acquire, if successful or failed
331 */
332 static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa,
333 child_sa_t *child_sa)
334 {
335 enumerator_t *enumerator;
336 entry_t *entry;
337
338 this->lock->read_lock(this->lock);
339 enumerator = this->traps->create_enumerator(this->traps);
340 while (enumerator->enumerate(enumerator, &entry))
341 {
342 if (entry->ike_sa != ike_sa)
343 {
344 continue;
345 }
346 if (child_sa && child_sa->get_reqid(child_sa) !=
347 entry->child_sa->get_reqid(entry->child_sa))
348 {
349 continue;
350 }
351 entry->ike_sa = NULL;
352 entry->pending = FALSE;
353 }
354 enumerator->destroy(enumerator);
355 this->lock->unlock(this->lock);
356 }
357
358 METHOD(listener_t, ike_state_change, bool,
359 trap_listener_t *listener, ike_sa_t *ike_sa, ike_sa_state_t state)
360 {
361 switch (state)
362 {
363 case IKE_DESTROYING:
364 complete(listener->traps, ike_sa, NULL);
365 return TRUE;
366 default:
367 return TRUE;
368 }
369 }
370
371 METHOD(listener_t, child_state_change, bool,
372 trap_listener_t *listener, ike_sa_t *ike_sa, child_sa_t *child_sa,
373 child_sa_state_t state)
374 {
375 switch (state)
376 {
377 case CHILD_INSTALLED:
378 case CHILD_DESTROYING:
379 complete(listener->traps, ike_sa, child_sa);
380 return TRUE;
381 default:
382 return TRUE;
383 }
384 }
385
386 METHOD(trap_manager_t, flush, void,
387 private_trap_manager_t *this)
388 {
389 linked_list_t *traps;
390 /* since destroying the CHILD_SA results in events which require a read
391 * lock we cannot destroy the list while holding the write lock */
392 this->lock->write_lock(this->lock);
393 traps = this->traps;
394 this->traps = linked_list_create();
395 this->lock->unlock(this->lock);
396 traps->destroy_function(traps, (void*)destroy_entry);
397 }
398
399 METHOD(trap_manager_t, destroy, void,
400 private_trap_manager_t *this)
401 {
402 charon->bus->remove_listener(charon->bus, &this->listener.listener);
403 this->traps->destroy_function(this->traps, (void*)destroy_entry);
404 this->lock->destroy(this->lock);
405 free(this);
406 }
407
408 /**
409 * See header
410 */
411 trap_manager_t *trap_manager_create(void)
412 {
413 private_trap_manager_t *this;
414
415 INIT(this,
416 .public = {
417 .install = _install,
418 .uninstall = _uninstall,
419 .create_enumerator = _create_enumerator,
420 .acquire = _acquire,
421 .flush = _flush,
422 .destroy = _destroy,
423 },
424 .listener = {
425 .traps = this,
426 .listener = {
427 .ike_state_change = _ike_state_change,
428 .child_state_change = _child_state_change,
429 },
430 },
431 .traps = linked_list_create(),
432 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
433 );
434 charon->bus->add_listener(charon->bus, &this->listener.listener);
435
436 return &this->public;
437 }
438