Order IPsec policies by a pseudo-priority based on the traffic selectors
[strongswan.git] / src / libipsec / ipsec_policy_mgr.c
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
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 "ipsec_policy_mgr.h"
19 #include "ipsec_policy.h"
20
21 #include <debug.h>
22 #include <threading/rwlock.h>
23 #include <utils/linked_list.h>
24
25 /** Base priority for installed policies */
26 #define PRIO_BASE 512
27
28 typedef struct private_ipsec_policy_mgr_t private_ipsec_policy_mgr_t;
29
30 /**
31 * Private additions to ipsec_policy_mgr_t.
32 */
33 struct private_ipsec_policy_mgr_t {
34
35 /**
36 * Public members of ipsec_policy_mgr_t.
37 */
38 ipsec_policy_mgr_t public;
39
40 /**
41 * Installed policies (ipsec_policy_entry_t*)
42 */
43 linked_list_t *policies;
44
45 /**
46 * Lock to safely access the list of policies
47 */
48 rwlock_t *lock;
49
50 };
51
52 /**
53 * Helper struct to store policies in a list sorted by the same pseudo-priority
54 * used by the NETLINK kernel interface.
55 */
56 typedef struct {
57
58 /**
59 * Priority used to sort policies
60 */
61 u_int32_t priority;
62
63 /**
64 * The policy
65 */
66 ipsec_policy_t *policy;
67
68 } ipsec_policy_entry_t;
69
70 /**
71 * Calculate the pseudo-priority to sort policies. This is the same algorithm
72 * used by the NETLINK kernel interface (i.e. high priority -> low value).
73 */
74 static u_int32_t calculate_priority(policy_priority_t policy_priority,
75 traffic_selector_t *src,
76 traffic_selector_t *dst)
77 {
78 u_int32_t priority = PRIO_BASE;
79 u_int16_t port;
80 u_int8_t mask, proto;
81 host_t *net;
82
83 switch (policy_priority)
84 {
85 case POLICY_PRIORITY_FALLBACK:
86 priority <<= 1;
87 /* fall-through */
88 case POLICY_PRIORITY_ROUTED:
89 priority <<= 1;
90 /* fall-through */
91 case POLICY_PRIORITY_DEFAULT:
92 break;
93 }
94 /* calculate priority based on selector size, small size = high prio */
95 src->to_subnet(src, &net, &mask);
96 priority -= mask;
97 proto = src->get_protocol(src);
98 port = net->get_port(net);
99 net->destroy(net);
100
101 dst->to_subnet(dst, &net, &mask);
102 priority -= mask;
103 proto = max(proto, dst->get_protocol(dst));
104 port = max(port, net->get_port(net));
105 net->destroy(net);
106
107 priority <<= 2; /* make some room for the two flags */
108 priority += port ? 0 : 2;
109 priority += proto ? 0 : 1;
110 return priority;
111 }
112
113 /**
114 * Create a policy entry
115 */
116 static ipsec_policy_entry_t *policy_entry_create(ipsec_policy_t *policy)
117 {
118 ipsec_policy_entry_t *this;
119
120 INIT(this,
121 .policy = policy,
122 .priority = calculate_priority(policy->get_priority(policy),
123 policy->get_source_ts(policy),
124 policy->get_destination_ts(policy)),
125 );
126 return this;
127 }
128
129 /**
130 * Destroy a policy entry
131 */
132 static void policy_entry_destroy(ipsec_policy_entry_t *this)
133 {
134 this->policy->destroy(this->policy);
135 free(this);
136 }
137
138 METHOD(ipsec_policy_mgr_t, add_policy, status_t,
139 private_ipsec_policy_mgr_t *this, host_t *src, host_t *dst,
140 traffic_selector_t *src_ts, traffic_selector_t *dst_ts,
141 policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark,
142 policy_priority_t priority)
143 {
144 enumerator_t *enumerator;
145 ipsec_policy_entry_t *entry, *current;
146 ipsec_policy_t *policy;
147
148 if (type != POLICY_IPSEC || direction == POLICY_FWD)
149 { /* we ignore these policies as we currently have no use for them */
150 return SUCCESS;
151 }
152
153 DBG2(DBG_ESP, "adding policy %R === %R %N", src_ts, dst_ts,
154 policy_dir_names, direction);
155
156 policy = ipsec_policy_create(src, dst, src_ts, dst_ts, direction, type, sa,
157 mark, priority);
158 entry = policy_entry_create(policy);
159
160 this->lock->write_lock(this->lock);
161 enumerator = this->policies->create_enumerator(this->policies);
162 while (enumerator->enumerate(enumerator, (void**)&current))
163 {
164 if (current->priority >= entry->priority)
165 {
166 break;
167 }
168 }
169 this->policies->insert_before(this->policies, enumerator, entry);
170 enumerator->destroy(enumerator);
171 this->lock->unlock(this->lock);
172 return SUCCESS;
173 }
174
175 METHOD(ipsec_policy_mgr_t, del_policy, status_t,
176 private_ipsec_policy_mgr_t *this, traffic_selector_t *src_ts,
177 traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid,
178 mark_t mark, policy_priority_t policy_priority)
179 {
180 enumerator_t *enumerator;
181 ipsec_policy_entry_t *current, *found = NULL;
182 u_int32_t priority;
183
184 if (direction == POLICY_FWD)
185 { /* we ignore these policies as we currently have no use for them */
186 return SUCCESS;
187 }
188 DBG2(DBG_ESP, "deleting policy %R === %R %N", src_ts, dst_ts,
189 policy_dir_names, direction);
190
191 priority = calculate_priority(policy_priority, src_ts, dst_ts);
192
193 this->lock->write_lock(this->lock);
194 enumerator = this->policies->create_enumerator(this->policies);
195 while (enumerator->enumerate(enumerator, (void**)&current))
196 {
197 if (current->priority == priority &&
198 current->policy->match(current->policy, src_ts, dst_ts, direction,
199 reqid, mark, policy_priority))
200 {
201 this->policies->remove_at(this->policies, enumerator);
202 found = current;
203 break;
204 }
205 }
206 enumerator->destroy(enumerator);
207 this->lock->unlock(this->lock);
208 if (found)
209 {
210 policy_entry_destroy(found);
211 return SUCCESS;
212 }
213 return FAILED;
214 }
215
216 METHOD(ipsec_policy_mgr_t, flush_policies, status_t,
217 private_ipsec_policy_mgr_t *this)
218 {
219 ipsec_policy_entry_t *entry;
220
221 DBG2(DBG_ESP, "flushing policies");
222
223 this->lock->write_lock(this->lock);
224 while (this->policies->remove_last(this->policies,
225 (void**)&entry) == SUCCESS)
226 {
227 policy_entry_destroy(entry);
228 }
229 this->lock->unlock(this->lock);
230 return SUCCESS;
231 }
232
233 METHOD(ipsec_policy_mgr_t, destroy, void,
234 private_ipsec_policy_mgr_t *this)
235 {
236 flush_policies(this);
237 this->policies->destroy(this->policies);
238 this->lock->destroy(this->lock);
239 free(this);
240 }
241
242 /**
243 * Described in header.
244 */
245 ipsec_policy_mgr_t *ipsec_policy_mgr_create()
246 {
247 private_ipsec_policy_mgr_t *this;
248
249 INIT(this,
250 .public = {
251 .add_policy = _add_policy,
252 .del_policy = _del_policy,
253 .flush_policies = _flush_policies,
254 .destroy = _destroy,
255 },
256 .policies = linked_list_create(),
257 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
258 );
259
260 return &this->public;
261 }