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