child-cfg: Add setting that controls whether outbound FWD policies are installed
[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 * Optional restriction of IPsec policy to a given network interface
143 */
144 char *interface;
145
146 /**
147 * set up IPsec transport SA in MIPv6 proxy mode
148 */
149 bool proxy_mode;
150
151 /**
152 * enable installation and removal of kernel IPsec policies
153 */
154 bool install_policy;
155
156 /**
157 * Install outbound FWD policies
158 */
159 bool fwd_out_policy;
160
161 /**
162 * anti-replay window size
163 */
164 uint32_t replay_window;
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, add_proposal, void,
174 private_child_cfg_t *this, proposal_t *proposal)
175 {
176 if (proposal)
177 {
178 this->proposals->insert_last(this->proposals, proposal);
179 }
180 }
181
182 static bool match_proposal(proposal_t *item, proposal_t *proposal)
183 {
184 return item->equals(item, proposal);
185 }
186
187 METHOD(child_cfg_t, get_proposals, linked_list_t*,
188 private_child_cfg_t *this, bool strip_dh)
189 {
190 enumerator_t *enumerator;
191 proposal_t *current;
192 linked_list_t *proposals = linked_list_create();
193
194 enumerator = this->proposals->create_enumerator(this->proposals);
195 while (enumerator->enumerate(enumerator, &current))
196 {
197 current = current->clone(current);
198 if (strip_dh)
199 {
200 current->strip_dh(current, MODP_NONE);
201 }
202 if (proposals->find_first(proposals, (linked_list_match_t)match_proposal,
203 NULL, current) == SUCCESS)
204 {
205 current->destroy(current);
206 continue;
207 }
208 proposals->insert_last(proposals, current);
209 }
210 enumerator->destroy(enumerator);
211
212 DBG2(DBG_CFG, "configured proposals: %#P", proposals);
213
214 return proposals;
215 }
216
217 METHOD(child_cfg_t, select_proposal, proposal_t*,
218 private_child_cfg_t*this, linked_list_t *proposals, bool strip_dh,
219 bool private, bool prefer_self)
220 {
221 enumerator_t *prefer_enum, *match_enum;
222 proposal_t *proposal, *match, *selected = NULL;
223
224 if (prefer_self)
225 {
226 prefer_enum = this->proposals->create_enumerator(this->proposals);
227 match_enum = proposals->create_enumerator(proposals);
228 }
229 else
230 {
231 prefer_enum = proposals->create_enumerator(proposals);
232 match_enum = this->proposals->create_enumerator(this->proposals);
233 }
234
235 while (prefer_enum->enumerate(prefer_enum, &proposal))
236 {
237 proposal = proposal->clone(proposal);
238 if (prefer_self)
239 {
240 proposals->reset_enumerator(proposals, match_enum);
241 }
242 else
243 {
244 this->proposals->reset_enumerator(this->proposals, match_enum);
245 }
246 while (match_enum->enumerate(match_enum, &match))
247 {
248 if (strip_dh)
249 {
250 proposal->strip_dh(proposal, MODP_NONE);
251 }
252 selected = proposal->select(proposal, match, private);
253 if (selected)
254 {
255 DBG2(DBG_CFG, "received proposals: %#P", proposals);
256 DBG2(DBG_CFG, "configured proposals: %#P", this->proposals);
257 DBG2(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)
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 ts1->is_dynamic(ts1))
314 {
315 e2 = hosts->create_enumerator(hosts);
316 while (e2->enumerate(e2, &host))
317 {
318 ts2 = ts1->clone(ts1);
319 ts2->set_address(ts2, host);
320 derived->insert_last(derived, ts2);
321 }
322 e2->destroy(e2);
323 }
324 else
325 {
326 derived->insert_last(derived, ts1->clone(ts1));
327 }
328 }
329 e1->destroy(e1);
330
331 DBG2(DBG_CFG, "%s traffic selectors for %s:",
332 supplied ? "selecting" : "proposing", local ? "us" : "other");
333 if (supplied == NULL)
334 {
335 while (derived->remove_first(derived, (void**)&ts1) == SUCCESS)
336 {
337 DBG2(DBG_CFG, " %R", ts1);
338 result->insert_last(result, ts1);
339 }
340 derived->destroy(derived);
341 }
342 else
343 {
344 e1 = derived->create_enumerator(derived);
345 e2 = supplied->create_enumerator(supplied);
346 /* enumerate all configured/derived selectors */
347 while (e1->enumerate(e1, &ts1))
348 {
349 /* enumerate all supplied traffic selectors */
350 while (e2->enumerate(e2, &ts2))
351 {
352 selected = ts1->get_subset(ts1, ts2);
353 if (selected)
354 {
355 DBG2(DBG_CFG, " config: %R, received: %R => match: %R",
356 ts1, ts2, selected);
357 result->insert_last(result, selected);
358 }
359 else
360 {
361 DBG2(DBG_CFG, " config: %R, received: %R => no match",
362 ts1, ts2);
363 }
364 }
365 supplied->reset_enumerator(supplied, e2);
366 }
367 e1->destroy(e1);
368 e2->destroy(e2);
369
370 /* check if we/peer did any narrowing, raise alert */
371 e1 = derived->create_enumerator(derived);
372 e2 = result->create_enumerator(result);
373 while (e1->enumerate(e1, &ts1))
374 {
375 if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2))
376 {
377 charon->bus->alert(charon->bus, ALERT_TS_NARROWED,
378 local, result, this);
379 break;
380 }
381 }
382 e1->destroy(e1);
383 e2->destroy(e2);
384
385 derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy));
386 }
387
388 /* remove any redundant traffic selectors in the list */
389 e1 = result->create_enumerator(result);
390 e2 = result->create_enumerator(result);
391 while (e1->enumerate(e1, &ts1))
392 {
393 while (e2->enumerate(e2, &ts2))
394 {
395 if (ts1 != ts2)
396 {
397 if (ts2->is_contained_in(ts2, ts1))
398 {
399 result->remove_at(result, e2);
400 ts2->destroy(ts2);
401 result->reset_enumerator(result, e1);
402 break;
403 }
404 if (ts1->is_contained_in(ts1, ts2))
405 {
406 result->remove_at(result, e1);
407 ts1->destroy(ts1);
408 break;
409 }
410 }
411 }
412 result->reset_enumerator(result, e2);
413 }
414 e1->destroy(e1);
415 e2->destroy(e2);
416
417 return result;
418 }
419
420 METHOD(child_cfg_t, get_updown, char*,
421 private_child_cfg_t *this)
422 {
423 return this->updown;
424 }
425
426 METHOD(child_cfg_t, get_hostaccess, bool,
427 private_child_cfg_t *this)
428 {
429 return this->hostaccess;
430 }
431
432 /**
433 * Applies jitter to the rekey value. Returns the new rekey value.
434 * Note: The distribution of random values is not perfect, but it
435 * should get the job done.
436 */
437 static uint64_t apply_jitter(uint64_t rekey, uint64_t jitter)
438 {
439 if (jitter == 0)
440 {
441 return rekey;
442 }
443 jitter = (jitter == UINT64_MAX) ? jitter : jitter + 1;
444 return rekey - jitter * (random() / (RAND_MAX + 1.0));
445 }
446 #define APPLY_JITTER(l) l.rekey = apply_jitter(l.rekey, l.jitter)
447
448 METHOD(child_cfg_t, get_lifetime, lifetime_cfg_t*,
449 private_child_cfg_t *this, bool jitter)
450 {
451 lifetime_cfg_t *lft = malloc_thing(lifetime_cfg_t);
452 memcpy(lft, &this->lifetime, sizeof(lifetime_cfg_t));
453 if (!jitter)
454 {
455 lft->time.jitter = lft->bytes.jitter = lft->packets.jitter = 0;
456 }
457 APPLY_JITTER(lft->time);
458 APPLY_JITTER(lft->bytes);
459 APPLY_JITTER(lft->packets);
460 return lft;
461 }
462
463 METHOD(child_cfg_t, get_mode, ipsec_mode_t,
464 private_child_cfg_t *this)
465 {
466 return this->mode;
467 }
468
469 METHOD(child_cfg_t, get_start_action, action_t,
470 private_child_cfg_t *this)
471 {
472 return this->start_action;
473 }
474
475 METHOD(child_cfg_t, get_dpd_action, action_t,
476 private_child_cfg_t *this)
477 {
478 return this->dpd_action;
479 }
480
481 METHOD(child_cfg_t, get_close_action, action_t,
482 private_child_cfg_t *this)
483 {
484 return this->close_action;
485 }
486
487 METHOD(child_cfg_t, get_dh_group, diffie_hellman_group_t,
488 private_child_cfg_t *this)
489 {
490 enumerator_t *enumerator;
491 proposal_t *proposal;
492 uint16_t dh_group = MODP_NONE;
493
494 enumerator = this->proposals->create_enumerator(this->proposals);
495 while (enumerator->enumerate(enumerator, &proposal))
496 {
497 if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &dh_group, NULL))
498 {
499 break;
500 }
501 }
502 enumerator->destroy(enumerator);
503 return dh_group;
504 }
505
506 METHOD(child_cfg_t, use_ipcomp, bool,
507 private_child_cfg_t *this)
508 {
509 return this->use_ipcomp;
510 }
511
512 METHOD(child_cfg_t, get_inactivity, uint32_t,
513 private_child_cfg_t *this)
514 {
515 return this->inactivity;
516 }
517
518 METHOD(child_cfg_t, get_reqid, uint32_t,
519 private_child_cfg_t *this)
520 {
521 return this->reqid;
522 }
523
524 METHOD(child_cfg_t, get_mark, mark_t,
525 private_child_cfg_t *this, bool inbound)
526 {
527 return inbound ? this->mark_in : this->mark_out;
528 }
529
530 METHOD(child_cfg_t, get_tfc, uint32_t,
531 private_child_cfg_t *this)
532 {
533 return this->tfc;
534 }
535
536 METHOD(child_cfg_t, get_manual_prio, uint32_t,
537 private_child_cfg_t *this)
538 {
539 return this->manual_prio;
540 }
541
542 METHOD(child_cfg_t, get_interface, char*,
543 private_child_cfg_t *this)
544 {
545 return this->interface;
546 }
547
548 METHOD(child_cfg_t, get_replay_window, uint32_t,
549 private_child_cfg_t *this)
550 {
551 return this->replay_window;
552 }
553
554 METHOD(child_cfg_t, set_replay_window, void,
555 private_child_cfg_t *this, uint32_t replay_window)
556 {
557 this->replay_window = replay_window;
558 }
559
560 METHOD(child_cfg_t, use_proxy_mode, bool,
561 private_child_cfg_t *this)
562 {
563 return this->proxy_mode;
564 }
565
566 METHOD(child_cfg_t, install_policy, bool,
567 private_child_cfg_t *this)
568 {
569 return this->install_policy;
570 }
571
572 METHOD(child_cfg_t, install_fwd_out_policy, bool,
573 private_child_cfg_t *this)
574 {
575 return this->fwd_out_policy;
576 }
577
578 #define LT_PART_EQUALS(a, b) ({ a.life == b.life && a.rekey == b.rekey && a.jitter == b.jitter; })
579 #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); })
580
581 METHOD(child_cfg_t, equals, bool,
582 private_child_cfg_t *this, child_cfg_t *other_pub)
583 {
584 private_child_cfg_t *other = (private_child_cfg_t*)other_pub;
585
586 if (this == other)
587 {
588 return TRUE;
589 }
590 if (this->public.equals != other->public.equals)
591 {
592 return FALSE;
593 }
594 if (!this->proposals->equals_offset(this->proposals, other->proposals,
595 offsetof(proposal_t, equals)))
596 {
597 return FALSE;
598 }
599 if (!this->my_ts->equals_offset(this->my_ts, other->my_ts,
600 offsetof(traffic_selector_t, equals)))
601 {
602 return FALSE;
603 }
604 if (!this->other_ts->equals_offset(this->other_ts, other->other_ts,
605 offsetof(traffic_selector_t, equals)))
606 {
607 return FALSE;
608 }
609 return this->hostaccess == other->hostaccess &&
610 this->mode == other->mode &&
611 this->start_action == other->start_action &&
612 this->dpd_action == other->dpd_action &&
613 this->close_action == other->close_action &&
614 LIFETIME_EQUALS(this->lifetime, other->lifetime) &&
615 this->use_ipcomp == other->use_ipcomp &&
616 this->inactivity == other->inactivity &&
617 this->reqid == other->reqid &&
618 this->mark_in.value == other->mark_in.value &&
619 this->mark_in.mask == other->mark_in.mask &&
620 this->mark_out.value == other->mark_out.value &&
621 this->mark_out.mask == other->mark_out.mask &&
622 this->tfc == other->tfc &&
623 this->manual_prio == other->manual_prio &&
624 this->replay_window == other->replay_window &&
625 this->proxy_mode == other->proxy_mode &&
626 this->install_policy == other->install_policy &&
627 this->fwd_out_policy == other->fwd_out_policy &&
628 streq(this->updown, other->updown) &&
629 streq(this->interface, other->interface);
630 }
631
632 METHOD(child_cfg_t, get_ref, child_cfg_t*,
633 private_child_cfg_t *this)
634 {
635 ref_get(&this->refcount);
636 return &this->public;
637 }
638
639 METHOD(child_cfg_t, destroy, void,
640 private_child_cfg_t *this)
641 {
642 if (ref_put(&this->refcount))
643 {
644 this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
645 this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy));
646 this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy));
647 free(this->updown);
648 free(this->interface);
649 free(this->name);
650 free(this);
651 }
652 }
653
654 /*
655 * Described in header-file
656 */
657 child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
658 {
659 private_child_cfg_t *this;
660
661 INIT(this,
662 .public = {
663 .get_name = _get_name,
664 .add_traffic_selector = _add_traffic_selector,
665 .get_traffic_selectors = _get_traffic_selectors,
666 .add_proposal = _add_proposal,
667 .get_proposals = _get_proposals,
668 .select_proposal = _select_proposal,
669 .get_updown = _get_updown,
670 .get_hostaccess = _get_hostaccess,
671 .get_mode = _get_mode,
672 .get_start_action = _get_start_action,
673 .get_dpd_action = _get_dpd_action,
674 .get_close_action = _get_close_action,
675 .get_lifetime = _get_lifetime,
676 .get_dh_group = _get_dh_group,
677 .use_ipcomp = _use_ipcomp,
678 .get_inactivity = _get_inactivity,
679 .get_reqid = _get_reqid,
680 .get_mark = _get_mark,
681 .get_tfc = _get_tfc,
682 .get_manual_prio = _get_manual_prio,
683 .get_interface = _get_interface,
684 .get_replay_window = _get_replay_window,
685 .set_replay_window = _set_replay_window,
686 .use_proxy_mode = _use_proxy_mode,
687 .install_policy = _install_policy,
688 .install_fwd_out_policy = _install_fwd_out_policy,
689 .equals = _equals,
690 .get_ref = _get_ref,
691 .destroy = _destroy,
692 },
693 .name = strdup(name),
694 .updown = strdupnull(data->updown),
695 .hostaccess = data->hostaccess,
696 .reqid = data->reqid,
697 .mode = data->mode,
698 .proxy_mode = data->proxy_mode,
699 .start_action = data->start_action,
700 .dpd_action = data->dpd_action,
701 .close_action = data->close_action,
702 .mark_in = data->mark_in,
703 .mark_out = data->mark_out,
704 .lifetime = data->lifetime,
705 .inactivity = data->inactivity,
706 .use_ipcomp = data->ipcomp,
707 .tfc = data->tfc,
708 .manual_prio = data->priority,
709 .interface = strdupnull(data->interface),
710 .install_policy = !data->suppress_policies,
711 .fwd_out_policy = data->fwd_out_policies,
712 .refcount = 1,
713 .proposals = linked_list_create(),
714 .my_ts = linked_list_create(),
715 .other_ts = linked_list_create(),
716 .replay_window = lib->settings->get_int(lib->settings,
717 "%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns),
718 );
719
720 return &this->public;
721 }