From b34738ed08c2227300d554b139e2495ca5da97d6 Mon Sep 17 00:00:00 2001 From: Yves-Alexis Perez Date: Thu, 28 Jun 2012 21:16:07 +0200 Subject: Imported Upstream version 4.6.4 --- src/libhydra/plugins/kernel_pfkey/Makefile.in | 7 + .../plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 917 +++++++++++++++------ .../plugins/kernel_pfkey/kernel_pfkey_plugin.c | 17 +- 3 files changed, 686 insertions(+), 255 deletions(-) (limited to 'src/libhydra/plugins/kernel_pfkey') diff --git a/src/libhydra/plugins/kernel_pfkey/Makefile.in b/src/libhydra/plugins/kernel_pfkey/Makefile.in index 251483017..14c924b6f 100644 --- a/src/libhydra/plugins/kernel_pfkey/Makefile.in +++ b/src/libhydra/plugins/kernel_pfkey/Makefile.in @@ -195,6 +195,9 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ +attest_plugins = @attest_plugins@ +axis2c_CFLAGS = @axis2c_CFLAGS@ +axis2c_LIBS = @axis2c_LIBS@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ @@ -203,6 +206,7 @@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ c_plugins = @c_plugins@ +clearsilver_LIBS = @clearsilver_LIBS@ datadir = @datadir@ datarootdir = @datarootdir@ dbusservicedir = @dbusservicedir@ @@ -219,11 +223,13 @@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ +imcvdir = @imcvdir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ ipsecdir = @ipsecdir@ ipsecgroup = @ipsecgroup@ +ipseclibdir = @ipseclibdir@ ipsecuser = @ipsecuser@ libcharon_plugins = @libcharon_plugins@ libdir = @libdir@ @@ -267,6 +273,7 @@ sharedstatedir = @sharedstatedir@ soup_CFLAGS = @soup_CFLAGS@ soup_LIBS = @soup_LIBS@ srcdir = @srcdir@ +starter_plugins = @starter_plugins@ strongswan_conf = @strongswan_conf@ sysconfdir = @sysconfdir@ systemdsystemunitdir = @systemdsystemunitdir@ diff --git a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index b252b7092..da10edffe 100644 --- a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2010 Tobias Brunner + * Copyright (C) 2008-2011 Tobias Brunner * Copyright (C) 2008 Andreas Steffen * Hochschule fuer Technik Rapperswil * @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -99,8 +100,7 @@ #endif /** default priority of installed policies */ -#define PRIO_LOW 1024 -#define PRIO_HIGH 512 +#define PRIO_BASE 512 #ifdef __APPLE__ /** from xnu/bsd/net/pfkeyv2.h */ @@ -162,6 +162,11 @@ struct private_kernel_pfkey_ipsec_t */ linked_list_t *policies; + /** + * Hash table of IPsec SAs using policies (ipsec_sa_t) + */ + hashtable_t *sas; + /** * whether to install routes along policies */ @@ -199,19 +204,19 @@ typedef struct route_entry_t route_entry_t; * installed routing entry */ struct route_entry_t { - /** Name of the interface the route is bound to */ + /** name of the interface the route is bound to */ char *if_name; - /** Source ip of the route */ + /** source ip of the route */ host_t *src_ip; /** gateway for this route */ host_t *gateway; - /** Destination net */ + /** destination net */ chunk_t dst_net; - /** Destination net prefixlen */ + /** destination net prefixlen */ u_int8_t prefixlen; }; @@ -227,57 +232,222 @@ static void route_entry_destroy(route_entry_t *this) free(this); } +/** + * compare two route_entry_t objects + */ +static bool route_entry_equals(route_entry_t *a, route_entry_t *b) +{ + return a->if_name && b->if_name && streq(a->if_name, b->if_name) && + a->src_ip->equals(a->src_ip, b->src_ip) && + a->gateway->equals(a->gateway, b->gateway) && + chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen; +} + +typedef struct ipsec_sa_t ipsec_sa_t; + +/** + * IPsec SA assigned to a policy. + */ +struct ipsec_sa_t { + /** Source address of this SA */ + host_t *src; + + /** Destination address of this SA */ + host_t *dst; + + /** Description of this SA */ + ipsec_sa_cfg_t cfg; + + /** Reference count for this SA */ + refcount_t refcount; +}; + +/** + * Hash function for ipsec_sa_t objects + */ +static u_int ipsec_sa_hash(ipsec_sa_t *sa) +{ + return chunk_hash_inc(sa->src->get_address(sa->src), + chunk_hash_inc(sa->dst->get_address(sa->dst), + chunk_hash(chunk_from_thing(sa->cfg)))); +} + +/** + * Equality function for ipsec_sa_t objects + */ +static bool ipsec_sa_equals(ipsec_sa_t *sa, ipsec_sa_t *other_sa) +{ + return sa->src->ip_equals(sa->src, other_sa->src) && + sa->dst->ip_equals(sa->dst, other_sa->dst) && + memeq(&sa->cfg, &other_sa->cfg, sizeof(ipsec_sa_cfg_t)); +} + +/** + * Allocate or reference an IPsec SA object + */ +static ipsec_sa_t *ipsec_sa_create(private_kernel_pfkey_ipsec_t *this, + host_t *src, host_t *dst, + ipsec_sa_cfg_t *cfg) +{ + ipsec_sa_t *sa, *found; + INIT(sa, + .src = src, + .dst = dst, + .cfg = *cfg, + ); + found = this->sas->get(this->sas, sa); + if (!found) + { + sa->src = src->clone(src); + sa->dst = dst->clone(dst); + this->sas->put(this->sas, sa, sa); + } + else + { + free(sa); + sa = found; + } + ref_get(&sa->refcount); + return sa; +} + +/** + * Release and destroy an IPsec SA object + */ +static void ipsec_sa_destroy(private_kernel_pfkey_ipsec_t *this, + ipsec_sa_t *sa) +{ + if (ref_put(&sa->refcount)) + { + this->sas->remove(this->sas, sa); + DESTROY_IF(sa->src); + DESTROY_IF(sa->dst); + free(sa); + } +} + +typedef struct policy_sa_t policy_sa_t; +typedef struct policy_sa_fwd_t policy_sa_fwd_t; + +/** + * Mapping between a policy and an IPsec SA. + */ +struct policy_sa_t { + /** Priority assigned to the policy when installed with this SA */ + u_int32_t priority; + + /** Type of the policy */ + policy_type_t type; + + /** Assigned SA */ + ipsec_sa_t *sa; +}; + +/** + * For forward policies we also cache the traffic selectors in order to install + * the route. + */ +struct policy_sa_fwd_t { + /** Generic interface */ + policy_sa_t generic; + + /** Source traffic selector of this policy */ + traffic_selector_t *src_ts; + + /** Destination traffic selector of this policy */ + traffic_selector_t *dst_ts; +}; + +/** + * Create a policy_sa(_fwd)_t object + */ +static policy_sa_t *policy_sa_create(private_kernel_pfkey_ipsec_t *this, + policy_dir_t dir, policy_type_t type, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, ipsec_sa_cfg_t *cfg) +{ + policy_sa_t *policy; + + if (dir == POLICY_FWD) + { + policy_sa_fwd_t *fwd; + INIT(fwd, + .src_ts = src_ts->clone(src_ts), + .dst_ts = dst_ts->clone(dst_ts), + ); + policy = &fwd->generic; + } + else + { + INIT(policy, .priority = 0); + } + policy->type = type; + policy->sa = ipsec_sa_create(this, src, dst, cfg); + return policy; +} + +/** + * Destroy a policy_sa(_fwd)_t object + */ +static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t *dir, + private_kernel_pfkey_ipsec_t *this) +{ + if (*dir == POLICY_FWD) + { + policy_sa_fwd_t *fwd = (policy_sa_fwd_t*)policy; + fwd->src_ts->destroy(fwd->src_ts); + fwd->dst_ts->destroy(fwd->dst_ts); + } + ipsec_sa_destroy(this, policy->sa); + free(policy); +} + typedef struct policy_entry_t policy_entry_t; /** * installed kernel policy. */ struct policy_entry_t { - - /** reqid of this policy */ - u_int32_t reqid; - - /** index assigned by the kernel */ + /** Index assigned by the kernel */ u_int32_t index; - /** direction of this policy: in, out, forward */ + /** Direction of this policy: in, out, forward */ u_int8_t direction; - /** parameters of installed policy */ + /** Parameters of installed policy */ struct { - /** subnet and port */ + /** Subnet and port */ host_t *net; - /** subnet mask */ + /** Subnet mask */ u_int8_t mask; - /** protocol */ + /** Protocol */ u_int8_t proto; } src, dst; - /** associated route installed for this policy */ + /** Associated route installed for this policy */ route_entry_t *route; - /** by how many CHILD_SA's this policy is used */ - u_int refcount; + /** List of SAs this policy is used by, ordered by priority */ + linked_list_t *used_by; }; /** - * create a policy_entry_t object + * Create a policy_entry_t object */ static policy_entry_t *create_policy_entry(traffic_selector_t *src_ts, - traffic_selector_t *dst_ts, policy_dir_t dir, u_int32_t reqid) + traffic_selector_t *dst_ts, + policy_dir_t dir) { - policy_entry_t *policy = malloc_thing(policy_entry_t); - policy->reqid = reqid; - policy->index = 0; - policy->direction = dir; - policy->route = NULL; - policy->refcount = 0; + policy_entry_t *policy; + INIT(policy, + .direction = dir, + ); src_ts->to_subnet(src_ts, &policy->src.net, &policy->src.mask); dst_ts->to_subnet(dst_ts, &policy->dst.net, &policy->dst.mask); /* src or dest proto may be "any" (0), use more restrictive one */ - policy->src.proto = max(src_ts->get_protocol(src_ts), dst_ts->get_protocol(dst_ts)); + policy->src.proto = max(src_ts->get_protocol(src_ts), + dst_ts->get_protocol(dst_ts)); policy->src.proto = policy->src.proto ? policy->src.proto : IPSEC_PROTO_ANY; policy->dst.proto = policy->src.proto; @@ -285,23 +455,32 @@ static policy_entry_t *create_policy_entry(traffic_selector_t *src_ts, } /** - * destroy a policy_entry_t object + * Destroy a policy_entry_t object */ -static void policy_entry_destroy(policy_entry_t *this) +static void policy_entry_destroy(policy_entry_t *policy, + private_kernel_pfkey_ipsec_t *this) { - DESTROY_IF(this->src.net); - DESTROY_IF(this->dst.net); - if (this->route) + if (policy->route) { - route_entry_destroy(this->route); + route_entry_destroy(policy->route); } - free(this); + if (policy->used_by) + { + policy->used_by->invoke_function(policy->used_by, + (linked_list_invoke_t)policy_sa_destroy, + &policy->direction, this); + policy->used_by->destroy(policy->used_by); + } + DESTROY_IF(policy->src.net); + DESTROY_IF(policy->dst.net); + free(policy); } /** * compares two policy_entry_t */ -static inline bool policy_entry_equals(policy_entry_t *current, policy_entry_t *policy) +static inline bool policy_entry_equals(policy_entry_t *current, + policy_entry_t *policy) { return current->direction == policy->direction && current->src.proto == policy->src.proto && @@ -315,11 +494,41 @@ static inline bool policy_entry_equals(policy_entry_t *current, policy_entry_t * /** * compare the given kernel index with that of a policy */ -static inline bool policy_entry_match_byindex(policy_entry_t *current, u_int32_t *index) +static inline bool policy_entry_match_byindex(policy_entry_t *current, + u_int32_t *index) { return current->index == *index; } +/** + * Calculate the priority of a policy + */ +static inline u_int32_t get_priority(policy_entry_t *policy, + policy_priority_t prio) +{ + u_int32_t priority = PRIO_BASE; + switch (prio) + { + case POLICY_PRIORITY_FALLBACK: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_ROUTED: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_DEFAULT: + break; + } + /* calculate priority based on selector size, small size = high prio */ + priority -= policy->src.mask; + priority -= policy->dst.mask; + priority <<= 2; /* make some room for the two flags */ + priority += policy->src.net->get_port(policy->src.net) || + policy->dst.net->get_port(policy->dst.net) ? + 0 : 2; + priority += policy->src.proto != IPSEC_PROTO_ANY ? 0 : 1; + return priority; +} + typedef struct pfkey_msg_t pfkey_msg_t; struct pfkey_msg_t @@ -470,6 +679,23 @@ static u_int8_t dir2kernel(policy_dir_t dir) } } +/** + * convert the policy type to the one defined in ipsec.h + */ +static inline u_int16_t type2kernel(policy_type_t type) +{ + switch (type) + { + case POLICY_IPSEC: + return IPSEC_POLICY_IPSEC; + case POLICY_PASS: + return IPSEC_POLICY_NONE; + case POLICY_DROP: + return IPSEC_POLICY_DISCARD; + } + return type; +} + #ifdef SADB_X_MIGRATE /** * convert the policy direction in ipsec.h to the general one. @@ -665,19 +891,19 @@ static void add_encap_ext(struct sadb_msg *msg, host_t *src, host_t *dst) nat_type = (struct sadb_x_nat_t_type*)PFKEY_EXT_ADD_NEXT(msg); nat_type->sadb_x_nat_t_type_exttype = SADB_X_EXT_NAT_T_TYPE; - nat_type->sadb_x_nat_t_type_len = PFKEY_LEN(sizeof(struct sadb_x_nat_t_type)); + nat_type->sadb_x_nat_t_type_len = PFKEY_LEN(sizeof(*nat_type)); nat_type->sadb_x_nat_t_type_type = UDP_ENCAP_ESPINUDP; PFKEY_EXT_ADD(msg, nat_type); nat_port = (struct sadb_x_nat_t_port*)PFKEY_EXT_ADD_NEXT(msg); nat_port->sadb_x_nat_t_port_exttype = SADB_X_EXT_NAT_T_SPORT; - nat_port->sadb_x_nat_t_port_len = PFKEY_LEN(sizeof(struct sadb_x_nat_t_port)); + nat_port->sadb_x_nat_t_port_len = PFKEY_LEN(sizeof(*nat_port)); nat_port->sadb_x_nat_t_port_port = htons(src->get_port(src)); PFKEY_EXT_ADD(msg, nat_port); nat_port = (struct sadb_x_nat_t_port*)PFKEY_EXT_ADD_NEXT(msg); nat_port->sadb_x_nat_t_port_exttype = SADB_X_EXT_NAT_T_DPORT; - nat_port->sadb_x_nat_t_port_len = PFKEY_LEN(sizeof(struct sadb_x_nat_t_port)); + nat_port->sadb_x_nat_t_port_len = PFKEY_LEN(sizeof(*nat_port)); nat_port->sadb_x_nat_t_port_port = htons(dst->get_port(dst)); PFKEY_EXT_ADD(msg, nat_port); } @@ -694,9 +920,11 @@ static traffic_selector_t* sadb_address2ts(struct sadb_address *address) /* The Linux 2.6 kernel does not set the protocol and port information * in the src and dst sadb_address extensions of the SADB_ACQUIRE message. */ - host = host_create_from_sockaddr((sockaddr_t*)&address[1]) ; - ts = traffic_selector_create_from_subnet(host, address->sadb_address_prefixlen, - address->sadb_address_proto, host->get_port(host)); + host = host_create_from_sockaddr((sockaddr_t*)&address[1]); + ts = traffic_selector_create_from_subnet(host, + address->sadb_address_prefixlen, + address->sadb_address_proto, + host->get_port(host)); return ts; } @@ -729,7 +957,8 @@ static status_t parse_pfkey_message(struct sadb_msg *msg, pfkey_msg_t *out) if ((ext->sadb_ext_type > SADB_EXT_MAX) || (!ext->sadb_ext_type)) { - DBG1(DBG_KNL, "type of PF_KEY extension (%d) is invalid", ext->sadb_ext_type); + DBG1(DBG_KNL, "type of PF_KEY extension (%d) is invalid", + ext->sadb_ext_type); break; } @@ -784,7 +1013,8 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket continue; } this->mutex_pfkey->unlock(this->mutex_pfkey); - DBG1(DBG_KNL, "error sending to PF_KEY socket: %s", strerror(errno)); + DBG1(DBG_KNL, "error sending to PF_KEY socket: %s", + strerror(errno)); return FAILED; } break; @@ -804,7 +1034,8 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket /* interrupted, try again */ continue; } - DBG1(DBG_KNL, "error reading from PF_KEY socket: %s", strerror(errno)); + DBG1(DBG_KNL, "error reading from PF_KEY socket: %s", + strerror(errno)); this->mutex_pfkey->unlock(this->mutex_pfkey); return FAILED; } @@ -817,7 +1048,8 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket } if (msg->sadb_msg_len > len / PFKEY_ALIGNMENT) { - DBG1(DBG_KNL, "buffer was too small to receive the complete PF_KEY message"); + DBG1(DBG_KNL, "buffer was too small to receive the complete PF_KEY " + "message"); this->mutex_pfkey->unlock(this->mutex_pfkey); return FAILED; } @@ -829,7 +1061,8 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket if (msg->sadb_msg_seq != this->seq) { DBG1(DBG_KNL, "received PF_KEY message with unexpected sequence " - "number, was %d expected %d", msg->sadb_msg_seq, this->seq); + "number, was %d expected %d", msg->sadb_msg_seq, + this->seq); if (msg->sadb_msg_seq == 0) { /* FreeBSD and Mac OS X do this for the response to @@ -849,8 +1082,8 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket if (msg->sadb_msg_type != in->sadb_msg_type) { DBG2(DBG_KNL, "received PF_KEY message of wrong type, " - "was %d expected %d, ignoring", - msg->sadb_msg_type, in->sadb_msg_type); + "was %d expected %d, ignoring", msg->sadb_msg_type, + in->sadb_msg_type); } break; } @@ -860,7 +1093,6 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket memcpy(*out, buf, len); this->mutex_pfkey->unlock(this->mutex_pfkey); - return SUCCESS; } @@ -868,7 +1100,8 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket * Send a message to the default PF_KEY socket and handle the response. */ static status_t pfkey_send(private_kernel_pfkey_ipsec_t *this, - struct sadb_msg *in, struct sadb_msg **out, size_t *out_len) + struct sadb_msg *in, struct sadb_msg **out, + size_t *out_len) { return pfkey_send_socket(this, this->socket, in, out, out_len); } @@ -876,12 +1109,14 @@ static status_t pfkey_send(private_kernel_pfkey_ipsec_t *this, /** * Process a SADB_ACQUIRE message from the kernel */ -static void process_acquire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* msg) +static void process_acquire(private_kernel_pfkey_ipsec_t *this, + struct sadb_msg* msg) { pfkey_msg_t response; u_int32_t index, reqid = 0; traffic_selector_t *src_ts, *dst_ts; policy_entry_t *policy; + policy_sa_t *sa; switch (msg->sadb_msg_satype) { @@ -904,18 +1139,21 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* index = response.x_policy->sadb_x_policy_id; this->mutex->lock(this->mutex); if (this->policies->find_first(this->policies, - (linked_list_match_t)policy_entry_match_byindex, (void**)&policy, &index) == SUCCESS) + (linked_list_match_t)policy_entry_match_byindex, + (void**)&policy, &index) == SUCCESS && + policy->used_by->get_first(policy->used_by, (void**)&sa) == SUCCESS) { - reqid = policy->reqid; + reqid = sa->sa->cfg.reqid; } else { - DBG1(DBG_KNL, "received an SADB_ACQUIRE with policy id %d but no" - " matching policy found", index); + DBG1(DBG_KNL, "received an SADB_ACQUIRE with policy id %d but no " + "matching policy found", index); } + this->mutex->unlock(this->mutex); + src_ts = sadb_address2ts(response.src); dst_ts = sadb_address2ts(response.dst); - this->mutex->unlock(this->mutex); hydra->kernel_interface->acquire(hydra->kernel_interface, reqid, src_ts, dst_ts); @@ -924,7 +1162,8 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* /** * Process a SADB_EXPIRE message from the kernel */ -static void process_expire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* msg) +static void process_expire(private_kernel_pfkey_ipsec_t *this, + struct sadb_msg* msg) { pfkey_msg_t response; u_int8_t protocol; @@ -946,8 +1185,8 @@ static void process_expire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* if (protocol != IPPROTO_ESP && protocol != IPPROTO_AH) { - DBG2(DBG_KNL, "ignoring SADB_EXPIRE for SA with SPI %.8x and reqid {%u} " - "which is not a CHILD_SA", ntohl(spi), reqid); + DBG2(DBG_KNL, "ignoring SADB_EXPIRE for SA with SPI %.8x and " + "reqid {%u} which is not a CHILD_SA", ntohl(spi), reqid); return; } @@ -959,7 +1198,8 @@ static void process_expire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* /** * Process a SADB_X_MIGRATE message from the kernel */ -static void process_migrate(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* msg) +static void process_migrate(private_kernel_pfkey_ipsec_t *this, + struct sadb_msg* msg) { pfkey_msg_t response; traffic_selector_t *src_ts, *dst_ts; @@ -1014,10 +1254,12 @@ static void process_migrate(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* /** * Process a SADB_X_NAT_T_NEW_MAPPING message from the kernel */ -static void process_mapping(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* msg) +static void process_mapping(private_kernel_pfkey_ipsec_t *this, + struct sadb_msg* msg) { pfkey_msg_t response; u_int32_t spi, reqid; + sockaddr_t *sa; host_t *host; DBG2(DBG_KNL, "received an SADB_X_NAT_T_NEW_MAPPING"); @@ -1038,30 +1280,33 @@ static void process_mapping(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* spi = response.sa->sadb_sa_spi; reqid = response.x_sa2->sadb_x_sa2_reqid; - if (satype2proto(msg->sadb_msg_satype) == IPPROTO_ESP) + if (satype2proto(msg->sadb_msg_satype) != IPPROTO_ESP) + { + return; + } + + sa = (sockaddr_t*)(response.dst + 1); + switch (sa->sa_family) { - sockaddr_t *sa = (sockaddr_t*)(response.dst + 1); - switch (sa->sa_family) + case AF_INET: { - case AF_INET: - { - struct sockaddr_in *sin = (struct sockaddr_in*)sa; - sin->sin_port = htons(response.x_natt_dport->sadb_x_nat_t_port_port); - } - case AF_INET6: - { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa; - sin6->sin6_port = htons(response.x_natt_dport->sadb_x_nat_t_port_port); - } - default: - break; + struct sockaddr_in *sin = (struct sockaddr_in*)sa; + sin->sin_port = htons(response.x_natt_dport->sadb_x_nat_t_port_port); } - host = host_create_from_sockaddr(sa); - if (host) + case AF_INET6: { - hydra->kernel_interface->mapping(hydra->kernel_interface, reqid, - spi, host); + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa; + sin6->sin6_port = htons(response.x_natt_dport->sadb_x_nat_t_port_port); } + default: + break; + } + + host = host_create_from_sockaddr(sa); + if (host) + { + hydra->kernel_interface->mapping(hydra->kernel_interface, reqid, + spi, host); } } #endif /*SADB_X_NAT_T_NEW_MAPPING*/ @@ -1073,8 +1318,8 @@ static job_requeue_t receive_events(private_kernel_pfkey_ipsec_t *this) { unsigned char buf[PFKEY_BUFFER_SIZE]; struct sadb_msg *msg = (struct sadb_msg*)buf; - int len; bool oldstate; + int len; oldstate = thread_cancelability(TRUE); len = recvfrom(this->socket_events, buf, sizeof(buf), 0, NULL, 0); @@ -1109,7 +1354,8 @@ static job_requeue_t receive_events(private_kernel_pfkey_ipsec_t *this) } if (msg->sadb_msg_len > len / PFKEY_ALIGNMENT) { - DBG1(DBG_KNL, "buffer was too small to receive the complete PF_KEY message"); + DBG1(DBG_KNL, "buffer was too small to receive the complete " + "PF_KEY message"); return JOB_REQUEUE_DIRECT; } @@ -1179,7 +1425,7 @@ METHOD(kernel_ipsec_t, get_spi, status_t, if (out->sadb_msg_errno) { DBG1(DBG_KNL, "allocating SPI failed: %s (%d)", - strerror(out->sadb_msg_errno), out->sadb_msg_errno); + strerror(out->sadb_msg_errno), out->sadb_msg_errno); } else if (parse_pfkey_message(out, &response) == SUCCESS) { @@ -1222,7 +1468,8 @@ METHOD(kernel_ipsec_t, add_sa, status_t, memset(&request, 0, sizeof(request)); - DBG2(DBG_KNL, "adding SAD entry with SPI %.8x and reqid {%u}", ntohl(spi), reqid); + DBG2(DBG_KNL, "adding SAD entry with SPI %.8x and reqid {%u}", + ntohl(spi), reqid); msg = (struct sadb_msg*)request; msg->sadb_msg_version = PF_KEY_V2; @@ -1287,11 +1534,11 @@ METHOD(kernel_ipsec_t, add_sa, status_t, if (!sa->sadb_sa_encrypt) { DBG1(DBG_KNL, "algorithm %N not supported by kernel!", - encryption_algorithm_names, enc_alg); + encryption_algorithm_names, enc_alg); return FAILED; } DBG2(DBG_KNL, " using encryption algorithm %N with key size %d", - encryption_algorithm_names, enc_alg, enc_key.len * 8); + encryption_algorithm_names, enc_alg, enc_key.len * 8); key = (struct sadb_key*)PFKEY_EXT_ADD_NEXT(msg); key->sadb_key_exttype = SADB_EXT_KEY_ENCRYPT; @@ -1307,11 +1554,11 @@ METHOD(kernel_ipsec_t, add_sa, status_t, if (!sa->sadb_sa_auth) { DBG1(DBG_KNL, "algorithm %N not supported by kernel!", - integrity_algorithm_names, int_alg); + integrity_algorithm_names, int_alg); return FAILED; } DBG2(DBG_KNL, " using integrity algorithm %N with key size %d", - integrity_algorithm_names, int_alg, int_key.len * 8); + integrity_algorithm_names, int_alg, int_key.len * 8); key = (struct sadb_key*)PFKEY_EXT_ADD_NEXT(msg); key->sadb_key_exttype = SADB_EXT_KEY_AUTH; @@ -1368,8 +1615,8 @@ METHOD(kernel_ipsec_t, update_sa, status_t, if (!src->ip_equals(src, new_src) || !dst->ip_equals(dst, new_dst)) { - DBG1(DBG_KNL, "unable to update SAD entry with SPI %.8x: address changes" - " are not supported", ntohl(spi)); + DBG1(DBG_KNL, "unable to update SAD entry with SPI %.8x: address " + "changes are not supported", ntohl(spi)); return NOT_SUPPORTED; } @@ -1396,27 +1643,27 @@ METHOD(kernel_ipsec_t, update_sa, status_t, if (pfkey_send(this, msg, &out, &len) != SUCCESS) { - DBG1(DBG_KNL, "unable to query SAD entry with SPI %.8x", - ntohl(spi)); + DBG1(DBG_KNL, "unable to query SAD entry with SPI %.8x", ntohl(spi)); return FAILED; } else if (out->sadb_msg_errno) { DBG1(DBG_KNL, "unable to query SAD entry with SPI %.8x: %s (%d)", - ntohl(spi), strerror(out->sadb_msg_errno), out->sadb_msg_errno); + ntohl(spi), strerror(out->sadb_msg_errno), + out->sadb_msg_errno); free(out); return FAILED; } else if (parse_pfkey_message(out, &response) != SUCCESS) { - DBG1(DBG_KNL, "unable to query SAD entry with SPI %.8x: parsing response " - "from kernel failed", ntohl(spi)); + DBG1(DBG_KNL, "unable to query SAD entry with SPI %.8x: parsing " + "response from kernel failed", ntohl(spi)); free(out); return FAILED; } DBG2(DBG_KNL, "updating SAD entry with SPI %.8x from %#H..%#H to %#H..%#H", - ntohl(spi), src, dst, new_src, new_dst); + ntohl(spi), src, dst, new_src, new_dst); memset(&request, 0, sizeof(request)); @@ -1476,7 +1723,8 @@ METHOD(kernel_ipsec_t, update_sa, status_t, else if (out->sadb_msg_errno) { DBG1(DBG_KNL, "unable to update SAD entry with SPI %.8x: %s (%d)", - ntohl(spi), strerror(out->sadb_msg_errno), out->sadb_msg_errno); + ntohl(spi), strerror(out->sadb_msg_errno), + out->sadb_msg_errno); free(out); return FAILED; } @@ -1525,7 +1773,8 @@ METHOD(kernel_ipsec_t, query_sa, status_t, else if (out->sadb_msg_errno) { DBG1(DBG_KNL, "unable to query SAD entry with SPI %.8x: %s (%d)", - ntohl(spi), strerror(out->sadb_msg_errno), out->sadb_msg_errno); + ntohl(spi), strerror(out->sadb_msg_errno), + out->sadb_msg_errno); free(out); return FAILED; } @@ -1580,7 +1829,8 @@ METHOD(kernel_ipsec_t, del_sa, status_t, else if (out->sadb_msg_errno) { DBG1(DBG_KNL, "unable to delete SAD entry with SPI %.8x: %s (%d)", - ntohl(spi), strerror(out->sadb_msg_errno), out->sadb_msg_errno); + ntohl(spi), strerror(out->sadb_msg_errno), + out->sadb_msg_errno); free(out); return FAILED; } @@ -1590,57 +1840,60 @@ METHOD(kernel_ipsec_t, del_sa, status_t, return SUCCESS; } -METHOD(kernel_ipsec_t, add_policy, status_t, - private_kernel_pfkey_ipsec_t *this, host_t *src, host_t *dst, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts, - policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, - mark_t mark, bool routed) +METHOD(kernel_ipsec_t, flush_sas, status_t, + private_kernel_pfkey_ipsec_t *this) { unsigned char request[PFKEY_BUFFER_SIZE]; struct sadb_msg *msg, *out; - struct sadb_x_policy *pol; - struct sadb_x_ipsecrequest *req; - policy_entry_t *policy, *found = NULL; - pfkey_msg_t response; size_t len; - if (dir2kernel(direction) == IPSEC_DIR_INVALID) - { - /* FWD policies are not supported on all platforms */ - return SUCCESS; - } + memset(&request, 0, sizeof(request)); - /* create a policy */ - policy = create_policy_entry(src_ts, dst_ts, direction, sa->reqid); + DBG2(DBG_KNL, "flushing all SAD entries"); - /* find a matching policy */ - this->mutex->lock(this->mutex); - if (this->policies->find_first(this->policies, - (linked_list_match_t)policy_entry_equals, (void**)&found, policy) == SUCCESS) + msg = (struct sadb_msg*)request; + msg->sadb_msg_version = PF_KEY_V2; + msg->sadb_msg_type = SADB_FLUSH; + msg->sadb_msg_satype = SADB_SATYPE_UNSPEC; + msg->sadb_msg_len = PFKEY_LEN(sizeof(struct sadb_msg)); + + if (pfkey_send(this, msg, &out, &len) != SUCCESS) { - /* use existing policy */ - found->refcount++; - DBG2(DBG_KNL, "policy %R === %R %N already exists, increasing " - "refcount", src_ts, dst_ts, - policy_dir_names, direction); - policy_entry_destroy(policy); - policy = found; + DBG1(DBG_KNL, "unable to flush SAD entries"); + return FAILED; } - else + else if (out->sadb_msg_errno) { - /* apply the new one, if we have no such policy */ - this->policies->insert_last(this->policies, policy); - policy->refcount = 1; + DBG1(DBG_KNL, "unable to flush SAD entries: %s (%d)", + strerror(out->sadb_msg_errno), out->sadb_msg_errno); + free(out); + return FAILED; } + free(out); + return SUCCESS; +} - memset(&request, 0, sizeof(request)); +/** + * Add or update a policy in the kernel. + * + * Note: The mutex has to be locked when entering this function. + */ +static status_t add_policy_internal(private_kernel_pfkey_ipsec_t *this, + policy_entry_t *policy, policy_sa_t *mapping, bool update) +{ + unsigned char request[PFKEY_BUFFER_SIZE]; + struct sadb_msg *msg, *out; + struct sadb_x_policy *pol; + struct sadb_x_ipsecrequest *req; + ipsec_sa_t *ipsec = mapping->sa; + pfkey_msg_t response; + size_t len; - DBG2(DBG_KNL, "adding policy %R === %R %N", src_ts, dst_ts, - policy_dir_names, direction); + memset(&request, 0, sizeof(request)); msg = (struct sadb_msg*)request; msg->sadb_msg_version = PF_KEY_V2; - msg->sadb_msg_type = found ? SADB_X_SPDUPDATE : SADB_X_SPDADD; + msg->sadb_msg_type = update ? SADB_X_SPDUPDATE : SADB_X_SPDADD; msg->sadb_msg_satype = 0; msg->sadb_msg_len = PFKEY_LEN(sizeof(struct sadb_msg)); @@ -1648,32 +1901,27 @@ METHOD(kernel_ipsec_t, add_policy, status_t, pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY; pol->sadb_x_policy_len = PFKEY_LEN(sizeof(struct sadb_x_policy)); pol->sadb_x_policy_id = 0; - pol->sadb_x_policy_dir = dir2kernel(direction); - pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC; + pol->sadb_x_policy_dir = dir2kernel(policy->direction); + pol->sadb_x_policy_type = type2kernel(mapping->type); #ifdef HAVE_STRUCT_SADB_X_POLICY_SADB_X_POLICY_PRIORITY - /* calculate priority based on selector size, small size = high prio */ - pol->sadb_x_policy_priority = routed ? PRIO_LOW : PRIO_HIGH; - pol->sadb_x_policy_priority -= policy->src.mask; - pol->sadb_x_policy_priority -= policy->dst.mask; - pol->sadb_x_policy_priority <<= 2; /* make some room for the flags */ - pol->sadb_x_policy_priority += policy->src.net->get_port(policy->src.net) || - policy->dst.net->get_port(policy->dst.net) ? 0 : 2; - pol->sadb_x_policy_priority += policy->src.proto != IPSEC_PROTO_ANY ? 0 : 1; + pol->sadb_x_policy_priority = mapping->priority; #endif - /* one or more sadb_x_ipsecrequest extensions are added to the sadb_x_policy extension */ + /* one or more sadb_x_ipsecrequest extensions are added to the + * sadb_x_policy extension */ req = (struct sadb_x_ipsecrequest*)(pol + 1); - req->sadb_x_ipsecrequest_proto = sa->esp.use ? IPPROTO_ESP : IPPROTO_AH; - /* !!! the length of this struct MUST be in octets instead of 64 bit words */ + req->sadb_x_ipsecrequest_proto = ipsec->cfg.esp.use ? IPPROTO_ESP + : IPPROTO_AH; + /* !!! the length here MUST be in octets instead of 64 bit words */ req->sadb_x_ipsecrequest_len = sizeof(struct sadb_x_ipsecrequest); - req->sadb_x_ipsecrequest_mode = mode2kernel(sa->mode); - req->sadb_x_ipsecrequest_reqid = sa->reqid; + req->sadb_x_ipsecrequest_mode = mode2kernel(ipsec->cfg.mode); + req->sadb_x_ipsecrequest_reqid = ipsec->cfg.reqid; req->sadb_x_ipsecrequest_level = IPSEC_LEVEL_UNIQUE; - if (sa->mode == MODE_TUNNEL) + if (ipsec->cfg.mode == MODE_TUNNEL) { - len = hostcpy(req + 1, src); + len = hostcpy(req + 1, ipsec->src); req->sadb_x_ipsecrequest_len += len; - len = hostcpy((char*)(req + 1) + len, dst); + len = hostcpy((char*)(req + 1) + len, ipsec->dst); req->sadb_x_ipsecrequest_len += len; } @@ -1701,33 +1949,31 @@ METHOD(kernel_ipsec_t, add_policy, status_t, if (pfkey_send(this, msg, &out, &len) != SUCCESS) { - DBG1(DBG_KNL, "unable to add policy %R === %R %N", src_ts, dst_ts, - policy_dir_names, direction); return FAILED; } else if (out->sadb_msg_errno) { - DBG1(DBG_KNL, "unable to add policy %R === %R %N: %s (%d)", src_ts, dst_ts, - policy_dir_names, direction, - strerror(out->sadb_msg_errno), out->sadb_msg_errno); + DBG1(DBG_KNL, "unable to %s policy: %s (%d)", + update ? "update" : "add", strerror(out->sadb_msg_errno), + out->sadb_msg_errno); free(out); return FAILED; } else if (parse_pfkey_message(out, &response) != SUCCESS) { - DBG1(DBG_KNL, "unable to add policy %R === %R %N: parsing response " - "from kernel failed", src_ts, dst_ts, policy_dir_names, direction); + DBG1(DBG_KNL, "unable to %s policy: parsing response from kernel " + "failed", update ? "update" : "add"); free(out); return FAILED; } - this->mutex->lock(this->mutex); - /* we try to find the policy again and update the kernel index */ - if (this->policies->find_last(this->policies, NULL, (void**)&policy) != SUCCESS) + this->mutex->lock(this->mutex); + if (this->policies->find_last(this->policies, NULL, + (void**)&policy) != SUCCESS) { - DBG2(DBG_KNL, "unable to update index, the policy %R === %R %N is " - "already gone, ignoring", src_ts, dst_ts, policy_dir_names, direction); + DBG2(DBG_KNL, "unable to update index, the policy is already gone, " + "ignoring"); this->mutex->unlock(this->mutex); free(out); return SUCCESS; @@ -1736,53 +1982,83 @@ METHOD(kernel_ipsec_t, add_policy, status_t, free(out); /* install a route, if: - * - we are NOT updating a policy * - this is a forward policy (to just get one for each child) * - we are in tunnel mode - * - we are not using IPv6 (does not work correctly yet!) * - routing is not disabled via strongswan.conf */ - if (policy->route == NULL && direction == POLICY_FWD && - sa->mode != MODE_TRANSPORT && src->get_family(src) != AF_INET6 && - this->install_routes) + if (policy->direction == POLICY_FWD && + ipsec->cfg.mode != MODE_TRANSPORT && this->install_routes) { route_entry_t *route = malloc_thing(route_entry_t); + policy_sa_fwd_t *fwd = (policy_sa_fwd_t*)mapping; if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, - dst_ts, &route->src_ip) == SUCCESS) + fwd->dst_ts, &route->src_ip) == SUCCESS) { /* get the nexthop to src (src as we are in POLICY_FWD).*/ route->gateway = hydra->kernel_interface->get_nexthop( - hydra->kernel_interface, src); + hydra->kernel_interface, ipsec->src); + /* install route via outgoing interface */ route->if_name = hydra->kernel_interface->get_interface( - hydra->kernel_interface, dst); - route->dst_net = chunk_clone(policy->src.net->get_address(policy->src.net)); + hydra->kernel_interface, ipsec->dst); + route->dst_net = chunk_clone(policy->src.net->get_address( + policy->src.net)); route->prefixlen = policy->src.mask; - if (route->if_name) + if (!route->if_name) { - switch (hydra->kernel_interface->add_route( - hydra->kernel_interface, route->dst_net, - route->prefixlen, route->gateway, - route->src_ip, route->if_name)) + this->mutex->unlock(this->mutex); + route_entry_destroy(route); + return SUCCESS; + } + + if (policy->route) + { + route_entry_t *old = policy->route; + if (route_entry_equals(old, route)) + { /* keep previously installed route. since it might have + * still been removed by an address change, we install it + * again but ignore the result */ + hydra->kernel_interface->add_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, route->gateway, + route->src_ip, route->if_name); + this->mutex->unlock(this->mutex); + route_entry_destroy(route); + return SUCCESS; + } + /* uninstall previously installed route */ + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + old->dst_net, old->prefixlen, old->gateway, + old->src_ip, old->if_name) != SUCCESS) { - default: - DBG1(DBG_KNL, "unable to install source route for %H", - route->src_ip); - /* FALL */ - case ALREADY_DONE: - /* route exists, do not uninstall */ - route_entry_destroy(route); - break; - case SUCCESS: - /* cache the installed route */ - policy->route = route; - break; + DBG1(DBG_KNL, "error uninstalling route installed with " + "policy %R === %R %N", fwd->src_ts, + fwd->dst_ts, policy_dir_names, + policy->direction); } + route_entry_destroy(old); + policy->route = NULL; } - else + + DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", + fwd->src_ts, route->gateway, route->src_ip, route->if_name); + switch (hydra->kernel_interface->add_route( + hydra->kernel_interface, route->dst_net, + route->prefixlen, route->gateway, + route->src_ip, route->if_name)) { - route_entry_destroy(route); + default: + DBG1(DBG_KNL, "unable to install source route for %H", + route->src_ip); + /* FALL */ + case ALREADY_DONE: + /* route exists, do not uninstall */ + route_entry_destroy(route); + break; + case SUCCESS: + /* cache the installed route */ + policy->route = route; + break; } } else @@ -1790,9 +2066,82 @@ METHOD(kernel_ipsec_t, add_policy, status_t, free(route); } } - this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_ipsec_t, add_policy, status_t, + private_kernel_pfkey_ipsec_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, + mark_t mark, policy_priority_t priority) +{ + policy_entry_t *policy, *found = NULL; + policy_sa_t *assigned_sa, *current_sa; + enumerator_t *enumerator; + bool update = TRUE; + + if (dir2kernel(direction) == IPSEC_DIR_INVALID) + { /* FWD policies are not supported on all platforms */ + return SUCCESS; + } + + /* create a policy */ + policy = create_policy_entry(src_ts, dst_ts, direction); + + /* find a matching policy */ + this->mutex->lock(this->mutex); + if (this->policies->find_first(this->policies, + (linked_list_match_t)policy_entry_equals, + (void**)&found, policy) == SUCCESS) + { /* use existing policy */ + DBG2(DBG_KNL, "policy %R === %R %N already exists, increasing " + "refcount", src_ts, dst_ts, policy_dir_names, direction); + policy_entry_destroy(policy, this); + policy = found; + } + else + { /* use the new one, if we have no such policy */ + this->policies->insert_last(this->policies, policy); + policy->used_by = linked_list_create(); + } + /* cache the assigned IPsec SA */ + assigned_sa = policy_sa_create(this, direction, type, src, dst, src_ts, + dst_ts, sa); + assigned_sa->priority = get_priority(policy, priority); + + /* insert the SA according to its priority */ + enumerator = policy->used_by->create_enumerator(policy->used_by); + while (enumerator->enumerate(enumerator, (void**)¤t_sa)) + { + if (current_sa->priority >= assigned_sa->priority) + { + break; + } + update = FALSE; + } + policy->used_by->insert_before(policy->used_by, enumerator, assigned_sa); + enumerator->destroy(enumerator); + + if (!update) + { /* we don't update the policy if the priority is lower than that of the + * currently installed one */ + this->mutex->unlock(this->mutex); + return SUCCESS; + } + + DBG2(DBG_KNL, "%s policy %R === %R %N", + found ? "updating" : "adding", src_ts, dst_ts, + policy_dir_names, direction); + + if (add_policy_internal(this, policy, assigned_sa, found) != SUCCESS) + { + DBG1(DBG_KNL, "unable to %s policy %R === %R %N", + found ? "update" : "add", src_ts, dst_ts, + policy_dir_names, direction); + return FAILED; + } return SUCCESS; } @@ -1809,8 +2158,7 @@ METHOD(kernel_ipsec_t, query_policy, status_t, size_t len; if (dir2kernel(direction) == IPSEC_DIR_INVALID) - { - /* FWD policies are not supported on all platforms */ + { /* FWD policies are not supported on all platforms */ return NOT_FOUND; } @@ -1818,20 +2166,21 @@ METHOD(kernel_ipsec_t, query_policy, status_t, policy_dir_names, direction); /* create a policy */ - policy = create_policy_entry(src_ts, dst_ts, direction, 0); + policy = create_policy_entry(src_ts, dst_ts, direction); /* find a matching policy */ this->mutex->lock(this->mutex); if (this->policies->find_first(this->policies, - (linked_list_match_t)policy_entry_equals, (void**)&found, policy) != SUCCESS) + (linked_list_match_t)policy_entry_equals, + (void**)&found, policy) != SUCCESS) { DBG1(DBG_KNL, "querying policy %R === %R %N failed, not found", src_ts, dst_ts, policy_dir_names, direction); - policy_entry_destroy(policy); + policy_entry_destroy(policy, this); this->mutex->unlock(this->mutex); return NOT_FOUND; } - policy_entry_destroy(policy); + policy_entry_destroy(policy, this); policy = found; memset(&request, 0, sizeof(request)); @@ -1874,17 +2223,19 @@ METHOD(kernel_ipsec_t, query_policy, status_t, else if (parse_pfkey_message(out, &response) != SUCCESS) { DBG1(DBG_KNL, "unable to query policy %R === %R %N: parsing response " - "from kernel failed", src_ts, dst_ts, policy_dir_names, direction); + "from kernel failed", src_ts, dst_ts, policy_dir_names, + direction); free(out); return FAILED; } else if (response.lft_current == NULL) { DBG1(DBG_KNL, "unable to query policy %R === %R %N: kernel reports no " - "use time", src_ts, dst_ts, policy_dir_names, direction); + "use time", src_ts, dst_ts, policy_dir_names, direction); free(out); return FAILED; } + /* we need the monotonic time, but the kernel returns system time. */ if (response.lft_current->sadb_lifetime_usetime) { @@ -1896,25 +2247,26 @@ METHOD(kernel_ipsec_t, query_policy, status_t, *use_time = 0; } free(out); - return SUCCESS; } METHOD(kernel_ipsec_t, del_policy, status_t, private_kernel_pfkey_ipsec_t *this, traffic_selector_t *src_ts, - traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark, - bool unrouted) + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t prio) { unsigned char request[PFKEY_BUFFER_SIZE]; struct sadb_msg *msg, *out; struct sadb_x_policy *pol; policy_entry_t *policy, *found = NULL; - route_entry_t *route; + policy_sa_t *mapping; + enumerator_t *enumerator; + bool is_installed = TRUE; + u_int32_t priority; size_t len; if (dir2kernel(direction) == IPSEC_DIR_INVALID) - { - /* FWD policies are not supported on all platforms */ + { /* FWD policies are not supported on all platforms */ return SUCCESS; } @@ -1922,35 +2274,59 @@ METHOD(kernel_ipsec_t, del_policy, status_t, policy_dir_names, direction); /* create a policy */ - policy = create_policy_entry(src_ts, dst_ts, direction, 0); + policy = create_policy_entry(src_ts, dst_ts, direction); /* find a matching policy */ this->mutex->lock(this->mutex); if (this->policies->find_first(this->policies, - (linked_list_match_t)policy_entry_equals, (void**)&found, policy) == SUCCESS) - { - if (--found->refcount > 0) - { - /* is used by more SAs, keep in kernel */ - DBG2(DBG_KNL, "policy still used by another CHILD_SA, not removed"); - policy_entry_destroy(policy); - this->mutex->unlock(this->mutex); - return SUCCESS; - } - /* remove if last reference */ - this->policies->remove(this->policies, found, NULL); - policy_entry_destroy(policy); - policy = found; - } - else + (linked_list_match_t)policy_entry_equals, + (void**)&found, policy) != SUCCESS) { DBG1(DBG_KNL, "deleting policy %R === %R %N failed, not found", src_ts, dst_ts, policy_dir_names, direction); - policy_entry_destroy(policy); + policy_entry_destroy(policy, this); this->mutex->unlock(this->mutex); return NOT_FOUND; } - this->mutex->unlock(this->mutex); + policy_entry_destroy(policy, this); + policy = found; + + /* remove mapping to SA by reqid and priority */ + priority = get_priority(policy, prio); + enumerator = policy->used_by->create_enumerator(policy->used_by); + while (enumerator->enumerate(enumerator, (void**)&mapping)) + { + if (reqid == mapping->sa->cfg.reqid && priority == mapping->priority) + { + policy->used_by->remove_at(policy->used_by, enumerator); + break; + } + is_installed = FALSE; + } + enumerator->destroy(enumerator); + + if (policy->used_by->get_count(policy->used_by) > 0) + { /* policy is used by more SAs, keep in kernel */ + DBG2(DBG_KNL, "policy still used by another CHILD_SA, not removed"); + policy_sa_destroy(mapping, &direction, this); + + if (!is_installed) + { /* no need to update as the policy was not installed for this SA */ + this->mutex->unlock(this->mutex); + return SUCCESS; + } + + DBG2(DBG_KNL, "updating policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + policy->used_by->get_first(policy->used_by, (void**)&mapping); + if (add_policy_internal(this, policy, mapping, TRUE) != SUCCESS) + { + DBG1(DBG_KNL, "unable to update policy %R === %R %N", + src_ts, dst_ts, policy_dir_names, direction); + return FAILED; + } + return SUCCESS; + } memset(&request, 0, sizeof(request)); @@ -1964,7 +2340,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY; pol->sadb_x_policy_len = PFKEY_LEN(sizeof(struct sadb_x_policy)); pol->sadb_x_policy_dir = dir2kernel(direction); - pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC; + pol->sadb_x_policy_type = type2kernel(mapping->type); PFKEY_EXT_ADD(msg, pol); add_addr_ext(msg, policy->src.net, SADB_EXT_ADDRESS_SRC, policy->src.proto, @@ -1972,9 +2348,23 @@ METHOD(kernel_ipsec_t, del_policy, status_t, add_addr_ext(msg, policy->dst.net, SADB_EXT_ADDRESS_DST, policy->dst.proto, policy->dst.mask); - route = policy->route; - policy->route = NULL; - policy_entry_destroy(policy); + if (policy->route) + { + route_entry_t *route = policy->route; + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, route->gateway, + route->src_ip, route->if_name) != SUCCESS) + { + DBG1(DBG_KNL, "error uninstalling route installed with " + "policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + } + } + + this->policies->remove(this->policies, found, NULL); + policy_sa_destroy(mapping, &direction, this); + policy_entry_destroy(policy, this); + this->mutex->unlock(this->mutex); if (pfkey_send(this, msg, &out, &len) != SUCCESS) { @@ -1991,25 +2381,44 @@ METHOD(kernel_ipsec_t, del_policy, status_t, return FAILED; } free(out); + return SUCCESS; +} + +METHOD(kernel_ipsec_t, flush_policies, status_t, + private_kernel_pfkey_ipsec_t *this) +{ + unsigned char request[PFKEY_BUFFER_SIZE]; + struct sadb_msg *msg, *out; + size_t len; + + memset(&request, 0, sizeof(request)); + + DBG2(DBG_KNL, "flushing all policies from SPD"); - if (route) + msg = (struct sadb_msg*)request; + msg->sadb_msg_version = PF_KEY_V2; + msg->sadb_msg_type = SADB_X_SPDFLUSH; + msg->sadb_msg_satype = SADB_SATYPE_UNSPEC; + msg->sadb_msg_len = PFKEY_LEN(sizeof(struct sadb_msg)); + + if (pfkey_send(this, msg, &out, &len) != SUCCESS) { - if (hydra->kernel_interface->del_route(hydra->kernel_interface, - route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name) != SUCCESS) - { - DBG1(DBG_KNL, "error uninstalling route installed with " - "policy %R === %R %N", src_ts, dst_ts, - policy_dir_names, direction); - } - route_entry_destroy(route); + DBG1(DBG_KNL, "unable to flush SPD entries"); + return FAILED; } - + else if (out->sadb_msg_errno) + { + DBG1(DBG_KNL, "unable to flush SPD entries: %s (%d)", + strerror(out->sadb_msg_errno), out->sadb_msg_errno); + free(out); + return FAILED; + } + free(out); return SUCCESS; } /** - * Register a socket for AQUIRE/EXPIRE messages + * Register a socket for ACQUIRE/EXPIRE messages */ static status_t register_pfkey_socket(private_kernel_pfkey_ipsec_t *this, u_int8_t satype) @@ -2075,14 +2484,14 @@ METHOD(kernel_ipsec_t, bypass_socket, bool, if (setsockopt(fd, sol, ipsec_policy, &policy, sizeof(policy)) < 0) { DBG1(DBG_KNL, "unable to set IPSEC_POLICY on socket: %s", - strerror(errno)); + strerror(errno)); return FALSE; } policy.sadb_x_policy_dir = IPSEC_DIR_INBOUND; if (setsockopt(fd, sol, ipsec_policy, &policy, sizeof(policy)) < 0) { DBG1(DBG_KNL, "unable to set IPSEC_POLICY on socket: %s", - strerror(errno)); + strerror(errno)); return FALSE; } return TRUE; @@ -2103,7 +2512,11 @@ METHOD(kernel_ipsec_t, destroy, void, { close(this->socket_events); } - this->policies->destroy_function(this->policies, (void*)policy_entry_destroy); + this->policies->invoke_function(this->policies, + (linked_list_invoke_t)policy_entry_destroy, + this); + this->policies->destroy(this->policies); + this->sas->destroy(this->sas); this->mutex->destroy(this->mutex); this->mutex_pfkey->destroy(this->mutex_pfkey); free(this); @@ -2125,14 +2538,18 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() .update_sa = _update_sa, .query_sa = _query_sa, .del_sa = _del_sa, + .flush_sas = _flush_sas, .add_policy = _add_policy, .query_policy = _query_policy, .del_policy = _del_policy, + .flush_policies = _flush_policies, .bypass_socket = _bypass_socket, .destroy = _destroy, }, }, .policies = linked_list_create(), + .sas = hashtable_create((hashtable_hash_t)ipsec_sa_hash, + (hashtable_equals_t)ipsec_sa_equals, 32), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .mutex_pfkey = mutex_create(MUTEX_TYPE_DEFAULT), .install_routes = lib->settings->get_bool(lib->settings, @@ -2172,8 +2589,8 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() return NULL; } - this->job = callback_job_create((callback_job_cb_t)receive_events, - this, NULL, NULL); + this->job = callback_job_create_with_prio((callback_job_cb_t)receive_events, + this, NULL, NULL, JOB_PRIO_CRITICAL); lib->processor->queue_job(lib->processor, (job_t*)this->job); return &this->public; diff --git a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_plugin.c b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_plugin.c index 842511181..894175402 100644 --- a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_plugin.c +++ b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_plugin.c @@ -38,11 +38,20 @@ METHOD(plugin_t, get_name, char*, return "kernel-pfkey"; } +METHOD(plugin_t, get_features, int, + private_kernel_pfkey_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK(kernel_ipsec_register, kernel_pfkey_ipsec_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + }; + *features = f; + return countof(f); +} + METHOD(plugin_t, destroy, void, private_kernel_pfkey_plugin_t *this) { - hydra->kernel_interface->remove_ipsec_interface(hydra->kernel_interface, - (kernel_ipsec_constructor_t)kernel_pfkey_ipsec_create); free(this); } @@ -57,13 +66,11 @@ plugin_t *kernel_pfkey_plugin_create() .public = { .plugin = { .get_name = _get_name, - .reload = (void*)return_false, + .get_features = _get_features, .destroy = _destroy, }, }, ); - hydra->kernel_interface->add_ipsec_interface(hydra->kernel_interface, - (kernel_ipsec_constructor_t)kernel_pfkey_ipsec_create); return &this->public.plugin; } -- cgit v1.2.3