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