fixed some rekey collision issues
[strongswan.git] / src / charon / sa / tasks / ike_rekey.c
1 /**
2 * @file ike_rekey.c
3 *
4 * @brief Implementation of the ike_rekey task.
5 *
6 */
7
8 /*
9 * Copyright (C) 2005-2007 Martin Willi
10 * Copyright (C) 2005 Jan Hutter
11 * Hochschule fuer Technik Rapperswil
12 *
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2 of the License, or (at your
16 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
17 *
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * for more details.
22 */
23
24 #include "ike_rekey.h"
25
26 #include <daemon.h>
27 #include <encoding/payloads/notify_payload.h>
28 #include <sa/tasks/ike_init.h>
29 #include <queues/jobs/delete_ike_sa_job.h>
30 #include <queues/jobs/rekey_ike_sa_job.h>
31
32
33 typedef struct private_ike_rekey_t private_ike_rekey_t;
34
35 /**
36 * Private members of a ike_rekey_t task.
37 */
38 struct private_ike_rekey_t {
39
40 /**
41 * Public methods and task_t interface.
42 */
43 ike_rekey_t public;
44
45 /**
46 * Assigned IKE_SA.
47 */
48 ike_sa_t *ike_sa;
49
50 /**
51 * New IKE_SA which replaces the current one
52 */
53 ike_sa_t *new_sa;
54
55 /**
56 * Are we the initiator?
57 */
58 bool initiator;
59
60 /**
61 * the IKE_INIT task which is reused to simplify rekeying
62 */
63 ike_init_t *ike_init;
64
65 /**
66 * colliding task detected by the task manager
67 */
68 task_t *collision;
69 };
70
71 /**
72 * Implementation of task_t.build for initiator
73 */
74 static status_t build_i(private_ike_rekey_t *this, message_t *message)
75 {
76 connection_t *connection;
77 policy_t *policy;
78 ike_sa_id_t *id;
79
80 id = ike_sa_id_create(0, 0, TRUE);
81 this->new_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id);
82 id->destroy(id);
83
84 connection = this->ike_sa->get_connection(this->ike_sa);
85 policy = this->ike_sa->get_policy(this->ike_sa);
86 this->new_sa->set_connection(this->new_sa, connection);
87 this->new_sa->set_policy(this->new_sa, policy);
88
89 this->ike_init = ike_init_create(this->new_sa, TRUE, this->ike_sa);
90 this->ike_init->task.build(&this->ike_init->task, message);
91
92 this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
93
94 return NEED_MORE;
95 }
96
97 /**
98 * Implementation of task_t.process for initiator
99 */
100 static status_t process_r(private_ike_rekey_t *this, message_t *message)
101 {
102 connection_t *connection;
103 policy_t *policy;
104 ike_sa_id_t *id;
105 iterator_t *iterator;
106 child_sa_t *child_sa;
107
108 if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
109 {
110 DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
111 return NEED_MORE;
112 }
113
114 iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa);
115 while (iterator->iterate(iterator, (void**)&child_sa))
116 {
117 switch (child_sa->get_state(child_sa))
118 {
119 case CHILD_CREATED:
120 case CHILD_REKEYING:
121 case CHILD_DELETING:
122 /* we do not allow rekeying while we have children in-progress */
123 DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
124 iterator->destroy(iterator);
125 return NEED_MORE;
126 default:
127 break;
128 }
129 }
130 iterator->destroy(iterator);
131
132 id = ike_sa_id_create(0, 0, FALSE);
133 this->new_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id);
134 id->destroy(id);
135
136 connection = this->ike_sa->get_connection(this->ike_sa);
137 policy = this->ike_sa->get_policy(this->ike_sa);
138 this->new_sa->set_connection(this->new_sa, connection);
139 this->new_sa->set_policy(this->new_sa, policy);
140
141 this->ike_init = ike_init_create(this->new_sa, FALSE, this->ike_sa);
142 this->ike_init->task.process(&this->ike_init->task, message);
143
144 return NEED_MORE;
145 }
146
147 /**
148 * Implementation of task_t.build for responder
149 */
150 static status_t build_r(private_ike_rekey_t *this, message_t *message)
151 {
152 if (this->new_sa == NULL)
153 {
154 /* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */
155 message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
156 return SUCCESS;
157 }
158
159 if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED)
160 {
161 return SUCCESS;
162 }
163
164 this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
165 this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED);
166
167 return SUCCESS;
168 }
169
170 /**
171 * Implementation of task_t.process for initiator
172 */
173 static status_t process_i(private_ike_rekey_t *this, message_t *message)
174 {
175 job_t *job;
176 ike_sa_id_t *to_delete;
177
178 if (this->ike_init->task.process(&this->ike_init->task, message) == FAILED)
179 {
180 /* rekeying failed, fallback to old SA */
181 if (!(this->collision &&
182 this->collision->get_type(this->collision) == IKE_DELETE))
183 {
184 job_t *job;
185 u_int32_t retry = charon->configuration->get_retry_interval(
186 charon->configuration);
187 job = (job_t*)rekey_ike_sa_job_create(
188 this->ike_sa->get_id(this->ike_sa), FALSE);
189 DBG1(DBG_IKE, "IKE_SA rekeying failed, "
190 "trying again in %d seconds", retry);
191 this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
192 charon->event_queue->add_relative(charon->event_queue, job, retry * 1000);
193 }
194 return SUCCESS;
195 }
196
197 this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED);
198 to_delete = this->ike_sa->get_id(this->ike_sa);
199
200 /* check for collisions */
201 if (this->collision &&
202 this->collision->get_type(this->collision) == IKE_REKEY)
203 {
204 chunk_t this_nonce, other_nonce;
205 host_t *host;
206 private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision;
207
208 this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
209 other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
210
211 /* if we have the lower nonce, delete rekeyed SA. If not, delete
212 * the redundant. */
213 if (memcmp(this_nonce.ptr, other_nonce.ptr,
214 min(this_nonce.len, other_nonce.len)) < 0)
215 {
216 DBG1(DBG_IKE, "IKE_SA rekey collision won, deleting rekeyed IKE_SA");
217 charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
218 }
219 else
220 {
221 DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant IKE_SA");
222 /* apply host for a proper delete */
223 host = this->ike_sa->get_my_host(this->ike_sa);
224 this->new_sa->set_my_host(this->new_sa, host->clone(host));
225 host = this->ike_sa->get_other_host(this->ike_sa);
226 this->new_sa->set_other_host(this->new_sa, host->clone(host));
227 this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
228 to_delete = this->new_sa->get_id(this->new_sa);
229 charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
230 /* inherit to other->new_sa in destroy() */
231 this->new_sa = other->new_sa;
232 other->new_sa = NULL;
233 }
234 }
235
236 job = (job_t*)delete_ike_sa_job_create(to_delete, TRUE);
237 charon->job_queue->add(charon->job_queue, job);
238
239 return SUCCESS;
240 }
241
242 /**
243 * Implementation of task_t.get_type
244 */
245 static task_type_t get_type(private_ike_rekey_t *this)
246 {
247 return IKE_REKEY;
248 }
249
250 static void collide(private_ike_rekey_t* this, task_t *other)
251 {
252 DESTROY_IF(this->collision);
253 this->collision = other;
254 }
255
256 /**
257 * Implementation of task_t.migrate
258 */
259 static void migrate(private_ike_rekey_t *this, ike_sa_t *ike_sa)
260 {
261 if (this->ike_init)
262 {
263 this->ike_init->task.destroy(&this->ike_init->task);
264 }
265 if (this->new_sa)
266 {
267 charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
268 this->new_sa);
269 }
270 DESTROY_IF(this->collision);
271
272 this->collision = NULL;
273 this->ike_sa = ike_sa;
274 this->new_sa = NULL;
275 this->ike_init = NULL;
276 }
277
278 /**
279 * Implementation of task_t.destroy
280 */
281 static void destroy(private_ike_rekey_t *this)
282 {
283 if (this->new_sa)
284 {
285 if (this->new_sa->get_state(this->new_sa) == IKE_ESTABLISHED)
286 {
287 this->new_sa->inherit(this->new_sa, this->ike_sa);
288 charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
289 }
290 else
291 {
292 charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
293 this->new_sa);
294 }
295 }
296 if (this->ike_init)
297 {
298 this->ike_init->task.destroy(&this->ike_init->task);
299 }
300 DESTROY_IF(this->collision);
301 free(this);
302 }
303
304 /*
305 * Described in header.
306 */
307 ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator)
308 {
309 private_ike_rekey_t *this = malloc_thing(private_ike_rekey_t);
310
311 this->public.collide = (void(*)(ike_rekey_t*,task_t*))collide;
312 this->public.task.get_type = (task_type_t(*)(task_t*))get_type;
313 this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate;
314 this->public.task.destroy = (void(*)(task_t*))destroy;
315 if (initiator)
316 {
317 this->public.task.build = (status_t(*)(task_t*,message_t*))build_i;
318 this->public.task.process = (status_t(*)(task_t*,message_t*))process_i;
319 }
320 else
321 {
322 this->public.task.build = (status_t(*)(task_t*,message_t*))build_r;
323 this->public.task.process = (status_t(*)(task_t*,message_t*))process_r;
324 }
325
326 this->ike_sa = ike_sa;
327 this->new_sa = NULL;
328 this->ike_init = NULL;
329 this->initiator = initiator;
330 this->collision = NULL;
331
332 return &this->public;
333 }