charon-nm: Set local address to %any so IPv6 may be used as outer address
[strongswan.git] / src / libipsec / ipsec_processor.c
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "ipsec.h"
17 #include "ipsec_processor.h"
18
19 #include <utils/debug.h>
20 #include <library.h>
21 #include <threading/rwlock.h>
22 #include <collections/blocking_queue.h>
23 #include <processing/jobs/callback_job.h>
24
25 typedef struct private_ipsec_processor_t private_ipsec_processor_t;
26
27 /**
28 * Private additions to ipsec_processor_t.
29 */
30 struct private_ipsec_processor_t {
31
32 /**
33 * Public members
34 */
35 ipsec_processor_t public;
36
37 /**
38 * Queue for inbound packets (esp_packet_t*)
39 */
40 blocking_queue_t *inbound_queue;
41
42 /**
43 * Queue for outbound packets (ip_packet_t*)
44 */
45 blocking_queue_t *outbound_queue;
46
47 /**
48 * Registered inbound callback
49 */
50 struct {
51 ipsec_inbound_cb_t cb;
52 void *data;
53 } inbound;
54
55 /**
56 * Registered outbound callback
57 */
58 struct {
59 ipsec_outbound_cb_t cb;
60 void *data;
61 } outbound;
62
63 /**
64 * Lock used to synchronize access to the callbacks
65 */
66 rwlock_t *lock;
67 };
68
69 /**
70 * Deliver an inbound IP packet to the registered listener
71 */
72 static void deliver_inbound(private_ipsec_processor_t *this,
73 esp_packet_t *packet)
74 {
75 this->lock->read_lock(this->lock);
76 if (this->inbound.cb)
77 {
78 this->inbound.cb(this->inbound.data, packet->extract_payload(packet));
79 }
80 else
81 {
82 DBG2(DBG_ESP, "no inbound callback registered, dropping packet");
83 }
84 packet->destroy(packet);
85 this->lock->unlock(this->lock);
86 }
87
88 /**
89 * Processes inbound packets
90 */
91 static job_requeue_t process_inbound(private_ipsec_processor_t *this)
92 {
93 esp_packet_t *packet;
94 ip_packet_t *ip_packet;
95 ipsec_sa_t *sa;
96 uint8_t next_header;
97 uint32_t spi, reqid;
98
99 packet = (esp_packet_t*)this->inbound_queue->dequeue(this->inbound_queue);
100
101 if (!packet->parse_header(packet, &spi))
102 {
103 packet->destroy(packet);
104 return JOB_REQUEUE_DIRECT;
105 }
106
107 sa = ipsec->sas->checkout_by_spi(ipsec->sas, spi,
108 packet->get_destination(packet));
109 if (!sa)
110 {
111 DBG2(DBG_ESP, "inbound ESP packet does not belong to an installed SA");
112 packet->destroy(packet);
113 return JOB_REQUEUE_DIRECT;
114 }
115
116 if (!sa->is_inbound(sa))
117 {
118 DBG1(DBG_ESP, "error: IPsec SA is not inbound");
119 packet->destroy(packet);
120 ipsec->sas->checkin(ipsec->sas, sa);
121 return JOB_REQUEUE_DIRECT;
122 }
123
124 if (packet->decrypt(packet, sa->get_esp_context(sa)) != SUCCESS)
125 {
126 ipsec->sas->checkin(ipsec->sas, sa);
127 packet->destroy(packet);
128 return JOB_REQUEUE_DIRECT;
129 }
130 ip_packet = packet->get_payload(packet);
131 sa->update_usestats(sa, ip_packet->get_encoding(ip_packet).len);
132 reqid = sa->get_reqid(sa);
133 ipsec->sas->checkin(ipsec->sas, sa);
134
135 next_header = packet->get_next_header(packet);
136 switch (next_header)
137 {
138 case IPPROTO_IPIP:
139 case IPPROTO_IPV6:
140 {
141 ipsec_policy_t *policy;
142
143 policy = ipsec->policies->find_by_packet(ipsec->policies,
144 ip_packet, TRUE, reqid);
145 if (policy)
146 {
147 deliver_inbound(this, packet);
148 policy->destroy(policy);
149 break;
150 }
151 DBG1(DBG_ESP, "discarding inbound IP packet %#H == %#H [%hhu] due "
152 "to policy", ip_packet->get_source(ip_packet),
153 ip_packet->get_destination(ip_packet),
154 ip_packet->get_next_header(ip_packet));
155 /* no matching policy found, fall-through */
156 }
157 case IPPROTO_NONE:
158 /* discard dummy packets */
159 /* fall-through */
160 default:
161 packet->destroy(packet);
162 break;
163 }
164 return JOB_REQUEUE_DIRECT;
165 }
166
167 /**
168 * Send an ESP packet using the registered outbound callback
169 */
170 static void send_outbound(private_ipsec_processor_t *this,
171 esp_packet_t *packet)
172 {
173 this->lock->read_lock(this->lock);
174 if (this->outbound.cb)
175 {
176 this->outbound.cb(this->outbound.data, packet);
177 }
178 else
179 {
180 DBG2(DBG_ESP, "no outbound callback registered, dropping packet");
181 packet->destroy(packet);
182 }
183 this->lock->unlock(this->lock);
184 }
185
186 /**
187 * Processes outbound packets
188 */
189 static job_requeue_t process_outbound(private_ipsec_processor_t *this)
190 {
191 ipsec_policy_t *policy;
192 esp_packet_t *esp_packet;
193 ip_packet_t *packet;
194 ipsec_sa_t *sa;
195 host_t *src, *dst;
196
197 packet = (ip_packet_t*)this->outbound_queue->dequeue(this->outbound_queue);
198
199 policy = ipsec->policies->find_by_packet(ipsec->policies, packet, FALSE, 0);
200 if (!policy)
201 {
202 DBG2(DBG_ESP, "no matching outbound IPsec policy for %#H == %#H [%hhu]",
203 packet->get_source(packet), packet->get_destination(packet),
204 packet->get_next_header(packet));
205 packet->destroy(packet);
206 return JOB_REQUEUE_DIRECT;
207 }
208
209 sa = ipsec->sas->checkout_by_reqid(ipsec->sas, policy->get_reqid(policy),
210 FALSE);
211 if (!sa)
212 { /* TODO-IPSEC: send an acquire to uppper layer */
213 DBG1(DBG_ESP, "could not find an outbound IPsec SA for reqid {%u}, "
214 "dropping packet", policy->get_reqid(policy));
215 packet->destroy(packet);
216 policy->destroy(policy);
217 return JOB_REQUEUE_DIRECT;
218 }
219 src = sa->get_source(sa);
220 dst = sa->get_destination(sa);
221 esp_packet = esp_packet_create_from_payload(src->clone(src),
222 dst->clone(dst), packet);
223 if (esp_packet->encrypt(esp_packet, sa->get_esp_context(sa),
224 sa->get_spi(sa)) != SUCCESS)
225 {
226 ipsec->sas->checkin(ipsec->sas, sa);
227 esp_packet->destroy(esp_packet);
228 policy->destroy(policy);
229 return JOB_REQUEUE_DIRECT;
230 }
231 sa->update_usestats(sa, packet->get_encoding(packet).len);
232 ipsec->sas->checkin(ipsec->sas, sa);
233 policy->destroy(policy);
234 send_outbound(this, esp_packet);
235 return JOB_REQUEUE_DIRECT;
236 }
237
238 METHOD(ipsec_processor_t, queue_inbound, void,
239 private_ipsec_processor_t *this, esp_packet_t *packet)
240 {
241 this->inbound_queue->enqueue(this->inbound_queue, packet);
242 }
243
244 METHOD(ipsec_processor_t, queue_outbound, void,
245 private_ipsec_processor_t *this, ip_packet_t *packet)
246 {
247 this->outbound_queue->enqueue(this->outbound_queue, packet);
248 }
249
250 METHOD(ipsec_processor_t, register_inbound, void,
251 private_ipsec_processor_t *this, ipsec_inbound_cb_t cb, void *data)
252 {
253 this->lock->write_lock(this->lock);
254 this->inbound.cb = cb;
255 this->inbound.data = data;
256 this->lock->unlock(this->lock);
257 }
258
259 METHOD(ipsec_processor_t, unregister_inbound, void,
260 private_ipsec_processor_t *this, ipsec_inbound_cb_t cb)
261 {
262 this->lock->write_lock(this->lock);
263 if (this->inbound.cb == cb)
264 {
265 this->inbound.cb = NULL;
266 }
267 this->lock->unlock(this->lock);
268 }
269
270 METHOD(ipsec_processor_t, register_outbound, void,
271 private_ipsec_processor_t *this, ipsec_outbound_cb_t cb, void *data)
272 {
273 this->lock->write_lock(this->lock);
274 this->outbound.cb = cb;
275 this->outbound.data = data;
276 this->lock->unlock(this->lock);
277 }
278
279 METHOD(ipsec_processor_t, unregister_outbound, void,
280 private_ipsec_processor_t *this, ipsec_outbound_cb_t cb)
281 {
282 this->lock->write_lock(this->lock);
283 if (this->outbound.cb == cb)
284 {
285 this->outbound.cb = NULL;
286 }
287 this->lock->unlock(this->lock);
288 }
289
290 METHOD(ipsec_processor_t, destroy, void,
291 private_ipsec_processor_t *this)
292 {
293 this->inbound_queue->destroy_offset(this->inbound_queue,
294 offsetof(esp_packet_t, destroy));
295 this->outbound_queue->destroy_offset(this->outbound_queue,
296 offsetof(ip_packet_t, destroy));
297 this->lock->destroy(this->lock);
298 free(this);
299 }
300
301 /**
302 * Described in header.
303 */
304 ipsec_processor_t *ipsec_processor_create()
305 {
306 private_ipsec_processor_t *this;
307
308 INIT(this,
309 .public = {
310 .queue_inbound = _queue_inbound,
311 .queue_outbound = _queue_outbound,
312 .register_inbound = _register_inbound,
313 .unregister_inbound = _unregister_inbound,
314 .register_outbound = _register_outbound,
315 .unregister_outbound = _unregister_outbound,
316 .destroy = _destroy,
317 },
318 .inbound_queue = blocking_queue_create(),
319 .outbound_queue = blocking_queue_create(),
320 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
321 );
322
323 lib->processor->queue_job(lib->processor,
324 (job_t*)callback_job_create((callback_job_cb_t)process_inbound, this,
325 NULL, (callback_job_cancel_t)return_false));
326 lib->processor->queue_job(lib->processor,
327 (job_t*)callback_job_create((callback_job_cb_t)process_outbound, this,
328 NULL, (callback_job_cancel_t)return_false));
329 return &this->public;
330 }