connmark: Add CONNMARK rules to select correct output SA based on conntrack
[strongswan.git] / src / libcharon / plugins / connmark / connmark_listener.c
1 /*
2 * Copyright (C) 2014 Martin Willi
3 * Copyright (C) 2014 revosec AG
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 "connmark_listener.h"
17
18 #include <daemon.h>
19
20 #include <errno.h>
21 #include <libiptc/libiptc.h>
22 #include <linux/netfilter/xt_esp.h>
23 #include <linux/netfilter/xt_tcpudp.h>
24 #include <linux/netfilter/xt_MARK.h>
25 #include <linux/netfilter/xt_policy.h>
26 #include <linux/netfilter/xt_CONNMARK.h>
27
28
29 typedef struct private_connmark_listener_t private_connmark_listener_t;
30
31 /**
32 * Private data of an connmark_listener_t object.
33 */
34 struct private_connmark_listener_t {
35
36 /**
37 * Public connmark_listener_t interface.
38 */
39 connmark_listener_t public;
40 };
41
42 /**
43 * Convert an (IPv4) traffic selector to an address and mask
44 */
45 static bool ts2in(traffic_selector_t *ts,
46 struct in_addr *addr, struct in_addr *mask)
47 {
48 u_int8_t bits;
49 host_t *net;
50
51 if (ts->get_type(ts) == TS_IPV4_ADDR_RANGE &&
52 ts->to_subnet(ts, &net, &bits))
53 {
54 memcpy(&addr->s_addr, net->get_address(net).ptr, 4);
55 net->destroy(net);
56 mask->s_addr = htonl(0xffffffffU << (32 - bits));
57 return TRUE;
58 }
59 return FALSE;
60 }
61
62 /**
63 * Convert an (IPv4) host to an address with mask
64 */
65 static bool host2in(host_t *host, struct in_addr *addr, struct in_addr *mask)
66 {
67 if (host->get_family(host) == AF_INET)
68 {
69 memcpy(&addr->s_addr, host->get_address(host).ptr, 4);
70 mask->s_addr = ~0;
71 return TRUE;
72 }
73 return FALSE;
74 }
75
76 /**
77 * Add or remove a rule to/from the specified chain
78 */
79 static bool manage_rule(struct iptc_handle *ipth, const char *chain,
80 bool add, struct ipt_entry *e)
81 {
82 if (add)
83 {
84 if (!iptc_insert_entry(chain, e, 0, ipth))
85 {
86 DBG1(DBG_CFG, "appending %s rule failed: %s",
87 chain, iptc_strerror(errno));
88 return FALSE;
89 }
90 }
91 else
92 {
93 if (!iptc_delete_entry(chain, e, "", ipth))
94 {
95 DBG1(DBG_CFG, "deleting %s rule failed: %s",
96 chain, iptc_strerror(errno));
97 return FALSE;
98 }
99 }
100 return TRUE;
101 }
102
103 /**
104 * Add rule marking UDP-encapsulated ESP packets to match the correct policy
105 */
106 static bool manage_pre_esp_in_udp(private_connmark_listener_t *this,
107 struct iptc_handle *ipth, bool add,
108 u_int mark, u_int32_t spi,
109 host_t *dst, host_t *src)
110 {
111 struct {
112 struct ipt_entry e;
113 struct ipt_entry_match m;
114 struct xt_udp udp;
115 struct ipt_entry_target t;
116 struct xt_mark_tginfo2 tm;
117 } ipt = {
118 .e = {
119 .target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) +
120 sizeof(ipt.udp)),
121 .next_offset = sizeof(ipt),
122 .ip = {
123 .proto = IPPROTO_UDP,
124 },
125 },
126 .m = {
127 .u = {
128 .user = {
129 .match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.udp)),
130 .name = "udp",
131 },
132 },
133 },
134 .udp = {
135 .spts = { src->get_port(src), src->get_port(src) },
136 .dpts = { dst->get_port(dst), dst->get_port(dst) },
137 },
138 .t = {
139 .u = {
140 .user = {
141 .target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.tm)),
142 .name = "MARK",
143 .revision = 2,
144 },
145 },
146 },
147 .tm = {
148 .mark = mark,
149 .mask = ~0,
150 },
151 };
152
153 if (!host2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
154 !host2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
155 {
156 return FALSE;
157 }
158 return manage_rule(ipth, "PREROUTING", add, &ipt.e);
159 }
160
161 /**
162 * Add rule marking non-encapsulated ESP packets to match the correct policy
163 */
164 static bool manage_pre_esp(private_connmark_listener_t *this,
165 struct iptc_handle *ipth, bool add,
166 u_int mark, u_int32_t spi,
167 host_t *dst, host_t *src)
168 {
169 struct {
170 struct ipt_entry e;
171 struct ipt_entry_match m;
172 struct xt_esp esp;
173 struct ipt_entry_target t;
174 struct xt_mark_tginfo2 tm;
175 } ipt = {
176 .e = {
177 .target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) +
178 sizeof(ipt.esp)),
179 .next_offset = sizeof(ipt),
180 .ip = {
181 .proto = IPPROTO_ESP,
182 },
183 },
184 .m = {
185 .u = {
186 .user = {
187 .match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.esp)),
188 .name = "esp",
189 },
190 },
191 },
192 .esp = {
193 .spis = { htonl(spi), htonl(spi) },
194 },
195 .t = {
196 .u = {
197 .user = {
198 .target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.tm)),
199 .name = "MARK",
200 .revision = 2,
201 },
202 },
203 },
204 .tm = {
205 .mark = mark,
206 .mask = ~0,
207 },
208 };
209
210 if (!host2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
211 !host2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
212 {
213 return FALSE;
214 }
215 return manage_rule(ipth, "PREROUTING", add, &ipt.e);
216 }
217
218 /**
219 * Add rule marking ESP packets to match the correct policy
220 */
221 static bool manage_pre(private_connmark_listener_t *this,
222 struct iptc_handle *ipth, bool add,
223 u_int mark, u_int32_t spi, bool encap,
224 host_t *dst, host_t *src)
225 {
226 if (encap)
227 {
228 return manage_pre_esp_in_udp(this, ipth, add, mark, spi, dst, src);
229 }
230 return manage_pre_esp(this, ipth, add, mark, spi, dst, src);
231 }
232
233 /**
234 * Add inbound rule applying CONNMARK to matching traffic
235 */
236 static bool manage_in(private_connmark_listener_t *this,
237 struct iptc_handle *ipth, bool add,
238 u_int mark, u_int32_t spi,
239 traffic_selector_t *dst, traffic_selector_t *src)
240 {
241 struct {
242 struct ipt_entry e;
243 struct ipt_entry_match m;
244 struct xt_policy_info p;
245 struct ipt_entry_target t;
246 struct xt_connmark_tginfo1 cm;
247 } ipt = {
248 .e = {
249 .target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) +
250 sizeof(ipt.p)),
251 .next_offset = sizeof(ipt),
252 },
253 .m = {
254 .u = {
255 .user = {
256 .match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.p)),
257 .name = "policy",
258 },
259 },
260 },
261 .p = {
262 .pol = {
263 {
264 .spi = spi,
265 .match.spi = 1,
266 },
267 },
268 .len = 1,
269 .flags = XT_POLICY_MATCH_IN,
270 },
271 .t = {
272 .u = {
273 .user = {
274 .target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.cm)),
275 .name = "CONNMARK",
276 .revision = 1,
277 },
278 },
279 },
280 .cm = {
281 .ctmark = mark,
282 .ctmask = ~0,
283 .nfmask = ~0,
284 .mode = XT_CONNMARK_SET,
285 },
286 };
287
288 if (!ts2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
289 !ts2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
290 {
291 return FALSE;
292 }
293 return manage_rule(ipth, "INPUT", add, &ipt.e);
294 }
295
296 /**
297 * Add outbund rule restoring CONNMARK on matching traffic
298 */
299 static bool manage_out(private_connmark_listener_t *this,
300 struct iptc_handle *ipth, bool add,
301 traffic_selector_t *dst, traffic_selector_t *src)
302 {
303 struct {
304 struct ipt_entry e;
305 struct ipt_entry_target t;
306 struct xt_connmark_tginfo1 cm;
307 } ipt = {
308 .e = {
309 .target_offset = XT_ALIGN(sizeof(ipt.e)),
310 .next_offset = sizeof(ipt),
311 },
312 .t = {
313 .u = {
314 .user = {
315 .target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.cm)),
316 .name = "CONNMARK",
317 .revision = 1,
318 },
319 },
320 },
321 .cm = {
322 .ctmask = ~0,
323 .nfmask = ~0,
324 .mode = XT_CONNMARK_RESTORE,
325 },
326 };
327
328 if (!ts2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
329 !ts2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
330 {
331 return FALSE;
332 }
333 return manage_rule(ipth, "OUTPUT", add, &ipt.e);
334 }
335
336 /**
337 * Initialize iptables handle, log error
338 */
339 static struct iptc_handle* init_handle()
340 {
341 struct iptc_handle *ipth;
342
343 ipth = iptc_init("mangle");
344 if (ipth)
345 {
346 return ipth;
347 }
348 DBG1(DBG_CFG, "initializing iptables failed: %s", iptc_strerror(errno));
349 return NULL;
350 }
351
352 /**
353 * Commit iptables rules, log error
354 */
355 static bool commit_handle(struct iptc_handle *ipth)
356 {
357 if (iptc_commit(ipth))
358 {
359 return TRUE;
360 }
361 DBG1(DBG_CFG, "forecast iptables commit failed: %s", iptc_strerror(errno));
362 return FALSE;
363 }
364
365 /**
366 * Add/Remove policies for a CHILD_SA using a iptables handle
367 */
368 static bool manage_policies(private_connmark_listener_t *this,
369 struct iptc_handle *ipth, host_t *dst, host_t *src,
370 bool encap, child_sa_t *child_sa, bool add)
371 {
372 traffic_selector_t *local, *remote;
373 enumerator_t *enumerator;
374 u_int32_t spi;
375 u_int mark;
376 bool done = TRUE;
377
378 spi = child_sa->get_spi(child_sa, TRUE);
379 mark = child_sa->get_mark(child_sa, TRUE).value;
380
381 enumerator = child_sa->create_policy_enumerator(child_sa);
382 while (enumerator->enumerate(enumerator, &local, &remote))
383 {
384 if (!manage_pre(this, ipth, add, mark, spi, encap, dst, src) ||
385 !manage_in(this, ipth, add, mark, spi, local, remote) ||
386 !manage_out(this, ipth, add, remote, local))
387 {
388 done = FALSE;
389 break;
390 }
391 }
392 enumerator->destroy(enumerator);
393
394 return done;
395 }
396
397 /**
398 * Check if rules should be installed for given CHILD_SA
399 */
400 static bool handle_sa(child_sa_t *child_sa)
401 {
402 return child_sa->get_mark(child_sa, TRUE).value &&
403 child_sa->get_mark(child_sa, FALSE).value &&
404 child_sa->get_mode(child_sa) == MODE_TRANSPORT &&
405 child_sa->get_protocol(child_sa) == PROTO_ESP;
406 }
407
408 METHOD(listener_t, child_updown, bool,
409 private_connmark_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
410 bool up)
411 {
412 struct iptc_handle *ipth;
413 host_t *dst, *src;
414 bool encap;
415
416 dst = ike_sa->get_my_host(ike_sa);
417 src = ike_sa->get_other_host(ike_sa);
418 encap = child_sa->has_encap(child_sa);
419
420 if (handle_sa(child_sa))
421 {
422 ipth = init_handle();
423 if (ipth)
424 {
425 if (manage_policies(this, ipth, dst, src, encap, child_sa, up))
426 {
427 commit_handle(ipth);
428 }
429 iptc_free(ipth);
430 }
431 }
432 return TRUE;
433 }
434
435 METHOD(listener_t, child_rekey, bool,
436 private_connmark_listener_t *this, ike_sa_t *ike_sa,
437 child_sa_t *old, child_sa_t *new)
438 {
439 struct iptc_handle *ipth;
440 host_t *dst, *src;
441 bool oldencap, newencap;
442
443 dst = ike_sa->get_my_host(ike_sa);
444 src = ike_sa->get_other_host(ike_sa);
445 oldencap = old->has_encap(old);
446 newencap = new->has_encap(new);
447
448 if (handle_sa(old))
449 {
450 ipth = init_handle();
451 if (ipth)
452 {
453 if (manage_policies(this, ipth, dst, src, oldencap, old, FALSE) &&
454 manage_policies(this, ipth, dst, src, newencap, new, TRUE))
455 {
456 commit_handle(ipth);
457 }
458 iptc_free(ipth);
459 }
460 }
461 return TRUE;
462 }
463
464 METHOD(listener_t, ike_update, bool,
465 private_connmark_listener_t *this, ike_sa_t *ike_sa,
466 bool local, host_t *new)
467 {
468 struct iptc_handle *ipth;
469 enumerator_t *enumerator;
470 child_sa_t *child_sa;
471 host_t *dst, *src;
472 bool oldencap, newencap;
473
474 if (local)
475 {
476 dst = new;
477 src = ike_sa->get_other_host(ike_sa);
478 }
479 else
480 {
481 dst = ike_sa->get_my_host(ike_sa);
482 src = new;
483 }
484 /* during ike_update(), has_encap() on the CHILD_SA has not yet been
485 * updated, but shows the old state. */
486 newencap = ike_sa->has_condition(ike_sa, COND_NAT_ANY);
487
488 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
489 while (enumerator->enumerate(enumerator, &child_sa))
490 {
491 if (handle_sa(child_sa))
492 {
493 oldencap = child_sa->has_encap(child_sa);
494 ipth = init_handle();
495 if (ipth)
496 {
497 if (manage_policies(this, ipth, dst, src, oldencap,
498 child_sa, FALSE) &&
499 manage_policies(this, ipth, dst, src, newencap,
500 child_sa, TRUE))
501 {
502 commit_handle(ipth);
503 }
504 iptc_free(ipth);
505 }
506 }
507 }
508 enumerator->destroy(enumerator);
509
510 return TRUE;
511 }
512
513 METHOD(connmark_listener_t, destroy, void,
514 private_connmark_listener_t *this)
515 {
516 free(this);
517 }
518
519 /**
520 * See header
521 */
522 connmark_listener_t *connmark_listener_create()
523 {
524 private_connmark_listener_t *this;
525
526 INIT(this,
527 .public = {
528 .listener = {
529 .ike_update = _ike_update,
530 .child_updown = _child_updown,
531 .child_rekey = _child_rekey,
532 },
533 .destroy = _destroy,
534 },
535 );
536
537 return &this->public;
538 }