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