Coverage Report

Created: 2026-03-31 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/strongswan/src/libcharon/sa/shunt_manager.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2015-2017 Tobias Brunner
3
 * Copyright (C) 2011-2016 Andreas Steffen
4
 *
5
 * Copyright (C) secunet Security Networks AG
6
 *
7
 * This program is free software; you can redistribute it and/or modify it
8
 * under the terms of the GNU General Public License as published by the
9
 * Free Software Foundation; either version 2 of the License, or (at your
10
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
11
 *
12
 * This program is distributed in the hope that it will be useful, but
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15
 * for more details.
16
 */
17
18
#include "shunt_manager.h"
19
20
#include <daemon.h>
21
#include <threading/rwlock.h>
22
#include <threading/rwlock_condvar.h>
23
#include <collections/linked_list.h>
24
25
0
#define INSTALL_DISABLED ((u_int)~0)
26
27
typedef struct private_shunt_manager_t private_shunt_manager_t;
28
29
/**
30
 * Private data of an shunt_manager_t object.
31
 */
32
struct private_shunt_manager_t {
33
34
  /**
35
   * Public shunt_manager_t interface.
36
   */
37
  shunt_manager_t public;
38
39
  /**
40
   * Installed shunts, as entry_t
41
   */
42
  linked_list_t *shunts;
43
44
  /**
45
   * Lock to safely access the list of shunts
46
   */
47
  rwlock_t *lock;
48
49
  /**
50
   * Number of threads currently installing shunts, or INSTALL_DISABLED
51
   */
52
  u_int installing;
53
54
  /**
55
   * Condvar to signal shunt installation
56
   */
57
  rwlock_condvar_t *condvar;
58
};
59
60
/**
61
 * Config entry for a shunt
62
 */
63
typedef struct {
64
  /**
65
   * Configured namespace
66
   */
67
  char *ns;
68
69
  /**
70
   * Child config
71
   */
72
  child_cfg_t *cfg;
73
74
} entry_t;
75
76
/**
77
 * Destroy a config entry
78
 */
79
static void entry_destroy(entry_t *this)
80
0
{
81
0
  this->cfg->destroy(this->cfg);
82
0
  free(this->ns);
83
0
  free(this);
84
0
}
85
86
/**
87
 * Install in and out shunt policies in the kernel
88
 */
89
static bool install_shunt_policy(child_cfg_t *child)
90
0
{
91
0
  enumerator_t *e_my_ts, *e_other_ts;
92
0
  linked_list_t *my_ts_list, *other_ts_list, *hosts;
93
0
  traffic_selector_t *my_ts, *other_ts;
94
0
  host_t *host_any, *host_any6;
95
0
  policy_type_t policy_type;
96
0
  policy_priority_t policy_prio;
97
0
  status_t status = SUCCESS;
98
0
  hw_offload_t hw_offload;
99
0
  uint32_t manual_prio;
100
0
  char *interface;
101
0
  bool fwd_out;
102
0
  ipsec_sa_cfg_t sa = { .mode = MODE_TRANSPORT };
103
104
0
  switch (child->get_mode(child))
105
0
  {
106
0
    case MODE_PASS:
107
0
      policy_type = POLICY_PASS;
108
0
      policy_prio = POLICY_PRIORITY_PASS;
109
0
      break;
110
0
    case MODE_DROP:
111
0
      policy_type = POLICY_DROP;
112
0
      policy_prio = POLICY_PRIORITY_FALLBACK;
113
0
      break;
114
0
    default:
115
0
      return FALSE;
116
0
  }
117
118
0
  host_any = host_create_any(AF_INET);
119
0
  host_any6 = host_create_any(AF_INET6);
120
121
0
  hosts = linked_list_create_with_items(host_any, host_any6, NULL);
122
0
  my_ts_list =    child->get_traffic_selectors(child, TRUE, hosts);
123
0
  other_ts_list = child->get_traffic_selectors(child, FALSE, hosts);
124
0
  hosts->destroy(hosts);
125
126
0
  manual_prio = child->get_manual_prio(child);
127
0
  hw_offload = child->get_hw_offload(child);
128
0
  interface = child->get_interface(child);
129
0
  fwd_out = child->has_option(child, OPT_FWD_OUT_POLICIES);
130
131
  /* enumerate pairs of traffic selectors */
132
0
  e_my_ts = my_ts_list->create_enumerator(my_ts_list);
133
0
  while (e_my_ts->enumerate(e_my_ts, &my_ts))
134
0
  {
135
0
    e_other_ts = other_ts_list->create_enumerator(other_ts_list);
136
0
    while (e_other_ts->enumerate(e_other_ts, &other_ts))
137
0
    {
138
0
      if (my_ts->get_type(my_ts) != other_ts->get_type(other_ts))
139
0
      {
140
0
        continue;
141
0
      }
142
0
      if (my_ts->get_protocol(my_ts) &&
143
0
        other_ts->get_protocol(other_ts) &&
144
0
        my_ts->get_protocol(my_ts) != other_ts->get_protocol(other_ts))
145
0
      {
146
0
        continue;
147
0
      }
148
      /* install out policy */
149
0
      kernel_ipsec_policy_id_t id = {
150
0
        .dir = POLICY_OUT,
151
0
        .src_ts = my_ts,
152
0
        .dst_ts = other_ts,
153
0
        .mark = child->get_mark(child, FALSE),
154
0
        .interface = interface,
155
0
      };
156
0
      kernel_ipsec_manage_policy_t policy = {
157
0
        .type = policy_type,
158
0
        .prio = policy_prio,
159
0
        .manual_prio = manual_prio,
160
0
        .hw_offload = hw_offload,
161
0
        .src = host_any,
162
0
        .dst = host_any,
163
0
        .sa = &sa,
164
0
      };
165
0
      status |= charon->kernel->add_policy(charon->kernel, &id, &policy);
166
0
      if (fwd_out)
167
0
      { /* install "outbound" forward policy */
168
0
        id.dir = POLICY_FWD;
169
0
        status |= charon->kernel->add_policy(charon->kernel, &id, &policy);
170
0
      }
171
      /* install in policy */
172
0
      id = (kernel_ipsec_policy_id_t){
173
0
        .dir = POLICY_IN,
174
0
        .src_ts = other_ts,
175
0
        .dst_ts = my_ts,
176
0
        .mark = child->get_mark(child, TRUE),
177
0
        .interface = interface,
178
0
      };
179
0
      status |= charon->kernel->add_policy(charon->kernel, &id, &policy);
180
      /* install "inbound" forward policy */
181
0
      id.dir = POLICY_FWD;
182
0
      status |= charon->kernel->add_policy(charon->kernel, &id, &policy);
183
0
    }
184
0
    e_other_ts->destroy(e_other_ts);
185
0
  }
186
0
  e_my_ts->destroy(e_my_ts);
187
188
0
  my_ts_list->destroy_offset(my_ts_list,
189
0
                 offsetof(traffic_selector_t, destroy));
190
0
  other_ts_list->destroy_offset(other_ts_list,
191
0
                 offsetof(traffic_selector_t, destroy));
192
0
  host_any6->destroy(host_any6);
193
0
  host_any->destroy(host_any);
194
195
0
  return status == SUCCESS;
196
0
}
197
198
METHOD(shunt_manager_t, install, bool,
199
  private_shunt_manager_t *this, char *ns, child_cfg_t *cfg)
200
0
{
201
0
  enumerator_t *enumerator;
202
0
  entry_t *entry;
203
0
  bool found = FALSE, success;
204
205
0
  if (!ns)
206
0
  {
207
0
    DBG1(DBG_CFG, "missing namespace for shunt policy '%s'",
208
0
       cfg->get_name(cfg));
209
0
    return FALSE;
210
0
  }
211
212
  /* check if not already installed */
213
0
  this->lock->write_lock(this->lock);
214
0
  if (this->installing == INSTALL_DISABLED)
215
0
  { /* flush() has been called */
216
0
    this->lock->unlock(this->lock);
217
0
    return FALSE;
218
0
  }
219
0
  enumerator = this->shunts->create_enumerator(this->shunts);
220
0
  while (enumerator->enumerate(enumerator, &entry))
221
0
  {
222
0
    if (streq(ns, entry->ns) &&
223
0
      streq(cfg->get_name(cfg), entry->cfg->get_name(entry->cfg)))
224
0
    {
225
0
      found = TRUE;
226
0
      break;
227
0
    }
228
0
  }
229
0
  enumerator->destroy(enumerator);
230
0
  if (found)
231
0
  {
232
0
    DBG1(DBG_CFG, "shunt %N policy '%s' already installed",
233
0
       ipsec_mode_names, cfg->get_mode(cfg), cfg->get_name(cfg));
234
0
    this->lock->unlock(this->lock);
235
0
    return TRUE;
236
0
  }
237
0
  INIT(entry,
238
0
    .ns = strdup(ns),
239
0
    .cfg = cfg->get_ref(cfg),
240
0
  );
241
0
  this->shunts->insert_last(this->shunts, entry);
242
0
  this->installing++;
243
0
  this->lock->unlock(this->lock);
244
245
0
  success = install_shunt_policy(cfg);
246
247
0
  this->lock->write_lock(this->lock);
248
0
  if (!success)
249
0
  {
250
0
    this->shunts->remove(this->shunts, entry, NULL);
251
0
    entry_destroy(entry);
252
0
  }
253
0
  this->installing--;
254
0
  this->condvar->signal(this->condvar);
255
0
  this->lock->unlock(this->lock);
256
0
  return success;
257
0
}
258
259
/**
260
 * Uninstall in and out shunt policies in the kernel
261
 */
262
static void uninstall_shunt_policy(child_cfg_t *child)
263
0
{
264
0
  enumerator_t *e_my_ts, *e_other_ts;
265
0
  linked_list_t *my_ts_list, *other_ts_list, *hosts;
266
0
  traffic_selector_t *my_ts, *other_ts;
267
0
  host_t *host_any, *host_any6;
268
0
  policy_type_t policy_type;
269
0
  policy_priority_t policy_prio;
270
0
  status_t status = SUCCESS;
271
0
  uint32_t manual_prio;
272
0
  char *interface;
273
0
  bool fwd_out;
274
0
  ipsec_sa_cfg_t sa = { .mode = MODE_TRANSPORT };
275
276
0
  switch (child->get_mode(child))
277
0
  {
278
0
    case MODE_PASS:
279
0
      policy_type = POLICY_PASS;
280
0
      policy_prio = POLICY_PRIORITY_PASS;
281
0
      break;
282
0
    case MODE_DROP:
283
0
      policy_type = POLICY_DROP;
284
0
      policy_prio = POLICY_PRIORITY_FALLBACK;
285
0
      break;
286
0
    default:
287
0
      return;
288
0
  }
289
290
0
  host_any = host_create_any(AF_INET);
291
0
  host_any6 = host_create_any(AF_INET6);
292
293
0
  hosts = linked_list_create_with_items(host_any, host_any6, NULL);
294
0
  my_ts_list =    child->get_traffic_selectors(child, TRUE, hosts);
295
0
  other_ts_list = child->get_traffic_selectors(child, FALSE, hosts);
296
0
  hosts->destroy(hosts);
297
298
0
  manual_prio = child->get_manual_prio(child);
299
0
  interface = child->get_interface(child);
300
0
  fwd_out = child->has_option(child, OPT_FWD_OUT_POLICIES);
301
302
  /* enumerate pairs of traffic selectors */
303
0
  e_my_ts = my_ts_list->create_enumerator(my_ts_list);
304
0
  while (e_my_ts->enumerate(e_my_ts, &my_ts))
305
0
  {
306
0
    e_other_ts = other_ts_list->create_enumerator(other_ts_list);
307
0
    while (e_other_ts->enumerate(e_other_ts, &other_ts))
308
0
    {
309
0
      if (my_ts->get_type(my_ts) != other_ts->get_type(other_ts))
310
0
      {
311
0
        continue;
312
0
      }
313
0
      if (my_ts->get_protocol(my_ts) &&
314
0
        other_ts->get_protocol(other_ts) &&
315
0
        my_ts->get_protocol(my_ts) != other_ts->get_protocol(other_ts))
316
0
      {
317
0
        continue;
318
0
      }
319
      /* uninstall out policy */
320
0
      kernel_ipsec_policy_id_t id = {
321
0
        .dir = POLICY_OUT,
322
0
        .src_ts = my_ts,
323
0
        .dst_ts = other_ts,
324
0
        .mark = child->get_mark(child, FALSE),
325
0
        .interface = interface,
326
0
      };
327
0
      kernel_ipsec_manage_policy_t policy = {
328
0
        .type = policy_type,
329
0
        .prio = policy_prio,
330
0
        .manual_prio = manual_prio,
331
0
        .src = host_any,
332
0
        .dst = host_any,
333
0
        .sa = &sa,
334
0
      };
335
0
      status |= charon->kernel->del_policy(charon->kernel, &id, &policy);
336
0
      if (fwd_out)
337
0
      {
338
        /* uninstall "outbound" forward policy */
339
0
        id.dir = POLICY_FWD;
340
0
        status |= charon->kernel->del_policy(charon->kernel, &id, &policy);
341
0
      }
342
      /* uninstall in policy */
343
0
      id = (kernel_ipsec_policy_id_t){
344
0
        .dir = POLICY_IN,
345
0
        .src_ts = other_ts,
346
0
        .dst_ts = my_ts,
347
0
        .mark = child->get_mark(child, TRUE),
348
0
        .interface = interface,
349
0
      };
350
0
      status |= charon->kernel->del_policy(charon->kernel, &id, &policy);
351
      /* uninstall "inbound" forward policy */
352
0
      id.dir = POLICY_FWD;
353
0
      status |= charon->kernel->del_policy(charon->kernel, &id, &policy);
354
0
    }
355
0
    e_other_ts->destroy(e_other_ts);
356
0
  }
357
0
  e_my_ts->destroy(e_my_ts);
358
359
0
  my_ts_list->destroy_offset(my_ts_list,
360
0
                 offsetof(traffic_selector_t, destroy));
361
0
  other_ts_list->destroy_offset(other_ts_list,
362
0
                 offsetof(traffic_selector_t, destroy));
363
0
  host_any6->destroy(host_any6);
364
0
  host_any->destroy(host_any);
365
366
0
  if (status != SUCCESS)
367
0
  {
368
0
    DBG1(DBG_CFG, "uninstalling shunt %N 'policy %s' failed",
369
0
       ipsec_mode_names, child->get_mode(child), child->get_name(child));
370
0
  }
371
0
}
372
373
METHOD(shunt_manager_t, uninstall, bool,
374
  private_shunt_manager_t *this, char *ns, char *name)
375
0
{
376
0
  enumerator_t *enumerator;
377
0
  entry_t *entry, *found = NULL;
378
379
0
  this->lock->write_lock(this->lock);
380
0
  enumerator = this->shunts->create_enumerator(this->shunts);
381
0
  while (enumerator->enumerate(enumerator, &entry))
382
0
  {
383
0
    if ((!ns || streq(ns, entry->ns)) &&
384
0
      streq(name, entry->cfg->get_name(entry->cfg)))
385
0
    {
386
0
      this->shunts->remove_at(this->shunts, enumerator);
387
0
      found = entry;
388
0
      break;
389
0
    }
390
0
  }
391
0
  enumerator->destroy(enumerator);
392
0
  this->lock->unlock(this->lock);
393
394
0
  if (!found)
395
0
  {
396
0
    return FALSE;
397
0
  }
398
0
  uninstall_shunt_policy(found->cfg);
399
0
  entry_destroy(found);
400
0
  return TRUE;
401
0
}
402
403
CALLBACK(filter_entries, bool,
404
  void *unused, enumerator_t *orig, va_list args)
405
0
{
406
0
  entry_t *entry;
407
0
  child_cfg_t **cfg;
408
0
  char **ns;
409
410
0
  VA_ARGS_VGET(args, ns, cfg);
411
412
0
  if (orig->enumerate(orig, &entry))
413
0
  {
414
0
    if (ns)
415
0
    {
416
0
      *ns = entry->ns;
417
0
    }
418
0
    *cfg = entry->cfg;
419
0
    return TRUE;
420
0
  }
421
0
  return FALSE;
422
0
}
423
424
METHOD(shunt_manager_t, create_enumerator, enumerator_t*,
425
  private_shunt_manager_t *this)
426
0
{
427
0
  this->lock->read_lock(this->lock);
428
0
  return enumerator_create_filter(
429
0
              this->shunts->create_enumerator(this->shunts),
430
0
              filter_entries, this->lock,
431
0
              (void*)this->lock->unlock);
432
0
}
433
434
METHOD(shunt_manager_t, flush, void,
435
  private_shunt_manager_t *this)
436
0
{
437
0
  entry_t *entry;
438
439
0
  this->lock->write_lock(this->lock);
440
0
  while (this->installing)
441
0
  {
442
0
    this->condvar->wait(this->condvar, this->lock);
443
0
  }
444
0
  while (this->shunts->remove_last(this->shunts, (void**)&entry) == SUCCESS)
445
0
  {
446
0
    uninstall_shunt_policy(entry->cfg);
447
0
    entry_destroy(entry);
448
0
  }
449
0
  this->installing = INSTALL_DISABLED;
450
0
  this->lock->unlock(this->lock);
451
0
}
452
453
METHOD(shunt_manager_t, destroy, void,
454
  private_shunt_manager_t *this)
455
0
{
456
0
  this->shunts->destroy_offset(this->shunts, offsetof(child_cfg_t, destroy));
457
0
  this->lock->destroy(this->lock);
458
0
  this->condvar->destroy(this->condvar);
459
0
  free(this);
460
0
}
461
462
/**
463
 * See header
464
 */
465
shunt_manager_t *shunt_manager_create()
466
2
{
467
2
  private_shunt_manager_t *this;
468
469
2
  INIT(this,
470
2
    .public = {
471
2
      .install = _install,
472
2
      .uninstall = _uninstall,
473
2
      .create_enumerator = _create_enumerator,
474
2
      .flush = _flush,
475
2
      .destroy = _destroy,
476
2
    },
477
2
    .shunts = linked_list_create(),
478
2
    .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
479
2
    .condvar = rwlock_condvar_create(),
480
2
  );
481
482
2
  return &this->public;
483
2
}