a7ec167a4fe151c1a86c1ef416f5b37c32220a73
[strongswan.git] / src / libcharon / config / child_cfg.c
1 /*
2 * Copyright (C) 2016 Andreas Steffen
3 * Copyright (C) 2008-2016 Tobias Brunner
4 * Copyright (C) 2005-2007 Martin Willi
5 * Copyright (C) 2005 Jan Hutter
6 * HSR Hochschule fuer Technik Rapperswil
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * for more details.
17 */
18
19 #include "child_cfg.h"
20
21 #include <stdint.h>
22
23 #include <daemon.h>
24
25 ENUM(action_names, ACTION_NONE, ACTION_RESTART,
26 "clear",
27 "hold",
28 "restart",
29 );
30
31 /** Default replay window size, if not set using charon.replay_window */
32 #define DEFAULT_REPLAY_WINDOW 32
33
34 typedef struct private_child_cfg_t private_child_cfg_t;
35
36 /**
37 * Private data of an child_cfg_t object
38 */
39 struct private_child_cfg_t {
40
41 /**
42 * Public part
43 */
44 child_cfg_t public;
45
46 /**
47 * Number of references hold by others to this child_cfg
48 */
49 refcount_t refcount;
50
51 /**
52 * Name of the child_cfg, used to query it
53 */
54 char *name;
55
56 /**
57 * list for all proposals
58 */
59 linked_list_t *proposals;
60
61 /**
62 * list for traffic selectors for my site
63 */
64 linked_list_t *my_ts;
65
66 /**
67 * list for traffic selectors for others site
68 */
69 linked_list_t *other_ts;
70
71 /**
72 * updown script
73 */
74 char *updown;
75
76 /**
77 * allow host access
78 */
79 bool hostaccess;
80
81 /**
82 * Mode to propose for a initiated CHILD: tunnel/transport
83 */
84 ipsec_mode_t mode;
85
86 /**
87 * action to take to start CHILD_SA
88 */
89 action_t start_action;
90
91 /**
92 * action to take on DPD
93 */
94 action_t dpd_action;
95
96 /**
97 * action to take on CHILD_SA close
98 */
99 action_t close_action;
100
101 /**
102 * CHILD_SA lifetime config
103 */
104 lifetime_cfg_t lifetime;
105
106 /**
107 * enable IPComp
108 */
109 bool use_ipcomp;
110
111 /**
112 * Inactivity timeout
113 */
114 uint32_t inactivity;
115
116 /**
117 * Reqid to install CHILD_SA with
118 */
119 uint32_t reqid;
120
121 /**
122 * Optional mark to install inbound CHILD_SA with
123 */
124 mark_t mark_in;
125
126 /**
127 * Optional mark to install outbound CHILD_SA with
128 */
129 mark_t mark_out;
130
131 /**
132 * Traffic Flow Confidentiality padding, if enabled
133 */
134 uint32_t tfc;
135
136 /**
137 * Optional manually-set IPsec policy priorities
138 */
139 uint32_t manual_prio;
140
141 /**
142 * set up IPsec transport SA in MIPv6 proxy mode
143 */
144 bool proxy_mode;
145
146 /**
147 * enable installation and removal of kernel IPsec policies
148 */
149 bool install_policy;
150
151 /**
152 * anti-replay window size
153 */
154 uint32_t replay_window;
155 };
156
157 METHOD(child_cfg_t, get_name, char*,
158 private_child_cfg_t *this)
159 {
160 return this->name;
161 }
162
163 METHOD(child_cfg_t, add_proposal, void,
164 private_child_cfg_t *this, proposal_t *proposal)
165 {
166 if (proposal)
167 {
168 this->proposals->insert_last(this->proposals, proposal);
169 }
170 }
171
172 static bool match_proposal(proposal_t *item, proposal_t *proposal)
173 {
174 return item->equals(item, proposal);
175 }
176
177 METHOD(child_cfg_t, get_proposals, linked_list_t*,
178 private_child_cfg_t *this, bool strip_dh)
179 {
180 enumerator_t *enumerator;
181 proposal_t *current;
182 linked_list_t *proposals = linked_list_create();
183
184 enumerator = this->proposals->create_enumerator(this->proposals);
185 while (enumerator->enumerate(enumerator, &current))
186 {
187 current = current->clone(current);
188 if (strip_dh)
189 {
190 current->strip_dh(current, MODP_NONE);
191 }
192 if (proposals->find_first(proposals, (linked_list_match_t)match_proposal,
193 NULL, current) == SUCCESS)
194 {
195 current->destroy(current);
196 continue;
197 }
198 proposals->insert_last(proposals, current);
199 }
200 enumerator->destroy(enumerator);
201
202 DBG2(DBG_CFG, "configured proposals: %#P", proposals);
203
204 return proposals;
205 }
206
207 METHOD(child_cfg_t, select_proposal, proposal_t*,
208 private_child_cfg_t*this, linked_list_t *proposals, bool strip_dh,
209 bool private)
210 {
211 enumerator_t *stored_enum, *supplied_enum;
212 proposal_t *stored, *supplied, *selected = NULL;
213
214 stored_enum = this->proposals->create_enumerator(this->proposals);
215 supplied_enum = proposals->create_enumerator(proposals);
216
217 /* compare all stored proposals with all supplied. Stored ones are preferred. */
218 while (stored_enum->enumerate(stored_enum, &stored))
219 {
220 stored = stored->clone(stored);
221 while (supplied_enum->enumerate(supplied_enum, &supplied))
222 {
223 if (strip_dh)
224 {
225 stored->strip_dh(stored, MODP_NONE);
226 }
227 selected = stored->select(stored, supplied, private);
228 if (selected)
229 {
230 DBG2(DBG_CFG, "received proposals: %#P", proposals);
231 DBG2(DBG_CFG, "configured proposals: %#P", this->proposals);
232 DBG2(DBG_CFG, "selected proposal: %P", selected);
233 break;
234 }
235 }
236 stored->destroy(stored);
237 if (selected)
238 {
239 break;
240 }
241 supplied_enum->destroy(supplied_enum);
242 supplied_enum = proposals->create_enumerator(proposals);
243 }
244 stored_enum->destroy(stored_enum);
245 supplied_enum->destroy(supplied_enum);
246 if (selected == NULL)
247 {
248 DBG1(DBG_CFG, "received proposals: %#P", proposals);
249 DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
250 }
251 return selected;
252 }
253
254 METHOD(child_cfg_t, add_traffic_selector, void,
255 private_child_cfg_t *this, bool local, traffic_selector_t *ts)
256 {
257 if (local)
258 {
259 this->my_ts->insert_last(this->my_ts, ts);
260 }
261 else
262 {
263 this->other_ts->insert_last(this->other_ts, ts);
264 }
265 }
266
267 METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*,
268 private_child_cfg_t *this, bool local, linked_list_t *supplied,
269 linked_list_t *hosts)
270 {
271 enumerator_t *e1, *e2;
272 traffic_selector_t *ts1, *ts2, *selected;
273 linked_list_t *result, *derived;
274 host_t *host;
275
276 result = linked_list_create();
277 derived = linked_list_create();
278 if (local)
279 {
280 e1 = this->my_ts->create_enumerator(this->my_ts);
281 }
282 else
283 {
284 e1 = this->other_ts->create_enumerator(this->other_ts);
285 }
286 /* In a first step, replace "dynamic" TS with the host list */
287 while (e1->enumerate(e1, &ts1))
288 {
289 if (hosts && hosts->get_count(hosts) &&
290 ts1->is_dynamic(ts1))
291 {
292 e2 = hosts->create_enumerator(hosts);
293 while (e2->enumerate(e2, &host))
294 {
295 ts2 = ts1->clone(ts1);
296 ts2->set_address(ts2, host);
297 derived->insert_last(derived, ts2);
298 }
299 e2->destroy(e2);
300 }
301 else
302 {
303 derived->insert_last(derived, ts1->clone(ts1));
304 }
305 }
306 e1->destroy(e1);
307
308 DBG2(DBG_CFG, "%s traffic selectors for %s:",
309 supplied ? "selecting" : "proposing", local ? "us" : "other");
310 if (supplied == NULL)
311 {
312 while (derived->remove_first(derived, (void**)&ts1) == SUCCESS)
313 {
314 DBG2(DBG_CFG, " %R", ts1);
315 result->insert_last(result, ts1);
316 }
317 derived->destroy(derived);
318 }
319 else
320 {
321 e1 = derived->create_enumerator(derived);
322 e2 = supplied->create_enumerator(supplied);
323 /* enumerate all configured/derived selectors */
324 while (e1->enumerate(e1, &ts1))
325 {
326 /* enumerate all supplied traffic selectors */
327 while (e2->enumerate(e2, &ts2))
328 {
329 selected = ts1->get_subset(ts1, ts2);
330 if (selected)
331 {
332 DBG2(DBG_CFG, " config: %R, received: %R => match: %R",
333 ts1, ts2, selected);
334 result->insert_last(result, selected);
335 }
336 else
337 {
338 DBG2(DBG_CFG, " config: %R, received: %R => no match",
339 ts1, ts2);
340 }
341 }
342 supplied->reset_enumerator(supplied, e2);
343 }
344 e1->destroy(e1);
345 e2->destroy(e2);
346
347 /* check if we/peer did any narrowing, raise alert */
348 e1 = derived->create_enumerator(derived);
349 e2 = result->create_enumerator(result);
350 while (e1->enumerate(e1, &ts1))
351 {
352 if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2))
353 {
354 charon->bus->alert(charon->bus, ALERT_TS_NARROWED,
355 local, result, this);
356 break;
357 }
358 }
359 e1->destroy(e1);
360 e2->destroy(e2);
361
362 derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy));
363 }
364
365 /* remove any redundant traffic selectors in the list */
366 e1 = result->create_enumerator(result);
367 e2 = result->create_enumerator(result);
368 while (e1->enumerate(e1, &ts1))
369 {
370 while (e2->enumerate(e2, &ts2))
371 {
372 if (ts1 != ts2)
373 {
374 if (ts2->is_contained_in(ts2, ts1))
375 {
376 result->remove_at(result, e2);
377 ts2->destroy(ts2);
378 result->reset_enumerator(result, e1);
379 break;
380 }
381 if (ts1->is_contained_in(ts1, ts2))
382 {
383 result->remove_at(result, e1);
384 ts1->destroy(ts1);
385 break;
386 }
387 }
388 }
389 result->reset_enumerator(result, e2);
390 }
391 e1->destroy(e1);
392 e2->destroy(e2);
393
394 return result;
395 }
396
397 METHOD(child_cfg_t, get_updown, char*,
398 private_child_cfg_t *this)
399 {
400 return this->updown;
401 }
402
403 METHOD(child_cfg_t, get_hostaccess, bool,
404 private_child_cfg_t *this)
405 {
406 return this->hostaccess;
407 }
408
409 /**
410 * Applies jitter to the rekey value. Returns the new rekey value.
411 * Note: The distribution of random values is not perfect, but it
412 * should get the job done.
413 */
414 static uint64_t apply_jitter(uint64_t rekey, uint64_t jitter)
415 {
416 if (jitter == 0)
417 {
418 return rekey;
419 }
420 jitter = (jitter == UINT64_MAX) ? jitter : jitter + 1;
421 return rekey - jitter * (random() / (RAND_MAX + 1.0));
422 }
423 #define APPLY_JITTER(l) l.rekey = apply_jitter(l.rekey, l.jitter)
424
425 METHOD(child_cfg_t, get_lifetime, lifetime_cfg_t*,
426 private_child_cfg_t *this)
427 {
428 lifetime_cfg_t *lft = malloc_thing(lifetime_cfg_t);
429 memcpy(lft, &this->lifetime, sizeof(lifetime_cfg_t));
430 APPLY_JITTER(lft->time);
431 APPLY_JITTER(lft->bytes);
432 APPLY_JITTER(lft->packets);
433 return lft;
434 }
435
436 METHOD(child_cfg_t, get_mode, ipsec_mode_t,
437 private_child_cfg_t *this)
438 {
439 return this->mode;
440 }
441
442 METHOD(child_cfg_t, get_start_action, action_t,
443 private_child_cfg_t *this)
444 {
445 return this->start_action;
446 }
447
448 METHOD(child_cfg_t, get_dpd_action, action_t,
449 private_child_cfg_t *this)
450 {
451 return this->dpd_action;
452 }
453
454 METHOD(child_cfg_t, get_close_action, action_t,
455 private_child_cfg_t *this)
456 {
457 return this->close_action;
458 }
459
460 METHOD(child_cfg_t, get_dh_group, diffie_hellman_group_t,
461 private_child_cfg_t *this)
462 {
463 enumerator_t *enumerator;
464 proposal_t *proposal;
465 uint16_t dh_group = MODP_NONE;
466
467 enumerator = this->proposals->create_enumerator(this->proposals);
468 while (enumerator->enumerate(enumerator, &proposal))
469 {
470 if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &dh_group, NULL))
471 {
472 break;
473 }
474 }
475 enumerator->destroy(enumerator);
476 return dh_group;
477 }
478
479 METHOD(child_cfg_t, use_ipcomp, bool,
480 private_child_cfg_t *this)
481 {
482 return this->use_ipcomp;
483 }
484
485 METHOD(child_cfg_t, get_inactivity, uint32_t,
486 private_child_cfg_t *this)
487 {
488 return this->inactivity;
489 }
490
491 METHOD(child_cfg_t, get_reqid, uint32_t,
492 private_child_cfg_t *this)
493 {
494 return this->reqid;
495 }
496
497 METHOD(child_cfg_t, get_mark, mark_t,
498 private_child_cfg_t *this, bool inbound)
499 {
500 return inbound ? this->mark_in : this->mark_out;
501 }
502
503 METHOD(child_cfg_t, get_tfc, uint32_t,
504 private_child_cfg_t *this)
505 {
506 return this->tfc;
507 }
508
509 METHOD(child_cfg_t, get_manual_prio, uint32_t,
510 private_child_cfg_t *this)
511 {
512 return this->manual_prio;
513 }
514
515 METHOD(child_cfg_t, get_replay_window, uint32_t,
516 private_child_cfg_t *this)
517 {
518 return this->replay_window;
519 }
520
521 METHOD(child_cfg_t, set_replay_window, void,
522 private_child_cfg_t *this, uint32_t replay_window)
523 {
524 this->replay_window = replay_window;
525 }
526
527 METHOD(child_cfg_t, use_proxy_mode, bool,
528 private_child_cfg_t *this)
529 {
530 return this->proxy_mode;
531 }
532
533 METHOD(child_cfg_t, install_policy, bool,
534 private_child_cfg_t *this)
535 {
536 return this->install_policy;
537 }
538
539 #define LT_PART_EQUALS(a, b) ({ a.life == b.life && a.rekey == b.rekey && a.jitter == b.jitter; })
540 #define LIFETIME_EQUALS(a, b) ({ LT_PART_EQUALS(a.time, b.time) && LT_PART_EQUALS(a.bytes, b.bytes) && LT_PART_EQUALS(a.packets, b.packets); })
541
542 METHOD(child_cfg_t, equals, bool,
543 private_child_cfg_t *this, child_cfg_t *other_pub)
544 {
545 private_child_cfg_t *other = (private_child_cfg_t*)other_pub;
546
547 if (this == other)
548 {
549 return TRUE;
550 }
551 if (this->public.equals != other->public.equals)
552 {
553 return FALSE;
554 }
555 if (!this->proposals->equals_offset(this->proposals, other->proposals,
556 offsetof(proposal_t, equals)))
557 {
558 return FALSE;
559 }
560 if (!this->my_ts->equals_offset(this->my_ts, other->my_ts,
561 offsetof(traffic_selector_t, equals)))
562 {
563 return FALSE;
564 }
565 if (!this->other_ts->equals_offset(this->other_ts, other->other_ts,
566 offsetof(traffic_selector_t, equals)))
567 {
568 return FALSE;
569 }
570 return this->hostaccess == other->hostaccess &&
571 this->mode == other->mode &&
572 this->start_action == other->start_action &&
573 this->dpd_action == other->dpd_action &&
574 this->close_action == other->close_action &&
575 LIFETIME_EQUALS(this->lifetime, other->lifetime) &&
576 this->use_ipcomp == other->use_ipcomp &&
577 this->inactivity == other->inactivity &&
578 this->reqid == other->reqid &&
579 this->mark_in.value == other->mark_in.value &&
580 this->mark_in.mask == other->mark_in.mask &&
581 this->mark_out.value == other->mark_out.value &&
582 this->mark_out.mask == other->mark_out.mask &&
583 this->tfc == other->tfc &&
584 this->manual_prio == other->manual_prio &&
585 this->replay_window == other->replay_window &&
586 this->proxy_mode == other->proxy_mode &&
587 this->install_policy == other->install_policy &&
588 streq(this->updown, other->updown);
589 }
590
591 METHOD(child_cfg_t, get_ref, child_cfg_t*,
592 private_child_cfg_t *this)
593 {
594 ref_get(&this->refcount);
595 return &this->public;
596 }
597
598 METHOD(child_cfg_t, destroy, void,
599 private_child_cfg_t *this)
600 {
601 if (ref_put(&this->refcount))
602 {
603 this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
604 this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy));
605 this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy));
606 if (this->updown)
607 {
608 free(this->updown);
609 }
610 free(this->name);
611 free(this);
612 }
613 }
614
615 /*
616 * Described in header-file
617 */
618 child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
619 {
620 private_child_cfg_t *this;
621
622 INIT(this,
623 .public = {
624 .get_name = _get_name,
625 .add_traffic_selector = _add_traffic_selector,
626 .get_traffic_selectors = _get_traffic_selectors,
627 .add_proposal = _add_proposal,
628 .get_proposals = _get_proposals,
629 .select_proposal = _select_proposal,
630 .get_updown = _get_updown,
631 .get_hostaccess = _get_hostaccess,
632 .get_mode = _get_mode,
633 .get_start_action = _get_start_action,
634 .get_dpd_action = _get_dpd_action,
635 .get_close_action = _get_close_action,
636 .get_lifetime = _get_lifetime,
637 .get_dh_group = _get_dh_group,
638 .use_ipcomp = _use_ipcomp,
639 .get_inactivity = _get_inactivity,
640 .get_reqid = _get_reqid,
641 .get_mark = _get_mark,
642 .get_tfc = _get_tfc,
643 .get_manual_prio = _get_manual_prio,
644 .get_replay_window = _get_replay_window,
645 .set_replay_window = _set_replay_window,
646 .use_proxy_mode = _use_proxy_mode,
647 .install_policy = _install_policy,
648 .equals = _equals,
649 .get_ref = _get_ref,
650 .destroy = _destroy,
651 },
652 .name = strdup(name),
653 .updown = strdupnull(data->updown),
654 .hostaccess = data->hostaccess,
655 .reqid = data->reqid,
656 .mode = data->mode,
657 .proxy_mode = data->proxy_mode,
658 .start_action = data->start_action,
659 .dpd_action = data->dpd_action,
660 .close_action = data->close_action,
661 .mark_in = data->mark_in,
662 .mark_out = data->mark_out,
663 .lifetime = data->lifetime,
664 .inactivity = data->inactivity,
665 .use_ipcomp = data->ipcomp,
666 .tfc = data->tfc,
667 .manual_prio = data->priority,
668 .install_policy = !data->suppress_policies,
669 .refcount = 1,
670 .proposals = linked_list_create(),
671 .my_ts = linked_list_create(),
672 .other_ts = linked_list_create(),
673 .replay_window = lib->settings->get_int(lib->settings,
674 "%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns),
675 );
676
677 return &this->public;
678 }