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