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