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