d8083d433216d60c919a60af0136409c323b8442
[strongswan.git] / src / libcharon / config / child_cfg.c
1 /*
2 * Copyright (C) 2008-2017 Tobias Brunner
3 * Copyright (C) 2016 Andreas Steffen
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 * Options
58 */
59 child_cfg_option_t options;
60
61 /**
62 * list for all proposals
63 */
64 linked_list_t *proposals;
65
66 /**
67 * list for traffic selectors for my site
68 */
69 linked_list_t *my_ts;
70
71 /**
72 * list for traffic selectors for others site
73 */
74 linked_list_t *other_ts;
75
76 /**
77 * updown script
78 */
79 char *updown;
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 * Inactivity timeout
108 */
109 uint32_t inactivity;
110
111 /**
112 * Reqid to install CHILD_SA with
113 */
114 uint32_t reqid;
115
116 /**
117 * Optional mark to install inbound CHILD_SA with
118 */
119 mark_t mark_in;
120
121 /**
122 * Optional mark to install outbound CHILD_SA with
123 */
124 mark_t mark_out;
125
126 /**
127 * Traffic Flow Confidentiality padding, if enabled
128 */
129 uint32_t tfc;
130
131 /**
132 * Optional manually-set IPsec policy priorities
133 */
134 uint32_t manual_prio;
135
136 /**
137 * Optional restriction of IPsec policy to a given network interface
138 */
139 char *interface;
140
141 /**
142 * anti-replay window size
143 */
144 uint32_t replay_window;
145
146 /**
147 * HW offload mode
148 */
149 hw_offload_t hw_offload;
150 };
151
152 METHOD(child_cfg_t, get_name, char*,
153 private_child_cfg_t *this)
154 {
155 return this->name;
156 }
157
158 METHOD(child_cfg_t, has_option, bool,
159 private_child_cfg_t *this, child_cfg_option_t option)
160 {
161 return this->options & option;
162 }
163
164 METHOD(child_cfg_t, add_proposal, void,
165 private_child_cfg_t *this, proposal_t *proposal)
166 {
167 if (proposal)
168 {
169 this->proposals->insert_last(this->proposals, proposal);
170 }
171 }
172
173 CALLBACK(match_proposal, bool,
174 proposal_t *item, va_list args)
175 {
176 proposal_t *proposal;
177
178 VA_ARGS_VGET(args, proposal);
179 return item->equals(item, proposal);
180 }
181
182 METHOD(child_cfg_t, get_proposals, linked_list_t*,
183 private_child_cfg_t *this, bool strip_dh)
184 {
185 enumerator_t *enumerator;
186 proposal_t *current;
187 linked_list_t *proposals = linked_list_create();
188
189 enumerator = this->proposals->create_enumerator(this->proposals);
190 while (enumerator->enumerate(enumerator, &current))
191 {
192 current = current->clone(current);
193 if (strip_dh)
194 {
195 current->strip_dh(current, MODP_NONE);
196 }
197 if (proposals->find_first(proposals, match_proposal, NULL, current))
198 {
199 current->destroy(current);
200 continue;
201 }
202 proposals->insert_last(proposals, current);
203 }
204 enumerator->destroy(enumerator);
205
206 DBG2(DBG_CFG, "configured proposals: %#P", proposals);
207
208 return proposals;
209 }
210
211 METHOD(child_cfg_t, select_proposal, proposal_t*,
212 private_child_cfg_t*this, linked_list_t *proposals, bool strip_dh,
213 bool private, bool prefer_self)
214 {
215 enumerator_t *prefer_enum, *match_enum;
216 proposal_t *proposal, *match, *selected = NULL;
217
218 if (prefer_self)
219 {
220 prefer_enum = this->proposals->create_enumerator(this->proposals);
221 match_enum = proposals->create_enumerator(proposals);
222 }
223 else
224 {
225 prefer_enum = proposals->create_enumerator(proposals);
226 match_enum = this->proposals->create_enumerator(this->proposals);
227 }
228
229 while (prefer_enum->enumerate(prefer_enum, &proposal))
230 {
231 proposal = proposal->clone(proposal);
232 if (strip_dh)
233 {
234 proposal->strip_dh(proposal, MODP_NONE);
235 }
236 if (prefer_self)
237 {
238 proposals->reset_enumerator(proposals, match_enum);
239 }
240 else
241 {
242 this->proposals->reset_enumerator(this->proposals, match_enum);
243 }
244 while (match_enum->enumerate(match_enum, &match))
245 {
246 match = match->clone(match);
247 if (strip_dh)
248 {
249 match->strip_dh(match, MODP_NONE);
250 }
251 selected = proposal->select(proposal, match, prefer_self, private);
252 match->destroy(match);
253 if (selected)
254 {
255 DBG2(DBG_CFG, "received proposals: %#P", proposals);
256 DBG2(DBG_CFG, "configured proposals: %#P", this->proposals);
257 DBG1(DBG_CFG, "selected proposal: %P", selected);
258 break;
259 }
260 }
261 proposal->destroy(proposal);
262 if (selected)
263 {
264 break;
265 }
266 }
267 prefer_enum->destroy(prefer_enum);
268 match_enum->destroy(match_enum);
269 if (!selected)
270 {
271 DBG1(DBG_CFG, "received proposals: %#P", proposals);
272 DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
273 }
274 return selected;
275 }
276
277 METHOD(child_cfg_t, add_traffic_selector, void,
278 private_child_cfg_t *this, bool local, traffic_selector_t *ts)
279 {
280 if (local)
281 {
282 this->my_ts->insert_last(this->my_ts, ts);
283 }
284 else
285 {
286 this->other_ts->insert_last(this->other_ts, ts);
287 }
288 }
289
290 METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*,
291 private_child_cfg_t *this, bool local, linked_list_t *supplied,
292 linked_list_t *hosts, bool log)
293 {
294 enumerator_t *e1, *e2;
295 traffic_selector_t *ts1, *ts2, *selected;
296 linked_list_t *result, *derived;
297 host_t *host;
298
299 result = linked_list_create();
300 derived = linked_list_create();
301 if (local)
302 {
303 e1 = this->my_ts->create_enumerator(this->my_ts);
304 }
305 else
306 {
307 e1 = this->other_ts->create_enumerator(this->other_ts);
308 }
309 /* in a first step, replace "dynamic" TS with the host list */
310 while (e1->enumerate(e1, &ts1))
311 {
312 if (hosts && hosts->get_count(hosts))
313 { /* set hosts if TS is dynamic or as initiator in transport mode */
314 bool dynamic = ts1->is_dynamic(ts1),
315 proxy_mode = has_option(this, OPT_PROXY_MODE);
316 if (dynamic || (this->mode == MODE_TRANSPORT && !proxy_mode &&
317 !supplied))
318 {
319 e2 = hosts->create_enumerator(hosts);
320 while (e2->enumerate(e2, &host))
321 {
322 ts2 = ts1->clone(ts1);
323 if (dynamic || !host->is_anyaddr(host))
324 { /* don't make regular TS larger than they were */
325 ts2->set_address(ts2, host);
326 }
327 derived->insert_last(derived, ts2);
328 }
329 e2->destroy(e2);
330 continue;
331 }
332 }
333 derived->insert_last(derived, ts1->clone(ts1));
334 }
335 e1->destroy(e1);
336
337 if (log)
338 {
339 DBG2(DBG_CFG, "%s traffic selectors for %s:",
340 supplied ? "selecting" : "proposing", local ? "us" : "other");
341 }
342 if (!supplied)
343 {
344 while (derived->remove_first(derived, (void**)&ts1) == SUCCESS)
345 {
346 if (log)
347 {
348 DBG2(DBG_CFG, " %R", ts1);
349 }
350 result->insert_last(result, ts1);
351 }
352 derived->destroy(derived);
353 }
354 else
355 {
356 e1 = derived->create_enumerator(derived);
357 e2 = supplied->create_enumerator(supplied);
358 /* enumerate all configured/derived selectors */
359 while (e1->enumerate(e1, &ts1))
360 {
361 /* enumerate all supplied traffic selectors */
362 while (e2->enumerate(e2, &ts2))
363 {
364 selected = ts1->get_subset(ts1, ts2);
365 if (selected)
366 {
367 if (log)
368 {
369 DBG2(DBG_CFG, " config: %R, received: %R => match: %R",
370 ts1, ts2, selected);
371 }
372 result->insert_last(result, selected);
373 }
374 else if (log)
375 {
376 DBG2(DBG_CFG, " config: %R, received: %R => no match",
377 ts1, ts2);
378 }
379 }
380 supplied->reset_enumerator(supplied, e2);
381 }
382 e1->destroy(e1);
383 e2->destroy(e2);
384
385 /* check if we/peer did any narrowing, raise alert */
386 e1 = derived->create_enumerator(derived);
387 e2 = result->create_enumerator(result);
388 while (e1->enumerate(e1, &ts1))
389 {
390 if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2))
391 {
392 charon->bus->alert(charon->bus, ALERT_TS_NARROWED,
393 local, result, this);
394 break;
395 }
396 }
397 e1->destroy(e1);
398 e2->destroy(e2);
399
400 derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy));
401 }
402
403 /* remove any redundant traffic selectors in the list */
404 e1 = result->create_enumerator(result);
405 e2 = result->create_enumerator(result);
406 while (e1->enumerate(e1, &ts1))
407 {
408 while (e2->enumerate(e2, &ts2))
409 {
410 if (ts1 != ts2)
411 {
412 if (ts2->is_contained_in(ts2, ts1))
413 {
414 result->remove_at(result, e2);
415 ts2->destroy(ts2);
416 result->reset_enumerator(result, e1);
417 break;
418 }
419 if (ts1->is_contained_in(ts1, ts2))
420 {
421 result->remove_at(result, e1);
422 ts1->destroy(ts1);
423 break;
424 }
425 }
426 }
427 result->reset_enumerator(result, e2);
428 }
429 e1->destroy(e1);
430 e2->destroy(e2);
431
432 return result;
433 }
434
435 METHOD(child_cfg_t, get_updown, char*,
436 private_child_cfg_t *this)
437 {
438 return this->updown;
439 }
440
441 /**
442 * Applies jitter to the rekey value. Returns the new rekey value.
443 * Note: The distribution of random values is not perfect, but it
444 * should get the job done.
445 */
446 static uint64_t apply_jitter(uint64_t rekey, uint64_t jitter)
447 {
448 if (jitter == 0)
449 {
450 return rekey;
451 }
452 jitter = (jitter == UINT64_MAX) ? jitter : jitter + 1;
453 return rekey - jitter * (random() / (RAND_MAX + 1.0));
454 }
455 #define APPLY_JITTER(l) l.rekey = apply_jitter(l.rekey, l.jitter)
456
457 METHOD(child_cfg_t, get_lifetime, lifetime_cfg_t*,
458 private_child_cfg_t *this, bool jitter)
459 {
460 lifetime_cfg_t *lft = malloc_thing(lifetime_cfg_t);
461 memcpy(lft, &this->lifetime, sizeof(lifetime_cfg_t));
462 if (!jitter)
463 {
464 lft->time.jitter = lft->bytes.jitter = lft->packets.jitter = 0;
465 }
466 APPLY_JITTER(lft->time);
467 APPLY_JITTER(lft->bytes);
468 APPLY_JITTER(lft->packets);
469 return lft;
470 }
471
472 METHOD(child_cfg_t, get_mode, ipsec_mode_t,
473 private_child_cfg_t *this)
474 {
475 return this->mode;
476 }
477
478 METHOD(child_cfg_t, get_start_action, action_t,
479 private_child_cfg_t *this)
480 {
481 return this->start_action;
482 }
483
484 METHOD(child_cfg_t, get_hw_offload, hw_offload_t,
485 private_child_cfg_t *this)
486 {
487 return this->hw_offload;
488 }
489
490 METHOD(child_cfg_t, get_dpd_action, action_t,
491 private_child_cfg_t *this)
492 {
493 return this->dpd_action;
494 }
495
496 METHOD(child_cfg_t, get_close_action, action_t,
497 private_child_cfg_t *this)
498 {
499 return this->close_action;
500 }
501
502 METHOD(child_cfg_t, get_dh_group, diffie_hellman_group_t,
503 private_child_cfg_t *this)
504 {
505 enumerator_t *enumerator;
506 proposal_t *proposal;
507 uint16_t dh_group = MODP_NONE;
508
509 enumerator = this->proposals->create_enumerator(this->proposals);
510 while (enumerator->enumerate(enumerator, &proposal))
511 {
512 if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &dh_group, NULL))
513 {
514 break;
515 }
516 }
517 enumerator->destroy(enumerator);
518 return dh_group;
519 }
520
521 METHOD(child_cfg_t, get_inactivity, uint32_t,
522 private_child_cfg_t *this)
523 {
524 return this->inactivity;
525 }
526
527 METHOD(child_cfg_t, get_reqid, uint32_t,
528 private_child_cfg_t *this)
529 {
530 return this->reqid;
531 }
532
533 METHOD(child_cfg_t, get_mark, mark_t,
534 private_child_cfg_t *this, bool inbound)
535 {
536 return inbound ? this->mark_in : this->mark_out;
537 }
538
539 METHOD(child_cfg_t, get_tfc, uint32_t,
540 private_child_cfg_t *this)
541 {
542 return this->tfc;
543 }
544
545 METHOD(child_cfg_t, get_manual_prio, uint32_t,
546 private_child_cfg_t *this)
547 {
548 return this->manual_prio;
549 }
550
551 METHOD(child_cfg_t, get_interface, char*,
552 private_child_cfg_t *this)
553 {
554 return this->interface;
555 }
556
557 METHOD(child_cfg_t, get_replay_window, uint32_t,
558 private_child_cfg_t *this)
559 {
560 return this->replay_window;
561 }
562
563 METHOD(child_cfg_t, set_replay_window, void,
564 private_child_cfg_t *this, uint32_t replay_window)
565 {
566 this->replay_window = replay_window;
567 }
568
569 #define LT_PART_EQUALS(a, b) ({ a.life == b.life && a.rekey == b.rekey && a.jitter == b.jitter; })
570 #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); })
571
572 METHOD(child_cfg_t, equals, bool,
573 private_child_cfg_t *this, child_cfg_t *other_pub)
574 {
575 private_child_cfg_t *other = (private_child_cfg_t*)other_pub;
576
577 if (this == other)
578 {
579 return TRUE;
580 }
581 if (this->public.equals != other->public.equals)
582 {
583 return FALSE;
584 }
585 if (!this->proposals->equals_offset(this->proposals, other->proposals,
586 offsetof(proposal_t, equals)))
587 {
588 return FALSE;
589 }
590 if (!this->my_ts->equals_offset(this->my_ts, other->my_ts,
591 offsetof(traffic_selector_t, equals)))
592 {
593 return FALSE;
594 }
595 if (!this->other_ts->equals_offset(this->other_ts, other->other_ts,
596 offsetof(traffic_selector_t, equals)))
597 {
598 return FALSE;
599 }
600 return this->options == other->options &&
601 this->mode == other->mode &&
602 this->start_action == other->start_action &&
603 this->dpd_action == other->dpd_action &&
604 this->close_action == other->close_action &&
605 LIFETIME_EQUALS(this->lifetime, other->lifetime) &&
606 this->inactivity == other->inactivity &&
607 this->reqid == other->reqid &&
608 this->mark_in.value == other->mark_in.value &&
609 this->mark_in.mask == other->mark_in.mask &&
610 this->mark_out.value == other->mark_out.value &&
611 this->mark_out.mask == other->mark_out.mask &&
612 this->tfc == other->tfc &&
613 this->manual_prio == other->manual_prio &&
614 this->replay_window == other->replay_window &&
615 streq(this->updown, other->updown) &&
616 streq(this->interface, other->interface);
617 }
618
619 METHOD(child_cfg_t, get_ref, child_cfg_t*,
620 private_child_cfg_t *this)
621 {
622 ref_get(&this->refcount);
623 return &this->public;
624 }
625
626 METHOD(child_cfg_t, destroy, void,
627 private_child_cfg_t *this)
628 {
629 if (ref_put(&this->refcount))
630 {
631 this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
632 this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy));
633 this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy));
634 free(this->updown);
635 free(this->interface);
636 free(this->name);
637 free(this);
638 }
639 }
640
641 /*
642 * Described in header-file
643 */
644 child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
645 {
646 private_child_cfg_t *this;
647
648 INIT(this,
649 .public = {
650 .get_name = _get_name,
651 .add_traffic_selector = _add_traffic_selector,
652 .get_traffic_selectors = _get_traffic_selectors,
653 .add_proposal = _add_proposal,
654 .get_proposals = _get_proposals,
655 .select_proposal = _select_proposal,
656 .get_updown = _get_updown,
657 .get_mode = _get_mode,
658 .get_start_action = _get_start_action,
659 .get_dpd_action = _get_dpd_action,
660 .get_close_action = _get_close_action,
661 .get_lifetime = _get_lifetime,
662 .get_dh_group = _get_dh_group,
663 .get_inactivity = _get_inactivity,
664 .get_reqid = _get_reqid,
665 .get_mark = _get_mark,
666 .get_tfc = _get_tfc,
667 .get_manual_prio = _get_manual_prio,
668 .get_interface = _get_interface,
669 .get_replay_window = _get_replay_window,
670 .set_replay_window = _set_replay_window,
671 .has_option = _has_option,
672 .equals = _equals,
673 .get_ref = _get_ref,
674 .destroy = _destroy,
675 .get_hw_offload = _get_hw_offload,
676 },
677 .name = strdup(name),
678 .options = data->options,
679 .updown = strdupnull(data->updown),
680 .reqid = data->reqid,
681 .mode = data->mode,
682 .start_action = data->start_action,
683 .dpd_action = data->dpd_action,
684 .close_action = data->close_action,
685 .mark_in = data->mark_in,
686 .mark_out = data->mark_out,
687 .lifetime = data->lifetime,
688 .inactivity = data->inactivity,
689 .tfc = data->tfc,
690 .manual_prio = data->priority,
691 .interface = strdupnull(data->interface),
692 .refcount = 1,
693 .proposals = linked_list_create(),
694 .my_ts = linked_list_create(),
695 .other_ts = linked_list_create(),
696 .replay_window = lib->settings->get_int(lib->settings,
697 "%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns),
698 .hw_offload = data->hw_offload,
699 );
700
701 return &this->public;
702 }