From 3cd7ba4960457c02f798abba1052eba1bde18b9c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jun 2013 16:05:40 +0200 Subject: [PATCH 01/26] kernel-netlink: Routes don't require a gateway/nexthop --- .../plugins/kernel_netlink/kernel_netlink_net.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c index d4cb4e1ac..020b36a0b 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c @@ -265,7 +265,7 @@ static route_entry_t *route_entry_clone(route_entry_t *this) INIT(route, .if_name = strdup(this->if_name), .src_ip = this->src_ip->clone(this->src_ip), - .gateway = this->gateway->clone(this->gateway), + .gateway = this->gateway ? this->gateway->clone(this->gateway) : NULL, .dst_net = chunk_clone(this->dst_net), .prefixlen = this->prefixlen, ); @@ -298,10 +298,14 @@ static u_int route_entry_hash(route_entry_t *this) */ 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->ip_equals(a->src_ip, b->src_ip) && - a->gateway->ip_equals(a->gateway, b->gateway) && - chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen; + if (a->if_name && b->if_name && streq(a->if_name, b->if_name) && + a->src_ip->ip_equals(a->src_ip, b->src_ip) && + chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen) + { + return (!a->gateway && !b->gateway) || (a->gateway && b->gateway && + a->gateway->ip_equals(a->gateway, b->gateway)); + } + return FALSE; } typedef struct net_change_t net_change_t; From 279e0d42bd1e16095ae773191f408c258be1bb9b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jun 2013 18:43:01 +0200 Subject: [PATCH 02/26] kernel-libipsec: Add plugin that implements kernel_ipsec_t using libipsec --- configure.in | 8 + src/libcharon/Makefile.am | 7 + .../plugins/kernel_libipsec/Makefile.am | 21 ++ .../kernel_libipsec/kernel_libipsec_ipsec.c | 189 ++++++++++++++++++ .../kernel_libipsec/kernel_libipsec_ipsec.h | 47 +++++ .../kernel_libipsec/kernel_libipsec_plugin.c | 84 ++++++++ .../kernel_libipsec/kernel_libipsec_plugin.h | 44 ++++ 7 files changed, 400 insertions(+) create mode 100644 src/libcharon/plugins/kernel_libipsec/Makefile.am create mode 100644 src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c create mode 100644 src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.h create mode 100644 src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c create mode 100644 src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.h diff --git a/configure.in b/configure.in index 6a7f9ffb5..a89edb879 100644 --- a/configure.in +++ b/configure.in @@ -192,6 +192,7 @@ ARG_DISBL_SET([kernel-netlink], [disable the netlink kernel interface.]) ARG_ENABL_SET([kernel-pfkey], [enable the PF_KEY kernel interface.]) ARG_ENABL_SET([kernel-pfroute], [enable the PF_ROUTE kernel interface.]) ARG_ENABL_SET([kernel-klips], [enable the KLIPS kernel interface.]) +ARG_ENABL_SET([kernel-libipsec],[enable the libipsec kernel interface.]) ARG_ENABL_SET([libipsec], [enable user space IPsec implementation.]) ARG_DISBL_SET([socket-default], [disable default socket implementation for charon.]) ARG_ENABL_SET([socket-dynamic], [enable dynamic socket implementation for charon]) @@ -306,6 +307,10 @@ if test x$xauth_generic_given = xfalse -a x$ikev1 = xfalse; then xauth_generic=false; fi +if test x$kernel_libipsec = xtrue; then + libipsec=true; +fi + if test x$eap_aka_3gpp2 = xtrue; then gmp=true; fi @@ -1015,6 +1020,7 @@ ADD_PLUGIN([gcm], [s charon scripts nm cmd]) ADD_PLUGIN([attr], [h charon]) ADD_PLUGIN([attr-sql], [h charon]) ADD_PLUGIN([load-tester], [c charon]) +ADD_PLUGIN([kernel-libipsec], [c charon cmd]) ADD_PLUGIN([kernel-pfkey], [h charon starter nm cmd]) ADD_PLUGIN([kernel-pfroute], [h charon starter nm cmd]) ADD_PLUGIN([kernel-klips], [h charon starter]) @@ -1163,6 +1169,7 @@ AM_CONDITIONAL(USE_DHCP, test x$dhcp = xtrue) AM_CONDITIONAL(USE_UNIT_TESTS, test x$unit_tester = xtrue) AM_CONDITIONAL(USE_LOAD_TESTER, test x$load_tester = xtrue) AM_CONDITIONAL(USE_HA, test x$ha = xtrue) +AM_CONDITIONAL(USE_KERNEL_LIBIPSEC, test x$kernel_libipsec = xtrue) AM_CONDITIONAL(USE_WHITELIST, test x$whitelist = xtrue) AM_CONDITIONAL(USE_LOOKIP, test x$lookip = xtrue) AM_CONDITIONAL(USE_ERROR_NOTIFY, test x$error_notify = xtrue) @@ -1418,6 +1425,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/unity/Makefile src/libcharon/plugins/uci/Makefile src/libcharon/plugins/ha/Makefile + src/libcharon/plugins/kernel_libipsec/Makefile src/libcharon/plugins/whitelist/Makefile src/libcharon/plugins/lookip/Makefile src/libcharon/plugins/error_notify/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index f0736c5ca..ae7867f35 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -485,6 +485,13 @@ if MONOLITHIC endif endif +if USE_KERNEL_LIBIPSEC + SUBDIRS += plugins/kernel_libipsec +if MONOLITHIC + libcharon_la_LIBADD += plugins/kernel_libipsec/libstrongswan-kernel-libipsec.la +endif +endif + if USE_WHITELIST SUBDIRS += plugins/whitelist if MONOLITHIC diff --git a/src/libcharon/plugins/kernel_libipsec/Makefile.am b/src/libcharon/plugins/kernel_libipsec/Makefile.am new file mode 100644 index 000000000..6337eb2f6 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/Makefile.am @@ -0,0 +1,21 @@ + +INCLUDES = -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/libcharon \ + -I$(top_srcdir)/src/libipsec + +AM_CFLAGS = -rdynamic + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-kernel-libipsec.la +else +plugin_LTLIBRARIES = libstrongswan-kernel-libipsec.la +endif + +libstrongswan_kernel_libipsec_la_SOURCES = \ + kernel_libipsec_plugin.h kernel_libipsec_plugin.c \ + kernel_libipsec_ipsec.h kernel_libipsec_ipsec.c + +libstrongswan_kernel_libipsec_la_LIBADD = $(top_builddir)/src/libipsec/libipsec.la + +libstrongswan_kernel_libipsec_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c new file mode 100644 index 000000000..fdcbace51 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "kernel_libipsec_ipsec.h" + +#include +#include +#include +#include + +typedef struct private_kernel_libipsec_ipsec_t private_kernel_libipsec_ipsec_t; + +struct private_kernel_libipsec_ipsec_t { + + /** + * Public libipsec_ipsec interface + */ + kernel_libipsec_ipsec_t public; + + /** + * Listener for lifetime expire events + */ + ipsec_event_listener_t ipsec_listener; +}; + +/** + * Expiration callback + */ +static void expire(u_int32_t reqid, u_int8_t protocol, u_int32_t spi, bool hard) +{ + hydra->kernel_interface->expire(hydra->kernel_interface, reqid, protocol, + spi, hard); +} + +METHOD(kernel_ipsec_t, get_spi, status_t, + private_kernel_libipsec_ipsec_t *this, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t reqid, u_int32_t *spi) +{ + return ipsec->sas->get_spi(ipsec->sas, src, dst, protocol, reqid, spi); +} + +METHOD(kernel_ipsec_t, get_cpi, status_t, + private_kernel_libipsec_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t reqid, u_int16_t *cpi) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, add_sa, status_t, + private_kernel_libipsec_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark, + u_int32_t tfc, lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key, + u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, u_int16_t ipcomp, + u_int16_t cpi, bool initiator, bool encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts) +{ + return ipsec->sas->add_sa(ipsec->sas, src, dst, spi, protocol, reqid, mark, + tfc, lifetime, enc_alg, enc_key, int_alg, int_key, + mode, ipcomp, cpi, initiator, encap, esn, inbound, + src_ts, dst_ts); +} + +METHOD(kernel_ipsec_t, update_sa, status_t, + private_kernel_libipsec_ipsec_t *this, u_int32_t spi, u_int8_t protocol, + u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst, + bool encap, bool new_encap, mark_t mark) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, query_sa, status_t, + private_kernel_libipsec_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, mark_t mark, u_int64_t *bytes, + u_int64_t *packets, u_int32_t *time) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, del_sa, status_t, + private_kernel_libipsec_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark) +{ + return ipsec->sas->del_sa(ipsec->sas, src, dst, spi, protocol, cpi, mark); +} + +METHOD(kernel_ipsec_t, flush_sas, status_t, + private_kernel_libipsec_ipsec_t *this) +{ + return ipsec->sas->flush_sas(ipsec->sas); +} + +METHOD(kernel_ipsec_t, add_policy, status_t, + private_kernel_libipsec_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) +{ + return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, + dst_ts, direction, type, sa, mark, priority); +} + +METHOD(kernel_ipsec_t, query_policy, status_t, + private_kernel_libipsec_ipsec_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark, + u_int32_t *use_time) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, del_policy, status_t, + private_kernel_libipsec_ipsec_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t priority) +{ + return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, + direction, reqid, mark, priority); +} + +METHOD(kernel_ipsec_t, flush_policies, status_t, + private_kernel_libipsec_ipsec_t *this) +{ + return ipsec->policies->flush_policies(ipsec->policies); +} + +METHOD(kernel_ipsec_t, bypass_socket, bool, + private_kernel_libipsec_ipsec_t *this, int fd, int family) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, enable_udp_decap, bool, + private_kernel_libipsec_ipsec_t *this, int fd, int family, u_int16_t port) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, destroy, void, + private_kernel_libipsec_ipsec_t *this) +{ + ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener); + free(this); +} + +/* + * Described in header. + */ +kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() +{ + private_kernel_libipsec_ipsec_t *this; + + INIT(this, + .public = { + .interface = { + .get_spi = _get_spi, + .get_cpi = _get_cpi, + .add_sa = _add_sa, + .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, + .enable_udp_decap = _enable_udp_decap, + .destroy = _destroy, + }, + }, + .ipsec_listener = { + .expire = expire, + }, + ); + + ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); + + return &this->public; +}; diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.h b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.h new file mode 100644 index 000000000..0a4936706 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup kernel_libipsec_ipsec kernel_libipsec_ipsec + * @{ @ingroup kernel_libipsec + */ + +#ifndef KERNEL_LIBIPSEC_IPSEC_H_ +#define KERNEL_LIBIPSEC_IPSEC_H_ + +#include +#include + +typedef struct kernel_libipsec_ipsec_t kernel_libipsec_ipsec_t; + +/** + * Implementation of the ipsec interface using libipsec + */ +struct kernel_libipsec_ipsec_t { + + /** + * Implements kernel_ipsec_t interface + */ + kernel_ipsec_t interface; +}; + +/** + * Create a libipsec ipsec interface instance. + * + * @return kernel_libipsec_ipsec_t instance + */ +kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create(); + +#endif /** KERNEL_LIBIPSEC_IPSEC_H_ @}*/ diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c new file mode 100644 index 000000000..a20ee7b4b --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "kernel_libipsec_plugin.h" +#include "kernel_libipsec_ipsec.h" + +#include +#include + +typedef struct private_kernel_libipsec_plugin_t private_kernel_libipsec_plugin_t; + +/** + * private data of "kernel" libipsec plugin + */ +struct private_kernel_libipsec_plugin_t { + + /** + * implements plugin interface + */ + kernel_libipsec_plugin_t public; +}; + +METHOD(plugin_t, get_name, char*, + private_kernel_libipsec_plugin_t *this) +{ + return "kernel-libipsec"; +} + +METHOD(plugin_t, get_features, int, + private_kernel_libipsec_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK(kernel_ipsec_register, kernel_libipsec_ipsec_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_kernel_libipsec_plugin_t *this) +{ + libipsec_deinit(); + free(this); +} + +/* + * see header file + */ +plugin_t *kernel_libipsec_plugin_create() +{ + private_kernel_libipsec_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + if (!libipsec_init()) + { + DBG1(DBG_LIB, "initialization of libipsec failed"); + destroy(this); + return NULL; + } + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.h b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.h new file mode 100644 index 000000000..a14426b4e --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup kernel_libipsec kernel_libipsec + * @ingroup cplugins + * + * @defgroup kernel_libipsec_plugin kernel_libipsec_plugin + * @{ @ingroup kernel_libipsec + */ + +#ifndef KERNEL_LIBIPSEC_PLUGIN_H_ +#define KERNEL_LIBIPSEC_PLUGIN_H_ + +#include +#include + +typedef struct kernel_libipsec_plugin_t kernel_libipsec_plugin_t; + +/** + * libipsec "kernel" interface plugin + */ +struct kernel_libipsec_plugin_t { + + /** + * implements plugin interface + */ + plugin_t plugin; + +}; + +#endif /** KERNEL_LIBIPSEC_PLUGIN_H_ @}*/ From 59be6ddd08f8b3eba53daf9f5fd69a2971bc4dee Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jun 2013 18:47:55 +0200 Subject: [PATCH 03/26] kernel-libipsec: Create a TUN device and use it to install virtual IPs --- .../kernel_libipsec/kernel_libipsec_ipsec.c | 7 ++++ .../kernel_libipsec/kernel_libipsec_plugin.c | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index fdcbace51..c7fc56b71 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -17,6 +17,7 @@ #include #include #include +#include #include typedef struct private_kernel_libipsec_ipsec_t private_kernel_libipsec_ipsec_t; @@ -32,6 +33,11 @@ struct private_kernel_libipsec_ipsec_t { * Listener for lifetime expire events */ ipsec_event_listener_t ipsec_listener; + + /** + * TUN device + */ + tun_device_t *tun; }; /** @@ -181,6 +187,7 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() .ipsec_listener = { .expire = expire, }, + .tun = lib->get(lib, "kernel-libipsec-tun"), ); ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c index a20ee7b4b..9fa811d02 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -17,8 +17,11 @@ #include "kernel_libipsec_ipsec.h" #include +#include #include +#define TUN_DEFAULT_MTU 1400 + typedef struct private_kernel_libipsec_plugin_t private_kernel_libipsec_plugin_t; /** @@ -30,6 +33,12 @@ struct private_kernel_libipsec_plugin_t { * implements plugin interface */ kernel_libipsec_plugin_t public; + + /** + * TUN device created by this plugin + */ + tun_device_t *tun; + }; METHOD(plugin_t, get_name, char*, @@ -52,6 +61,11 @@ METHOD(plugin_t, get_features, int, METHOD(plugin_t, destroy, void, private_kernel_libipsec_plugin_t *this) { + if (this->tun) + { + lib->set(lib, "kernel-libipsec-tun", NULL); + this->tun->destroy(this->tun); + } libipsec_deinit(); free(this); } @@ -80,5 +94,24 @@ plugin_t *kernel_libipsec_plugin_create() return NULL; } + this->tun = tun_device_create("ipsec%d"); + if (!this->tun) + { + DBG1(DBG_KNL, "failed to create TUN device"); + destroy(this); + return NULL; + } + if (!this->tun->set_mtu(this->tun, TUN_DEFAULT_MTU) || + !this->tun->up(this->tun)) + { + DBG1(DBG_KNL, "failed to configure TUN device"); + destroy(this); + return NULL; + } + lib->set(lib, "kernel-libipsec-tun", this->tun); + + /* set TUN device as default to install VIPs */ + lib->settings->set_str(lib->settings, "%s.install_virtual_ip_on", + this->tun->get_name(this->tun), charon->name); return &this->public.plugin; } From 44a49681fd17ae339e9b1cdd462eef5f92afa9d4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jun 2013 18:52:23 +0200 Subject: [PATCH 04/26] kernel-libipsec: Handle packets between charon socket, libipsec and TUN device --- .../kernel_libipsec/kernel_libipsec_plugin.c | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c index 9fa811d02..06bde52ae 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -16,8 +16,10 @@ #include "kernel_libipsec_plugin.h" #include "kernel_libipsec_ipsec.h" +#include #include #include +#include #include #define TUN_DEFAULT_MTU 1400 @@ -47,12 +49,95 @@ METHOD(plugin_t, get_name, char*, return "kernel-libipsec"; } +/** + * Outbound callback + */ +static void send_esp(void *data, esp_packet_t *packet) +{ + charon->sender->send_no_marker(charon->sender, (packet_t*)packet); +} + +/** + * Inbound callback + */ +static void deliver_plain(private_kernel_libipsec_plugin_t *this, + ip_packet_t *packet) +{ + this->tun->write_packet(this->tun, packet->get_encoding(packet)); + packet->destroy(packet); +} + +/** + * Receiver callback + */ +static void receiver_esp_cb(void *data, packet_t *packet) +{ + ipsec->processor->queue_inbound(ipsec->processor, + esp_packet_create_from_packet(packet)); +} + +/** + * Job handling outbound plaintext packets + */ +static job_requeue_t handle_plain(private_kernel_libipsec_plugin_t *this) +{ + chunk_t raw; + + if (this->tun->read_packet(this->tun, &raw)) + { + ip_packet_t *packet; + + packet = ip_packet_create(raw); + if (packet) + { + ipsec->processor->queue_outbound(ipsec->processor, packet); + } + else + { + DBG1(DBG_KNL, "invalid IP packet read from TUN device"); + } + } + return JOB_REQUEUE_DIRECT; +} + +/** + * Initialize/deinitialize sender and receiver + */ +static bool packet_handler_cb(private_kernel_libipsec_plugin_t *this, + plugin_feature_t *feature, bool reg, void *arg) +{ + if (reg) + { + ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL); + ipsec->processor->register_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain, this); + charon->receiver->add_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb, NULL); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, + NULL, (callback_job_cancel_t)return_false)); + } + else + { + charon->receiver->del_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb); + ipsec->processor->unregister_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp); + ipsec->processor->unregister_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain); + } + return TRUE; +} + METHOD(plugin_t, get_features, int, private_kernel_libipsec_plugin_t *this, plugin_feature_t *features[]) { static plugin_feature_t f[] = { PLUGIN_CALLBACK(kernel_ipsec_register, kernel_libipsec_ipsec_create), PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK((plugin_feature_callback_t)packet_handler_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "kernel-libipsec-handler"), + PLUGIN_DEPENDS(CUSTOM, "libcharon-receiver"), }; *features = f; return countof(f); From 587bdf87688f6176e02633da0d0fdc8924cd9352 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jun 2013 18:53:28 +0200 Subject: [PATCH 05/26] kernel-libipsec: Track policies and automatically install routes The routes direct traffic matching the remote traffic selector to the TUN device. If the remote traffic selector includes the IKE peer a very specific route is installed to allow IKE traffic. --- .../kernel_libipsec/kernel_libipsec_ipsec.c | 460 +++++++++++++++++- 1 file changed, 455 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index c7fc56b71..efa519a83 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -18,6 +18,7 @@ #include #include #include +#include #include typedef struct private_kernel_libipsec_ipsec_t private_kernel_libipsec_ipsec_t; @@ -38,8 +39,173 @@ struct private_kernel_libipsec_ipsec_t { * TUN device */ tun_device_t *tun; + + /** + * Mutex to lock access to various lists + */ + mutex_t *mutex; + + /** + * List of installed policies (policy_entry_t) + */ + linked_list_t *policies; + + /** + * List of exclude routes (exclude_route_t) + */ + linked_list_t *excludes; }; +typedef struct exclude_route_t exclude_route_t; + +/** + * Exclude route definition + */ +struct exclude_route_t { + /** Destination address to exclude */ + host_t *dst; + /** Source address for route */ + host_t *src; + /** Nexthop exclude has been installed */ + host_t *gtw; + /** References to this route */ + int refs; +}; + +/** + * Clean up an exclude route entry + */ +static void exclude_route_destroy(exclude_route_t *this) +{ + this->dst->destroy(this->dst); + this->src->destroy(this->src); + this->gtw->destroy(this->gtw); + free(this); +} + +/** + * Find an exclude route entry by destination address + */ +static bool exclude_route_match(exclude_route_t *current, + host_t *dst) +{ + return dst->ip_equals(dst, current->dst); +} + +typedef struct route_entry_t route_entry_t; + +/** + * Installed routing entry + */ +struct route_entry_t { + /** Name of the interface the route is bound to */ + char *if_name; + /** Source ip of the route */ + host_t *src_ip; + /** Destination net */ + chunk_t dst_net; + /** Destination net prefixlen */ + u_int8_t prefixlen; + /** Reference to exclude route, if any */ + exclude_route_t *exclude; +}; + +/** + * Destroy a route_entry_t object + */ +static void route_entry_destroy(route_entry_t *this) +{ + free(this->if_name); + DESTROY_IF(this->src_ip); + chunk_free(&this->dst_net); + 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->ip_equals(a->src_ip, b->src_ip) && + chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen; +} + +typedef struct policy_entry_t policy_entry_t; + +/** + * Installed policy + */ +struct policy_entry_t { + /** Direction of this policy: in, out, forward */ + u_int8_t direction; + /** Parameters of installed policy */ + struct { + /** Subnet and port */ + host_t *net; + /** Subnet mask */ + u_int8_t mask; + /** Protocol */ + u_int8_t proto; + } src, dst; + /** Associated route installed for this policy */ + route_entry_t *route; + /** References to this policy */ + int refs; +}; + +/** + * 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) +{ + policy_entry_t *this; + INIT(this, + .direction = dir, + ); + + src_ts->to_subnet(src_ts, &this->src.net, &this->src.mask); + dst_ts->to_subnet(dst_ts, &this->dst.net, &this->dst.mask); + + /* src or dest proto may be "any" (0), use more restrictive one */ + this->src.proto = max(src_ts->get_protocol(src_ts), + dst_ts->get_protocol(dst_ts)); + this->src.proto = this->src.proto ? this->src.proto : 0; + this->dst.proto = this->src.proto; + return this; +} + +/** + * Destroy a policy_entry_t object + */ +static void policy_entry_destroy(policy_entry_t *this) +{ + if (this->route) + { + route_entry_destroy(this->route); + } + DESTROY_IF(this->src.net); + DESTROY_IF(this->dst.net); + free(this); +} + +/** + * Compare two policy_entry_t objects + */ +static inline bool policy_entry_equals(policy_entry_t *a, + policy_entry_t *b) +{ + return a->direction == b->direction && + a->src.proto == b->src.proto && + a->dst.proto == b->dst.proto && + a->src.mask == b->src.mask && + a->dst.mask == b->dst.mask && + a->src.net->equals(a->src.net, b->src.net) && + a->dst.net->equals(a->dst.net, b->dst.net); +} + /** * Expiration callback */ @@ -106,14 +272,229 @@ METHOD(kernel_ipsec_t, flush_sas, status_t, return ipsec->sas->flush_sas(ipsec->sas); } +/** + * Add an explicit exclude route to a routing entry + */ +static void add_exclude_route(private_kernel_libipsec_ipsec_t *this, + route_entry_t *route, host_t *src, host_t *dst) +{ + exclude_route_t *exclude; + host_t *gtw; + + if (this->excludes->find_first(this->excludes, + (linked_list_match_t)exclude_route_match, + (void**)&exclude, dst) == SUCCESS) + { + route->exclude = exclude; + exclude->refs++; + } + + if (!route->exclude) + { + DBG2(DBG_KNL, "installing new exclude route for %H src %H", dst, src); + gtw = hydra->kernel_interface->get_nexthop(hydra->kernel_interface, + dst, NULL); + if (gtw) + { + char *if_name = NULL; + + if (hydra->kernel_interface->get_interface( + hydra->kernel_interface, src, &if_name) && + hydra->kernel_interface->add_route(hydra->kernel_interface, + dst->get_address(dst), + dst->get_family(dst) == AF_INET ? 32 : 128, + gtw, src, if_name) == SUCCESS) + { + INIT(exclude, + .dst = dst->clone(dst), + .src = src->clone(src), + .gtw = gtw->clone(gtw), + .refs = 1, + ); + route->exclude = exclude; + this->excludes->insert_last(this->excludes, exclude); + } + else + { + DBG1(DBG_KNL, "installing exclude route for %H failed", dst); + } + gtw->destroy(gtw); + free(if_name); + } + else + { + DBG1(DBG_KNL, "gateway lookup for %H failed", dst); + } + } +} + +/** + * Remove an exclude route attached to a routing entry + */ +static void remove_exclude_route(private_kernel_libipsec_ipsec_t *this, + route_entry_t *route) +{ + char *if_name = NULL; + host_t *dst; + + if (!route->exclude || --route->exclude->refs > 0) + { + return; + } + this->excludes->remove(this->excludes, route->exclude, NULL); + + dst = route->exclude->dst; + DBG2(DBG_KNL, "uninstalling exclude route for %H src %H", + dst, route->exclude->src); + if (hydra->kernel_interface->get_interface( + hydra->kernel_interface, + route->exclude->src, &if_name) && + hydra->kernel_interface->del_route(hydra->kernel_interface, + dst->get_address(dst), + dst->get_family(dst) == AF_INET ? 32 : 128, + route->exclude->gtw, route->exclude->src, + if_name) != SUCCESS) + { + DBG1(DBG_KNL, "uninstalling exclude route for %H failed", dst); + } + exclude_route_destroy(route->exclude); + route->exclude = NULL; + free(if_name); +} + +/** + * Install a route for the given policy + * + * this->mutex is released by this function + */ +static bool install_route(private_kernel_libipsec_ipsec_t *this, + host_t *src, host_t *dst, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_entry_t *policy) +{ + route_entry_t *route, *old; + host_t *src_ip; + bool is_virtual; + + if (policy->direction != POLICY_OUT) + { + this->mutex->unlock(this->mutex); + return TRUE; + } + + if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, + src_ts, &src_ip, &is_virtual) != SUCCESS) + { + this->mutex->unlock(this->mutex); + return FALSE; + } + + INIT(route, + .if_name = strdup(this->tun->get_name(this->tun)), + .src_ip = src_ip, + .dst_net = chunk_clone(policy->dst.net->get_address(policy->dst.net)), + .prefixlen = policy->dst.mask, + ); + + if (policy->route) + { + old = policy->route; + + if (route_entry_equals(old, route)) + { /* such a route already exists */ + route_entry_destroy(route); + this->mutex->unlock(this->mutex); + return TRUE; + } + /* uninstall previously installed route */ + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + old->dst_net, old->prefixlen, NULL, + old->src_ip, old->if_name) != SUCCESS) + { + DBG1(DBG_KNL, "error uninstalling route installed with policy " + "%R === %R %N", src_ts, dst_ts, policy_dir_names, + policy->direction); + } + route_entry_destroy(old); + policy->route = NULL; + } + + /* if remote traffic selector covers the IKE peer, add an exclude route */ + if (dst_ts->includes(dst_ts, dst)) + { + /* add exclude route for peer */ + add_exclude_route(this, route, src, dst); + } + + DBG2(DBG_KNL, "installing route: %R src %H dev %s", + dst_ts, route->src_ip, route->if_name); + + switch (hydra->kernel_interface->add_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, NULL, + route->src_ip, route->if_name)) + { + case ALREADY_DONE: + /* route exists, do not uninstall */ + remove_exclude_route(this, route); + route_entry_destroy(route); + this->mutex->unlock(this->mutex); + return TRUE; + case SUCCESS: + /* cache the installed route */ + policy->route = route; + this->mutex->unlock(this->mutex); + return TRUE; + default: + DBG1(DBG_KNL, "installing route failed: %R src %H dev %s", + dst_ts, route->src_ip, route->if_name); + remove_exclude_route(this, route); + route_entry_destroy(route); + this->mutex->unlock(this->mutex); + return FALSE; + } +} + METHOD(kernel_ipsec_t, add_policy, status_t, private_kernel_libipsec_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) { - return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, - dst_ts, direction, type, sa, mark, priority); + policy_entry_t *policy, *found = NULL; + status_t status; + + if (type != POLICY_IPSEC) + { + return SUCCESS; + } + + status = ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, + dst_ts, direction, type, sa, mark, priority); + if (status != SUCCESS) + { + return status; + } + /* we track policies in order to install routes */ + policy = create_policy_entry(src_ts, dst_ts, direction); + + this->mutex->lock(this->mutex); + if (this->policies->find_first(this->policies, + (linked_list_match_t)policy_entry_equals, + (void**)&found, policy) == SUCCESS) + { + policy_entry_destroy(policy); + policy = found; + } + else + { /* use the new one, if we have no such policy */ + this->policies->insert_last(this->policies, policy); + } + policy->refs++; + + if (!install_route(this, src, dst, src_ts, dst_ts, policy)) + { + return FAILED; + } + return SUCCESS; } METHOD(kernel_ipsec_t, query_policy, status_t, @@ -129,19 +510,82 @@ METHOD(kernel_ipsec_t, del_policy, status_t, traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, mark_t mark, policy_priority_t priority) { - return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, - direction, reqid, mark, priority); + policy_entry_t *policy, *found = NULL; + status_t status; + + status = ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, + direction, reqid, mark, priority); + + policy = create_policy_entry(src_ts, dst_ts, direction); + + this->mutex->lock(this->mutex); + if (this->policies->find_first(this->policies, + (linked_list_match_t)policy_entry_equals, + (void**)&found, policy) != SUCCESS) + { + policy_entry_destroy(policy); + this->mutex->unlock(this->mutex); + return status; + } + policy_entry_destroy(policy); + policy = found; + + if (--policy->refs > 0) + { /* policy is still in use */ + this->mutex->unlock(this->mutex); + return status; + } + + if (policy->route) + { + route_entry_t *route = policy->route; + + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, NULL, 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); + } + remove_exclude_route(this, route); + } + this->policies->remove(this->policies, policy, NULL); + policy_entry_destroy(policy); + this->mutex->unlock(this->mutex); + return status; } METHOD(kernel_ipsec_t, flush_policies, status_t, private_kernel_libipsec_ipsec_t *this) { - return ipsec->policies->flush_policies(ipsec->policies); + policy_entry_t *pol; + status_t status; + + status = ipsec->policies->flush_policies(ipsec->policies); + + this->mutex->lock(this->mutex); + while (this->policies->remove_first(this->policies, (void*)&pol) == SUCCESS) + { + if (pol->route) + { + route_entry_t *route = pol->route; + + hydra->kernel_interface->del_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, NULL, route->src_ip, + route->if_name); + remove_exclude_route(this, route); + } + policy_entry_destroy(pol); + } + this->mutex->unlock(this->mutex); + return status; } METHOD(kernel_ipsec_t, bypass_socket, bool, private_kernel_libipsec_ipsec_t *this, int fd, int family) { + /* we use exclude routes for this */ return NOT_SUPPORTED; } @@ -155,6 +599,9 @@ METHOD(kernel_ipsec_t, destroy, void, private_kernel_libipsec_ipsec_t *this) { ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener); + this->policies->destroy_function(this->policies, (void*)policy_entry_destroy); + this->excludes->destroy(this->excludes); + this->mutex->destroy(this->mutex); free(this); } @@ -188,6 +635,9 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() .expire = expire, }, .tun = lib->get(lib, "kernel-libipsec-tun"), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .policies = linked_list_create(), + .excludes = linked_list_create(), ); ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); From 0d2ad63fe2b9206124c150db799e7018bca0d14e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 12 Jun 2013 16:13:32 +0200 Subject: [PATCH 06/26] printf-hook: Avoid double-free when freeing Vstr config Thread-specific objects get freed when the thread value object is destroyed (wasn't the case earlier, i.e. before 2b19dd35), which may cause the second call to vstr_free_conf() to fail in an assert in Vstr (depending on how it was built). --- src/libstrongswan/utils/printf_hook.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libstrongswan/utils/printf_hook.c b/src/libstrongswan/utils/printf_hook.c index 6e51aa4c3..f030f45c8 100644 --- a/src/libstrongswan/utils/printf_hook.c +++ b/src/libstrongswan/utils/printf_hook.c @@ -474,7 +474,6 @@ METHOD(printf_hook_t, destroy, void, /* freeing the Vstr_conf of the main thread */ vstr_conf->destroy(vstr_conf); vstr_conf = NULL; - vstr_free_conf(conf); vstr_exit(); #endif free(this); From 4868d1c3bce7c126c2ce539e7b2b733365054fb6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 13 Jun 2013 17:03:17 +0200 Subject: [PATCH 07/26] kernel: Add an event kernel interfaces can raise if they create/destroy a TUN device --- src/libhydra/kernel/kernel_interface.c | 22 +++++++++++++++++++++- src/libhydra/kernel/kernel_interface.h | 10 +++++++++- src/libhydra/kernel/kernel_listener.h | 16 +++++++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/libhydra/kernel/kernel_interface.c b/src/libhydra/kernel/kernel_interface.c index be247df30..90637fa06 100644 --- a/src/libhydra/kernel/kernel_interface.c +++ b/src/libhydra/kernel/kernel_interface.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2012 Tobias Brunner + * Copyright (C) 2008-2013 Tobias Brunner * Hochschule fuer Technik Rapperswil * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG @@ -644,6 +644,25 @@ METHOD(kernel_interface_t, roam, void, this->mutex->unlock(this->mutex); } +METHOD(kernel_interface_t, tun, void, + private_kernel_interface_t *this, tun_device_t *tun, bool created) +{ + kernel_listener_t *listener; + enumerator_t *enumerator; + this->mutex->lock(this->mutex); + enumerator = this->listeners->create_enumerator(this->listeners); + while (enumerator->enumerate(enumerator, &listener)) + { + if (listener->tun && + !listener->tun(listener, tun, created)) + { + this->listeners->remove_at(this->listeners, enumerator); + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); +} + METHOD(kernel_interface_t, register_algorithm, void, private_kernel_interface_t *this, u_int16_t alg_id, transform_type_t type, u_int16_t kernel_id, char *kernel_name) @@ -764,6 +783,7 @@ kernel_interface_t *kernel_interface_create() .mapping = _mapping, .migrate = _migrate, .roam = _roam, + .tun = _tun, .destroy = _destroy, }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), diff --git a/src/libhydra/kernel/kernel_interface.h b/src/libhydra/kernel/kernel_interface.h index ab2b38256..2e8a4c082 100644 --- a/src/libhydra/kernel/kernel_interface.h +++ b/src/libhydra/kernel/kernel_interface.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2012 Tobias Brunner + * Copyright (C) 2006-2013 Tobias Brunner * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter @@ -561,6 +561,14 @@ struct kernel_interface_t { */ void (*roam)(kernel_interface_t *this, bool address); + /** + * Raise a tun event. + * + * @param tun TUN device + * @param created TRUE if created, FALSE if going to be destroyed + */ + void (*tun)(kernel_interface_t *this, tun_device_t *tun, bool created); + /** * Register a new algorithm with the kernel interface. * diff --git a/src/libhydra/kernel/kernel_listener.h b/src/libhydra/kernel/kernel_listener.h index 27ea947eb..4382a43fd 100644 --- a/src/libhydra/kernel/kernel_listener.h +++ b/src/libhydra/kernel/kernel_listener.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Tobias Brunner + * Copyright (C) 2010-2013 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -23,9 +23,10 @@ typedef struct kernel_listener_t kernel_listener_t; -#include -#include #include +#include +#include +#include /** * Interface for components interested in kernel events. @@ -91,6 +92,15 @@ struct kernel_listener_t { * @return TRUE to remain registered, FALSE to unregister */ bool (*roam)(kernel_listener_t *this, bool address); + + /** + * Hook called after a TUN device was created for a virtual IP address, or + * before such a device gets destroyed. + * + * @param tun TUN device + * @param created TRUE if created, FALSE if going to be destroyed + */ + bool (*tun)(kernel_listener_t *this, tun_device_t *tun, bool created); }; #endif /** KERNEL_LISTENER_H_ @}*/ From 554c4276a5afec3fb28562c757791258c5803c9e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 13 Jun 2013 17:51:16 +0200 Subject: [PATCH 08/26] kernel-pfroute: Raise tun event when creating/destroying TUN devices for virtual IPs --- src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 6c0b457c5..520f52a6c 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -878,7 +878,10 @@ METHOD(kernel_net_t, add_ip, status_t, } } ifaces->destroy(ifaces); - + /* lets do this while holding the lock, thus preventing another thread + * from deleting the TUN device concurrently, hopefully listeneres are quick + * and cause no deadlocks */ + hydra->kernel_interface->tun(hydra->kernel_interface, tun, TRUE); this->lock->unlock(this->lock); return SUCCESS; @@ -901,6 +904,8 @@ METHOD(kernel_net_t, del_ip, status_t, if (addr && addr->ip_equals(addr, vip)) { this->tuns->remove_at(this->tuns, enumerator); + hydra->kernel_interface->tun(hydra->kernel_interface, tun, + FALSE); tun->destroy(tun); found = TRUE; break; From 7045defbffb2917279b3b8222b700fed4d7af925 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 15 Jun 2013 14:21:34 +0200 Subject: [PATCH 09/26] kernel-libipsec: Use separate class to route packets between charon, libipsec and TUN device --- .../plugins/kernel_libipsec/Makefile.am | 3 +- .../kernel_libipsec/kernel_libipsec_plugin.c | 86 ++---------- .../kernel_libipsec/kernel_libipsec_router.c | 128 ++++++++++++++++++ .../kernel_libipsec/kernel_libipsec_router.h | 45 ++++++ 4 files changed, 188 insertions(+), 74 deletions(-) create mode 100644 src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c create mode 100644 src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h diff --git a/src/libcharon/plugins/kernel_libipsec/Makefile.am b/src/libcharon/plugins/kernel_libipsec/Makefile.am index 6337eb2f6..d57d1ae7a 100644 --- a/src/libcharon/plugins/kernel_libipsec/Makefile.am +++ b/src/libcharon/plugins/kernel_libipsec/Makefile.am @@ -14,7 +14,8 @@ endif libstrongswan_kernel_libipsec_la_SOURCES = \ kernel_libipsec_plugin.h kernel_libipsec_plugin.c \ - kernel_libipsec_ipsec.h kernel_libipsec_ipsec.c + kernel_libipsec_ipsec.h kernel_libipsec_ipsec.c \ + kernel_libipsec_router.h kernel_libipsec_router.c libstrongswan_kernel_libipsec_la_LIBADD = $(top_builddir)/src/libipsec/libipsec.la diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c index 06bde52ae..003ea29c0 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -15,12 +15,11 @@ #include "kernel_libipsec_plugin.h" #include "kernel_libipsec_ipsec.h" +#include "kernel_libipsec_router.h" #include #include #include -#include -#include #define TUN_DEFAULT_MTU 1400 @@ -41,6 +40,10 @@ struct private_kernel_libipsec_plugin_t { */ tun_device_t *tun; + /** + * Packet router + */ + kernel_libipsec_router_t *router; }; METHOD(plugin_t, get_name, char*, @@ -50,81 +53,18 @@ METHOD(plugin_t, get_name, char*, } /** - * Outbound callback + * Create the kernel_libipsec_router_t instance */ -static void send_esp(void *data, esp_packet_t *packet) -{ - charon->sender->send_no_marker(charon->sender, (packet_t*)packet); -} - -/** - * Inbound callback - */ -static void deliver_plain(private_kernel_libipsec_plugin_t *this, - ip_packet_t *packet) -{ - this->tun->write_packet(this->tun, packet->get_encoding(packet)); - packet->destroy(packet); -} - -/** - * Receiver callback - */ -static void receiver_esp_cb(void *data, packet_t *packet) -{ - ipsec->processor->queue_inbound(ipsec->processor, - esp_packet_create_from_packet(packet)); -} - -/** - * Job handling outbound plaintext packets - */ -static job_requeue_t handle_plain(private_kernel_libipsec_plugin_t *this) -{ - chunk_t raw; - - if (this->tun->read_packet(this->tun, &raw)) - { - ip_packet_t *packet; - - packet = ip_packet_create(raw); - if (packet) - { - ipsec->processor->queue_outbound(ipsec->processor, packet); - } - else - { - DBG1(DBG_KNL, "invalid IP packet read from TUN device"); - } - } - return JOB_REQUEUE_DIRECT; -} - -/** - * Initialize/deinitialize sender and receiver - */ -static bool packet_handler_cb(private_kernel_libipsec_plugin_t *this, - plugin_feature_t *feature, bool reg, void *arg) +static bool create_router(private_kernel_libipsec_plugin_t *this, + plugin_feature_t *feature, bool reg, void *arg) { if (reg) - { - ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL); - ipsec->processor->register_inbound(ipsec->processor, - (ipsec_inbound_cb_t)deliver_plain, this); - charon->receiver->add_esp_cb(charon->receiver, - (receiver_esp_cb_t)receiver_esp_cb, NULL); - lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, - NULL, (callback_job_cancel_t)return_false)); + { /* registers as packet handler etc. */ + this->router = kernel_libipsec_router_create(); } else { - charon->receiver->del_esp_cb(charon->receiver, - (receiver_esp_cb_t)receiver_esp_cb); - ipsec->processor->unregister_outbound(ipsec->processor, - (ipsec_outbound_cb_t)send_esp); - ipsec->processor->unregister_inbound(ipsec->processor, - (ipsec_inbound_cb_t)deliver_plain); + this->router->destroy(this->router); } return TRUE; } @@ -135,8 +75,8 @@ METHOD(plugin_t, get_features, int, static plugin_feature_t f[] = { PLUGIN_CALLBACK(kernel_ipsec_register, kernel_libipsec_ipsec_create), PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), - PLUGIN_CALLBACK((plugin_feature_callback_t)packet_handler_cb, NULL), - PLUGIN_PROVIDE(CUSTOM, "kernel-libipsec-handler"), + PLUGIN_CALLBACK((plugin_feature_callback_t)create_router, NULL), + PLUGIN_PROVIDE(CUSTOM, "kernel-libipsec-router"), PLUGIN_DEPENDS(CUSTOM, "libcharon-receiver"), }; *features = f; diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c new file mode 100644 index 000000000..9a829f965 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "kernel_libipsec_router.h" + +#include +#include +#include +#include + +typedef struct private_kernel_libipsec_router_t private_kernel_libipsec_router_t; + +/** + * Private data + */ +struct private_kernel_libipsec_router_t { + + /** + * Public interface + */ + kernel_libipsec_router_t public; + + /** + * TUN device + */ + tun_device_t *tun; +}; + +/** + * Outbound callback + */ +static void send_esp(void *data, esp_packet_t *packet) +{ + charon->sender->send_no_marker(charon->sender, (packet_t*)packet); +} + +/** + * Receiver callback + */ +static void receiver_esp_cb(void *data, packet_t *packet) +{ + ipsec->processor->queue_inbound(ipsec->processor, + esp_packet_create_from_packet(packet)); +} + +/** + * Inbound callback + */ +static void deliver_plain(private_kernel_libipsec_router_t *this, + ip_packet_t *packet) +{ + this->tun->write_packet(this->tun, packet->get_encoding(packet)); + packet->destroy(packet); +} + +/** + * Job handling outbound plaintext packets + */ +static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this) +{ + chunk_t raw; + + if (this->tun->read_packet(this->tun, &raw)) + { + ip_packet_t *packet; + + packet = ip_packet_create(raw); + if (packet) + { + ipsec->processor->queue_outbound(ipsec->processor, packet); + } + else + { + DBG1(DBG_KNL, "invalid IP packet read from TUN device"); + } + } + return JOB_REQUEUE_DIRECT; +} + +METHOD(kernel_libipsec_router_t, destroy, void, + private_kernel_libipsec_router_t *this) +{ + charon->receiver->del_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb); + ipsec->processor->unregister_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp); + ipsec->processor->unregister_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain); + free(this); +} + +/* + * See header file + */ +kernel_libipsec_router_t *kernel_libipsec_router_create(tun_device_t *tun) +{ + private_kernel_libipsec_router_t *this; + + INIT(this, + .public = { + .destroy = _destroy, + }, + .tun = lib->get(lib, "kernel-libipsec-tun"), + ); + + ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL); + ipsec->processor->register_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain, this); + charon->receiver->add_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb, NULL); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, + NULL, (callback_job_cancel_t)return_false)); + + return &this->public; +} diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h new file mode 100644 index 000000000..a7ef27c0a --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup kernel_libipsec_router kernel_libipsec_router + * @{ @ingroup kernel_libipsec + */ + +#ifndef KERNEL_LIBIPSEC_ROUTER_H_ +#define KERNEL_LIBIPSEC_ROUTER_H_ + +typedef struct kernel_libipsec_router_t kernel_libipsec_router_t; + +/** + * Class that routes the network packets between TUN device, libipsec and + * charon's IKE socket. + */ +struct kernel_libipsec_router_t { + + /** + * Destroy the given instance + */ + void (*destroy)(kernel_libipsec_router_t *this); +}; + +/** + * Create a kernel_libipsec_router_t instance. + * + * @return kernel_libipsec_router_t instance + */ +kernel_libipsec_router_t *kernel_libipsec_router_create(); + +#endif /** KERNEL_LIBIPSEC_ROUTER_H_ @}*/ From dcaf8d570c5c17cf1ec82d9d19ca53e1bfdff52b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 15 Jun 2013 16:21:08 +0200 Subject: [PATCH 10/26] kernel-libipsec: Router reads packets from multiple TUN devices These devices are collected via kernel_listener_t interface. --- .../kernel_libipsec/kernel_libipsec_ipsec.c | 9 +- .../kernel_libipsec/kernel_libipsec_plugin.c | 2 +- .../kernel_libipsec/kernel_libipsec_router.c | 253 +++++++++++++++++- .../kernel_libipsec/kernel_libipsec_router.h | 20 ++ 4 files changed, 268 insertions(+), 16 deletions(-) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index efa519a83..f605501ba 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -13,6 +13,7 @@ */ #include "kernel_libipsec_ipsec.h" +#include "kernel_libipsec_router.h" #include #include @@ -35,11 +36,6 @@ struct private_kernel_libipsec_ipsec_t { */ ipsec_event_listener_t ipsec_listener; - /** - * TUN device - */ - tun_device_t *tun; - /** * Mutex to lock access to various lists */ @@ -389,7 +385,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, } INIT(route, - .if_name = strdup(this->tun->get_name(this->tun)), + .if_name = router->get_tun_name(router, is_virtual ? src_ip : NULL), .src_ip = src_ip, .dst_net = chunk_clone(policy->dst.net->get_address(policy->dst.net)), .prefixlen = policy->dst.mask, @@ -634,7 +630,6 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() .ipsec_listener = { .expire = expire, }, - .tun = lib->get(lib, "kernel-libipsec-tun"), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .policies = linked_list_create(), .excludes = linked_list_create(), diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c index 003ea29c0..d5f3bc248 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -64,7 +64,7 @@ static bool create_router(private_kernel_libipsec_plugin_t *this, } else { - this->router->destroy(this->router); + DESTROY_IF(this->router); } return TRUE; } diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c index 9a829f965..6ce1d4eb0 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c @@ -13,15 +13,39 @@ * for more details. */ +#include +#include + #include "kernel_libipsec_router.h" #include +#include #include +#include #include +#include +#include #include typedef struct private_kernel_libipsec_router_t private_kernel_libipsec_router_t; +/** + * Entry in the TUN device map + */ +typedef struct { + /** virtual IP (points to internal data of tun) */ + host_t *addr; + /** underlying TUN file descriptor (cached from tun) */ + int fd; + /** TUN device */ + tun_device_t *tun; +} tun_entry_t; + +/** + * Single instance of the router + */ +kernel_libipsec_router_t *router; + /** * Private data */ @@ -33,11 +57,43 @@ struct private_kernel_libipsec_router_t { kernel_libipsec_router_t public; /** - * TUN device + * Default TUN device if kernel interface does not require separate TUN + * devices per VIP or for tunnels without VIP. */ - tun_device_t *tun; + tun_entry_t tun; + + /** + * Hashtable that maps virtual IPs to TUN devices (tun_entry_t). + */ + hashtable_t *tuns; + + /** + * Lock for TUN device map + */ + rwlock_t *lock; + + /** + * Pipe to signal handle_plain() about changes regarding TUN devices + */ + int notify[2]; }; +/** + * Hash function for TUN device map + */ +static u_int tun_entry_hash(tun_entry_t *entry) +{ + return chunk_hash(entry->addr->get_address(entry->addr)); +} + +/** + * Comparison function for TUN device map + */ +static bool tun_entry_equals(tun_entry_t *a, tun_entry_t *b) +{ + return a->addr->ip_equals(a->addr, b->addr); +} + /** * Outbound callback */ @@ -61,18 +117,56 @@ static void receiver_esp_cb(void *data, packet_t *packet) static void deliver_plain(private_kernel_libipsec_router_t *this, ip_packet_t *packet) { - this->tun->write_packet(this->tun, packet->get_encoding(packet)); + tun_device_t *tun; + tun_entry_t *entry, lookup = { + .addr = packet->get_destination(packet), + }; + + this->lock->read_lock(this->lock); + entry = this->tuns->get(this->tuns, &lookup); + tun = entry ? entry->tun : this->tun.tun; + tun->write_packet(tun, packet->get_encoding(packet)); + this->lock->unlock(this->lock); packet->destroy(packet); } /** - * Job handling outbound plaintext packets + * Create an FD set covering all TUN devices and the read end of the notify pipe */ -static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this) +static int collect_fds(private_kernel_libipsec_router_t *this, fd_set *fds) +{ + enumerator_t *enumerator; + tun_entry_t *entry; + int maxfd; + + FD_ZERO(fds); + FD_SET(this->notify[0], fds); + maxfd = this->notify[0]; + + FD_SET(this->tun.fd, fds); + maxfd = max(maxfd, this->tun.fd); + + this->lock->read_lock(this->lock); + enumerator = this->tuns->create_enumerator(this->tuns); + while (enumerator->enumerate(enumerator, NULL, &entry)) + { + FD_SET(entry->fd, fds); + maxfd = max(maxfd, entry->fd); + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + return maxfd + 1; +} + +/** + * Read and process outbound plaintext packet for the given TUN device + */ +static void process_plain(tun_device_t *tun) { chunk_t raw; - if (this->tun->read_packet(this->tun, &raw)) + if (tun->read_packet(tun, &raw)) { ip_packet_t *packet; @@ -86,9 +180,113 @@ static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this) DBG1(DBG_KNL, "invalid IP packet read from TUN device"); } } +} + +/** + * Handle waiting data for any TUN device + */ +static void handle_tuns(private_kernel_libipsec_router_t *this, fd_set *fds) +{ + enumerator_t *enumerator; + tun_entry_t *entry; + + if (FD_ISSET(this->tun.fd, fds)) + { + process_plain(this->tun.tun); + } + + this->lock->read_lock(this->lock); + enumerator = this->tuns->create_enumerator(this->tuns); + while (enumerator->enumerate(enumerator, NULL, &entry)) + { + if (FD_ISSET(entry->fd, fds)) + { + process_plain(entry->tun); + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); +} + +/** + * Job handling outbound plaintext packets + */ +static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this) +{ + bool oldstate; + fd_set fds; + int maxfd; + + maxfd = collect_fds(this, &fds); + + oldstate = thread_cancelability(TRUE); + if (select(maxfd, &fds, NULL, NULL, NULL) <= 0) + { + thread_cancelability(oldstate); + return JOB_REQUEUE_FAIR; + } + thread_cancelability(oldstate); + + if (FD_ISSET(this->notify[0], &fds)) + { /* list of TUN devices changed, read notification data, rebuild FDs */ + char buf[1]; + while (read(this->notify[0], &buf, sizeof(buf)) == sizeof(buf)); + return JOB_REQUEUE_DIRECT; + } + + handle_tuns(this, &fds); return JOB_REQUEUE_DIRECT; } +METHOD(kernel_listener_t, tun, bool, + private_kernel_libipsec_router_t *this, tun_device_t *tun, bool created) +{ + tun_entry_t *entry, lookup; + char buf[] = {0x01}; + + this->lock->write_lock(this->lock); + if (created) + { + INIT(entry, + .addr = tun->get_address(tun, NULL), + .fd = tun->get_fd(tun), + .tun = tun, + ); + this->tuns->put(this->tuns, entry, entry); + } + else + { + lookup.addr = tun->get_address(tun, NULL); + entry = this->tuns->remove(this->tuns, &lookup); + free(entry); + } + /* notify handler thread to recreate FD set */ + ignore_result(write(this->notify[1], buf, sizeof(buf))); + this->lock->unlock(this->lock); + return TRUE; +} + +METHOD(kernel_libipsec_router_t, get_tun_name, char*, + private_kernel_libipsec_router_t *this, host_t *vip) +{ + tun_entry_t *entry, lookup = { + .addr = vip, + }; + tun_device_t *tun; + char *name; + + if (!vip) + { + return strdup(this->tun.tun->get_name(this->tun.tun)); + } + this->lock->read_lock(this->lock); + entry = this->tuns->get(this->tuns, &lookup); + tun = entry ? entry->tun : this->tun.tun; + name = strdup(tun->get_name(tun)); + this->lock->unlock(this->lock); + return name; +} + METHOD(kernel_libipsec_router_t, destroy, void, private_kernel_libipsec_router_t *this) { @@ -98,23 +296,61 @@ METHOD(kernel_libipsec_router_t, destroy, void, (ipsec_outbound_cb_t)send_esp); ipsec->processor->unregister_inbound(ipsec->processor, (ipsec_inbound_cb_t)deliver_plain); + hydra->kernel_interface->remove_listener(hydra->kernel_interface, + &this->public.listener); + this->lock->destroy(this->lock); + this->tuns->destroy(this->tuns); + close(this->notify[0]); + close(this->notify[1]); + router = NULL; free(this); } +/** + * Set O_NONBLOCK on the given socket. + */ +static bool set_nonblock(int socket) +{ + int flags = fcntl(socket, F_GETFL); + return flags != -1 && fcntl(socket, F_SETFL, flags | O_NONBLOCK) != -1; +} + /* * See header file */ -kernel_libipsec_router_t *kernel_libipsec_router_create(tun_device_t *tun) +kernel_libipsec_router_t *kernel_libipsec_router_create() { private_kernel_libipsec_router_t *this; INIT(this, .public = { + .listener = { + .tun = _tun, + }, + .get_tun_name = _get_tun_name, .destroy = _destroy, }, - .tun = lib->get(lib, "kernel-libipsec-tun"), + .tun = { + .tun = lib->get(lib, "kernel-libipsec-tun"), + } ); + if (pipe(this->notify) != 0 || + !set_nonblock(this->notify[0]) || !set_nonblock(this->notify[1])) + { + DBG1(DBG_KNL, "creating notify pipe for kernel-libipsec router failed"); + free(this); + return NULL; + } + + this->tun.fd = this->tun.tun->get_fd(this->tun.tun); + + this->tuns = hashtable_create((hashtable_hash_t)tun_entry_hash, + (hashtable_equals_t)tun_entry_equals, 4); + this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); + + hydra->kernel_interface->add_listener(hydra->kernel_interface, + &this->public.listener); ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL); ipsec->processor->register_inbound(ipsec->processor, (ipsec_inbound_cb_t)deliver_plain, this); @@ -124,5 +360,6 @@ kernel_libipsec_router_t *kernel_libipsec_router_create(tun_device_t *tun) (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, NULL, (callback_job_cancel_t)return_false)); + router = &this->public; return &this->public; } diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h index a7ef27c0a..7b2f3c6c5 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h @@ -21,6 +21,8 @@ #ifndef KERNEL_LIBIPSEC_ROUTER_H_ #define KERNEL_LIBIPSEC_ROUTER_H_ +#include + typedef struct kernel_libipsec_router_t kernel_libipsec_router_t; /** @@ -29,12 +31,30 @@ typedef struct kernel_libipsec_router_t kernel_libipsec_router_t; */ struct kernel_libipsec_router_t { + /** + * Implements kernel_listener_t interface + */ + kernel_listener_t listener; + + /** + * Get the name of the TUN device to be used with the given virtual IP. + * + * @param vip virtual IP + * @return allocated name + */ + char *(*get_tun_name)(kernel_libipsec_router_t *this, host_t *vip); + /** * Destroy the given instance */ void (*destroy)(kernel_libipsec_router_t *this); }; +/** + * Single instance of this class, if created + */ +extern kernel_libipsec_router_t *router; + /** * Create a kernel_libipsec_router_t instance. * From c8a56512a67076d5e5eb08166532e7081feec437 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 15 Jun 2013 17:46:25 +0200 Subject: [PATCH 11/26] tun-device: Avoid opening /dev/tunX multiple times (e.g. on FreeBSD) --- src/libstrongswan/networking/tun_device.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libstrongswan/networking/tun_device.c b/src/libstrongswan/networking/tun_device.c index 2191198e2..00de2eddf 100644 --- a/src/libstrongswan/networking/tun_device.c +++ b/src/libstrongswan/networking/tun_device.c @@ -390,14 +390,18 @@ static bool init_tun(private_tun_device_t *this, const char *name_tmpl) /* this works on FreeBSD and might also work on Linux with older TUN * driver versions (no IFF_TUN) */ char devname[IFNAMSIZ]; - int i; + /* the same process is allowed to open a device again, but that's not what + * we want (unless we previously closed a device, which we don't know at + * this point). therefore, this counter is static so we don't accidentally + * open a device twice */ + static int i = -1; if (name_tmpl) { DBG1(DBG_LIB, "arbitrary naming of TUN devices is not supported"); } - for (i = 0; i < 256; i++) + for (; ++i < 256; ) { snprintf(devname, IFNAMSIZ, "/dev/tun%d", i); this->tunfd = open(devname, O_RDWR); From 93e4df3761bc496a1bae71705f13f3f5a29288ed Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 15 Jun 2013 18:56:11 +0200 Subject: [PATCH 12/26] kernel-pfroute: Activate TUN device before setting address On FreeBSD, for some reason, we don't learn the interface is up otherwise. Even though ifconfig lists it as up at the same time. --- src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 520f52a6c..d6fd33a44 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -837,7 +837,7 @@ METHOD(kernel_net_t, add_ip, status_t, { prefix = vip->get_address(vip).len * 8; } - if (!tun->set_address(tun, vip, prefix) || !tun->up(tun)) + if (!tun->up(tun) || !tun->set_address(tun, vip, prefix)) { tun->destroy(tun); return FAILED; From 1f31a2bc2eeb92bf47272a784e88d893e450a17c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sun, 16 Jun 2013 10:47:39 +0200 Subject: [PATCH 13/26] kernel-libipsec: Install a gateway for routes on platforms other than Linux This seems required e.g. on FreeBSD but doesn't work on Linux. --- .../kernel_libipsec/kernel_libipsec_ipsec.c | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index f605501ba..45b34062d 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -96,8 +96,10 @@ typedef struct route_entry_t route_entry_t; struct route_entry_t { /** 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 of the route */ + host_t *gateway; /** Destination net */ chunk_t dst_net; /** Destination net prefixlen */ @@ -113,6 +115,7 @@ static void route_entry_destroy(route_entry_t *this) { free(this->if_name); DESTROY_IF(this->src_ip); + DESTROY_IF(this->gateway); chunk_free(&this->dst_net); free(this); } @@ -122,9 +125,18 @@ static void route_entry_destroy(route_entry_t *this) */ 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->ip_equals(a->src_ip, b->src_ip) && - chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen; + if ((!a->src_ip && !b->src_ip) || (a->src_ip && b->src_ip && + a->src_ip->ip_equals(a->src_ip, b->src_ip))) + { + if ((!a->gateway && !b->gateway) || (a->gateway && b->gateway && + a->gateway->ip_equals(a->gateway, b->gateway))) + { + return a->if_name && b->if_name && streq(a->if_name, b->if_name) && + chunk_equals(a->dst_net, b->dst_net) && + a->prefixlen == b->prefixlen; + } + } + return FALSE; } typedef struct policy_entry_t policy_entry_t; @@ -390,6 +402,11 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, .dst_net = chunk_clone(policy->dst.net->get_address(policy->dst.net)), .prefixlen = policy->dst.mask, ); +#ifndef __linux__ + /* on Linux we cant't install a gateway */ + route->gateway = hydra->kernel_interface->get_nexthop( + hydra->kernel_interface, dst, src); +#endif if (policy->route) { @@ -403,7 +420,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, } /* uninstall previously installed route */ if (hydra->kernel_interface->del_route(hydra->kernel_interface, - old->dst_net, old->prefixlen, NULL, + old->dst_net, old->prefixlen, old->gateway, old->src_ip, old->if_name) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " @@ -425,7 +442,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, dst_ts, route->src_ip, route->if_name); switch (hydra->kernel_interface->add_route(hydra->kernel_interface, - route->dst_net, route->prefixlen, NULL, + route->dst_net, route->prefixlen, route->gateway, route->src_ip, route->if_name)) { case ALREADY_DONE: @@ -537,7 +554,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, route_entry_t *route = policy->route; if (hydra->kernel_interface->del_route(hydra->kernel_interface, - route->dst_net, route->prefixlen, NULL, route->src_ip, + route->dst_net, route->prefixlen, route->gateway, route->src_ip, route->if_name) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with " @@ -568,8 +585,8 @@ METHOD(kernel_ipsec_t, flush_policies, status_t, route_entry_t *route = pol->route; hydra->kernel_interface->del_route(hydra->kernel_interface, - route->dst_net, route->prefixlen, NULL, route->src_ip, - route->if_name); + route->dst_net, route->prefixlen, route->gateway, + route->src_ip, route->if_name); remove_exclude_route(this, route); } policy_entry_destroy(pol); From f58f8bf4097a2e30ebf4a70c762824577ea8d11f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sun, 16 Jun 2013 10:49:25 +0200 Subject: [PATCH 14/26] kernel-pfroute: Properly install routes with interface and gateway --- .../plugins/kernel_pfroute/kernel_pfroute_net.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index d6fd33a44..540bce5d1 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -510,7 +510,7 @@ static void process_link(private_kernel_pfroute_net_t *this, { enumerator_t *enumerator; iface_entry_t *iface; - bool roam = FALSE, found = FALSE;; + bool roam = FALSE, found = FALSE; this->lock->write_lock(this->lock); enumerator = this->ifaces->create_enumerator(this->ifaces); @@ -1051,13 +1051,14 @@ static status_t manage_route(private_kernel_pfroute_net_t *this, int op, dst->get_family(dst), prefixlen); } break; - case RTAX_GATEWAY: - /* interface name seems to replace gateway on OS X */ + case RTAX_IFP: if (if_name) { - add_rt_ifname(&msg.hdr, RTA_GATEWAY, if_name); + add_rt_ifname(&msg.hdr, RTA_IFP, if_name); } - else if (gateway) + break; + case RTAX_GATEWAY: + if (gateway) { add_rt_addr(&msg.hdr, RTA_GATEWAY, gateway); } From d6c17e96b26e74d6cdb2dfa9355f2acf830811f3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sun, 16 Jun 2013 11:40:16 +0200 Subject: [PATCH 15/26] kernel-pfroute: Implement get_source_addr() --- .../kernel_pfroute/kernel_pfroute_net.c | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 540bce5d1..e7e6da3d2 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -812,12 +812,6 @@ METHOD(kernel_net_t, get_interface_name, bool, return FALSE; } -METHOD(kernel_net_t, get_source_addr, host_t*, - private_kernel_pfroute_net_t *this, host_t *dest, host_t *src) -{ - return NULL; -} - METHOD(kernel_net_t, add_ip, status_t, private_kernel_pfroute_net_t *this, host_t *vip, int prefix, char *ifname) @@ -1092,8 +1086,12 @@ METHOD(kernel_net_t, del_route, status_t, return manage_route(this, RTM_DELETE, dst_net, prefixlen, gateway, if_name); } -METHOD(kernel_net_t, get_nexthop, host_t*, - private_kernel_pfroute_net_t *this, host_t *dest, host_t *src) +/** + * Do a route lookup for dest and return either the nexthop or the source + * address. + */ +static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, + host_t *dest, host_t *src) { struct { struct rt_msghdr hdr; @@ -1106,7 +1104,7 @@ METHOD(kernel_net_t, get_nexthop, host_t*, .rtm_seq = ++this->seq, }, }; - host_t *hop = NULL; + host_t *host = NULL; enumerator_t *enumerator; struct sockaddr *addr; int type; @@ -1150,9 +1148,14 @@ METHOD(kernel_net_t, get_nexthop, host_t*, sizeof(*this->reply)); while (enumerator->enumerate(enumerator, &type, &addr)) { - if (type == RTAX_GATEWAY) + if (nexthop && type == RTAX_GATEWAY) { - hop = host_create_from_sockaddr(addr); + host = host_create_from_sockaddr(addr); + break; + } + if (!nexthop && type == RTAX_IFA) + { + host = host_create_from_sockaddr(addr); break; } } @@ -1169,7 +1172,19 @@ METHOD(kernel_net_t, get_nexthop, host_t*, this->condvar->signal(this->condvar); this->mutex->unlock(this->mutex); - return hop; + return host; +} + +METHOD(kernel_net_t, get_source_addr, host_t*, + private_kernel_pfroute_net_t *this, host_t *dest, host_t *src) +{ + return get_route(this, FALSE, dest, src); +} + +METHOD(kernel_net_t, get_nexthop, host_t*, + private_kernel_pfroute_net_t *this, host_t *dest, host_t *src) +{ + return get_route(this, TRUE, dest, src); } /** From 34b0ad0653d911c9ba8d89e65377bbf68c63c781 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sun, 16 Jun 2013 18:59:13 +0200 Subject: [PATCH 16/26] kernel-pfroute: Use DST as nexthop for host routes These are created as cache/clone on Mac OS X. --- src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index e7e6da3d2..21bbf1ac2 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -1153,6 +1153,12 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, host = host_create_from_sockaddr(addr); break; } + if (nexthop && type == RTAX_DST && + this->reply->rtm_flags & RTF_HOST) + { /* probably a cloned direct route */ + host = host_create_from_sockaddr(addr); + break; + } if (!nexthop && type == RTAX_IFA) { host = host_create_from_sockaddr(addr); From 66aaabf34238aa3ddcbb328b370e2d5abae566b4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sun, 16 Jun 2013 20:05:08 +0200 Subject: [PATCH 17/26] tun-device: Packets sent over utun devices on Mac OS X have the protocol family prepended --- src/libstrongswan/networking/tun_device.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libstrongswan/networking/tun_device.c b/src/libstrongswan/networking/tun_device.c index 00de2eddf..af7e57140 100644 --- a/src/libstrongswan/networking/tun_device.c +++ b/src/libstrongswan/networking/tun_device.c @@ -225,6 +225,12 @@ METHOD(tun_device_t, write_packet, bool, { ssize_t s; +#ifdef __APPLE__ + /* UTUN's expect the packets to be prepended by a 32-bit protocol number + * instead of parsing the packet again, we assume IPv4 for now */ + u_int32_t proto = htonl(AF_INET); + packet = chunk_cata("cc", chunk_from_thing(proto), packet); +#endif s = write(this->tunfd, packet.ptr, packet.len); if (s < 0) { @@ -271,6 +277,11 @@ METHOD(tun_device_t, read_packet, bool, return FALSE; } packet->len = len; +#ifdef __APPLE__ + /* UTUN's prepend packets with a 32-bit protocol number */ + packet->len -= sizeof(u_int32_t); + memmove(packet->ptr, packet->ptr + sizeof(u_int32_t), packet->len); +#endif return TRUE; } From 35fe41f7d0952f2930b5042c416f039f9509540f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 17 Jun 2013 10:13:56 +0200 Subject: [PATCH 18/26] kernel-libipsec: Add a feature to request UDP encapsulation of ESP packets --- .../plugins/kernel_libipsec/kernel_libipsec_ipsec.c | 7 +++++++ src/libhydra/kernel/kernel_interface.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index 45b34062d..e304d955d 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -223,6 +223,12 @@ static void expire(u_int32_t reqid, u_int8_t protocol, u_int32_t spi, bool hard) spi, hard); } +METHOD(kernel_ipsec_t, get_features, kernel_feature_t, + private_kernel_libipsec_ipsec_t *this) +{ + return KERNEL_REQUIRE_UDP_ENCAPSULATION; +} + METHOD(kernel_ipsec_t, get_spi, status_t, private_kernel_libipsec_ipsec_t *this, host_t *src, host_t *dst, u_int8_t protocol, u_int32_t reqid, u_int32_t *spi) @@ -628,6 +634,7 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() INIT(this, .public = { .interface = { + .get_features = _get_features, .get_spi = _get_spi, .get_cpi = _get_cpi, .add_sa = _add_sa, diff --git a/src/libhydra/kernel/kernel_interface.h b/src/libhydra/kernel/kernel_interface.h index 2e8a4c082..f73578167 100644 --- a/src/libhydra/kernel/kernel_interface.h +++ b/src/libhydra/kernel/kernel_interface.h @@ -67,6 +67,8 @@ enum kernel_feature_t { KERNEL_ESP_V3_TFC = (1<<0), /** Networking requires an "exclude" route for IKE/ESP packets */ KERNEL_REQUIRE_EXCLUDE_ROUTE = (1<<1), + /** IPsec implementation requires UDP encapsulation of ESP packets */ + KERNEL_REQUIRE_UDP_ENCAPSULATION = (1<<2), }; /** From 01955eec71341987f73a43a3dcd4ead7a7ff9574 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 17 Jun 2013 10:23:04 +0200 Subject: [PATCH 19/26] ike: Force NAT-T/UDP encapsulation if kernel interface requires it --- src/libcharon/sa/ikev1/tasks/isakmp_natd.c | 18 ++++++++++++++++-- src/libcharon/sa/ikev2/tasks/ike_natd.c | 19 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_natd.c b/src/libcharon/sa/ikev1/tasks/isakmp_natd.c index 5a779ff62..fc6ac0771 100644 --- a/src/libcharon/sa/ikev1/tasks/isakmp_natd.c +++ b/src/libcharon/sa/ikev1/tasks/isakmp_natd.c @@ -96,6 +96,20 @@ struct private_isakmp_natd_t { bool dst_matched; }; +/** + * Check if UDP encapsulation has to be forced either by config or required + * by the kernel interface + */ +static bool force_encap(ike_cfg_t *ike_cfg) +{ + if (!ike_cfg->force_encap(ike_cfg)) + { + return hydra->kernel_interface->get_features(hydra->kernel_interface) & + KERNEL_REQUIRE_UDP_ENCAPSULATION; + } + return TRUE; +} + /** * Get NAT-D payload type (RFC 3947 or RFC 3947 drafts). */ @@ -183,7 +197,7 @@ static hash_payload_t *build_natd_payload(private_isakmp_natd_t *this, bool src, chunk_t hash; config = this->ike_sa->get_ike_cfg(this->ike_sa); - if (src && config->force_encap(config)) + if (src && force_encap(config)) { hash = generate_natd_hash_faked(this); } @@ -297,7 +311,7 @@ static void process_payloads(private_isakmp_natd_t *this, message_t *message) !this->src_matched); config = this->ike_sa->get_ike_cfg(this->ike_sa); if (this->dst_matched && this->src_matched && - config->force_encap(config)) + force_encap(config)) { this->ike_sa->set_condition(this->ike_sa, COND_NAT_FAKE, TRUE); } diff --git a/src/libcharon/sa/ikev2/tasks/ike_natd.c b/src/libcharon/sa/ikev2/tasks/ike_natd.c index 0a93db9ed..4fc968f25 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_natd.c +++ b/src/libcharon/sa/ikev2/tasks/ike_natd.c @@ -78,6 +78,19 @@ struct private_ike_natd_t { bool mapping_changed; }; +/** + * Check if UDP encapsulation has to be forced either by config or required + * by the kernel interface + */ +static bool force_encap(ike_cfg_t *ike_cfg) +{ + if (!ike_cfg->force_encap(ike_cfg)) + { + return hydra->kernel_interface->get_features(hydra->kernel_interface) & + KERNEL_REQUIRE_UDP_ENCAPSULATION; + } + return TRUE; +} /** * Build NAT detection hash for a host @@ -147,7 +160,7 @@ static notify_payload_t *build_natd_payload(private_ike_natd_t *this, ike_sa_id = this->ike_sa->get_id(this->ike_sa); config = this->ike_sa->get_ike_cfg(this->ike_sa); - if (config->force_encap(config) && type == NAT_DETECTION_SOURCE_IP) + if (force_encap(config) && type == NAT_DETECTION_SOURCE_IP) { hash = generate_natd_hash_faked(this); } @@ -256,7 +269,7 @@ static void process_payloads(private_ike_natd_t *this, message_t *message) !this->src_matched); config = this->ike_sa->get_ike_cfg(this->ike_sa); if (this->dst_matched && this->src_matched && - config->force_encap(config)) + force_encap(config)) { this->ike_sa->set_condition(this->ike_sa, COND_NAT_FAKE, TRUE); } @@ -316,7 +329,7 @@ METHOD(task_t, build_i, status_t, * 3. Include all possbile addresses */ host = message->get_source(message); - if (!host->is_anyaddr(host) || ike_cfg->force_encap(ike_cfg)) + if (!host->is_anyaddr(host) || force_encap(ike_cfg)) { /* 1. or if we force UDP encap, as it doesn't matter if it's %any */ notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); if (notify) From 1c697ff1c5520353bec35003bfb8ac426516800a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 17 Jun 2013 15:38:35 +0200 Subject: [PATCH 20/26] kernel-pfroute: Try to ensure we get a source address or interface name --- src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 21bbf1ac2..b38a906bd 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -1120,6 +1120,12 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, case RTAX_IFA: add_rt_addr(&msg.hdr, RTA_IFA, src); break; + case RTAX_IFP: + if (!nexthop) + { /* add an empty IFP to ensure we get a source address */ + add_rt_ifname(&msg.hdr, RTA_IFP, ""); + } + break; default: break; } From b0629f7d9b37d98308aefaec3da89b6c427b3f5c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 17 Jun 2013 15:56:44 +0200 Subject: [PATCH 21/26] kernel-pfroute: Improve route lookup depending on information we get back Kernels don't provide the same information for all routes. --- .../kernel_pfroute/kernel_pfroute_net.c | 112 +++++++++++++++--- 1 file changed, 98 insertions(+), 14 deletions(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index b38a906bd..acb21d371 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -1086,6 +1086,48 @@ METHOD(kernel_net_t, del_route, status_t, return manage_route(this, RTM_DELETE, dst_net, prefixlen, gateway, if_name); } +/** + * Get an address on the given interface, preferably src. + */ +static host_t *get_address_on(private_kernel_pfroute_net_t *this, char *ifname, + host_t *src) +{ + enumerator_t *ifaces, *addrs; + iface_entry_t *iface; + addr_entry_t *addr, *found = NULL; + host_t *host = NULL; + + this->lock->read_lock(this->lock); + ifaces = this->ifaces->create_enumerator(this->ifaces); + while (ifaces->enumerate(ifaces, &iface)) + { + if (streq(ifname, iface->ifname)) + { + addrs = iface->addrs->create_enumerator(iface->addrs); + while (addrs->enumerate(addrs, &addr)) + { + if (src->ip_equals(src, addr->ip)) + { + found = addr; + break; + } + else if (!found) + { /* use the first address as fallback if we don't find src */ + found = addr; + } + } + addrs->destroy(addrs); + if (found) + { + host = found->ip->clone(found->ip); + } + break; + } + } + ifaces->destroy(ifaces); + this->lock->unlock(this->lock); + return host; +} /** * Do a route lookup for dest and return either the nexthop or the source * address. @@ -1104,9 +1146,10 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, .rtm_seq = ++this->seq, }, }; - host_t *host = NULL; + host_t *host = NULL, *gtw = NULL, *dst = NULL, *ifa = NULL; enumerator_t *enumerator; struct sockaddr *addr; + char *ifname = NULL; int type; msg.hdr.rtm_msglen = sizeof(struct rt_msghdr); @@ -1154,21 +1197,25 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, sizeof(*this->reply)); while (enumerator->enumerate(enumerator, &type, &addr)) { - if (nexthop && type == RTAX_GATEWAY) - { - host = host_create_from_sockaddr(addr); - break; + if (type == RTAX_DST && this->reply->rtm_flags & RTF_HOST) + { /* probably a cloned/cached direct route */ + dst = host_create_from_sockaddr(addr); } - if (nexthop && type == RTAX_DST && - this->reply->rtm_flags & RTF_HOST) - { /* probably a cloned direct route */ - host = host_create_from_sockaddr(addr); - break; - } - if (!nexthop && type == RTAX_IFA) + if (type == RTAX_GATEWAY) { - host = host_create_from_sockaddr(addr); - break; + gtw = host_create_from_sockaddr(addr); + } + if (type == RTAX_IFA) + { + ifa = host_create_from_sockaddr(addr); + } + if (type == RTAX_IFP) + { + struct sockaddr_dl *sdl = (struct sockaddr_dl*)addr; + if (addr->sa_family == AF_LINK && sdl->sdl_nlen) + { + ifname = strndup(sdl->sdl_data, sdl->sdl_nlen); + } } } enumerator->destroy(enumerator); @@ -1184,6 +1231,43 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, this->condvar->signal(this->condvar); this->mutex->unlock(this->mutex); + DBG3(DBG_KNL, "route to %H: dst %H gw %H src %H if %s", dest, dst, gtw, + ifa, ifname); + if (nexthop) + { + if (gtw) + { + host = gtw->clone(gtw); + } + else if (dst) + { + host = dst->clone(dst); + } + } + else + { + if (ifa) + { + host = ifa->clone(ifa); + } + else if (ifname) + { + host = get_address_on(this, ifname, src); + } + else if (gtw && !gtw->ip_equals(gtw, dest)) + { + host = get_route(this, FALSE, gtw, src); + } + } + if (host) + { + DBG2(DBG_KNL, "using %H as %s to reach %H", host, + nexthop ? "nexthop" : "address", dest); + } + DESTROY_IF(gtw); + DESTROY_IF(dst); + DESTROY_IF(ifa); + free(ifname); return host; } From 23ea59a95cfa13686c78399029202167639fa307 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 17 Jun 2013 21:51:23 +0200 Subject: [PATCH 22/26] kernel-libipsec: Ignore failures when installing routes for multicast or broadcast policies --- .../kernel_libipsec/kernel_libipsec_ipsec.c | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index e304d955d..3740c7643 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -398,8 +398,30 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, src_ts, &src_ip, &is_virtual) != SUCCESS) { + traffic_selector_t *multicast, *broadcast = NULL; + bool ignore = FALSE; + this->mutex->unlock(this->mutex); - return FALSE; + switch (src_ts->get_type(src_ts)) + { + case TS_IPV4_ADDR_RANGE: + multicast = traffic_selector_create_from_cidr("224.0.0.0/4", + 0, 0, 0xffff); + broadcast = traffic_selector_create_from_cidr("255.255.255.255/32", + 0, 0, 0xffff); + break; + case TS_IPV6_ADDR_RANGE: + multicast = traffic_selector_create_from_cidr("ff00::/8", + 0, 0, 0xffff); + break; + default: + return FALSE; + } + ignore = src_ts->is_contained_in(src_ts, multicast); + ignore |= broadcast && src_ts->is_contained_in(src_ts, broadcast); + multicast->destroy(multicast); + DESTROY_IF(broadcast); + return ignore; } INIT(route, From aa33d2e6eb2f7508d0650132e481758d6372825e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Jun 2013 16:55:03 +0200 Subject: [PATCH 23/26] kernel-pfroute: struct sockaddr arguments are 4 byte aligned This was noticed on Mac OS X where, if the default route is returned, RTA_NETMASK has sa_len set to 0, but skipping zero bytes to read the next address makes no sense, of course. Using 0 for sa_len seems a bit strange, in particular, because struct sockaddr has by definition a minimum length of 16 bytes. But it seems FreeBSD actually does the same. --- .../plugins/kernel_pfroute/kernel_pfroute_net.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index acb21d371..0dfb2957a 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -40,6 +40,10 @@ #error Cannot compile this plugin on systems where 'struct sockaddr' has no sa_len member. #endif +/** properly align sockaddrs */ +#define SA_ALIGN 4 +#define SA_LEN(len) ((len) > 0 ? (((len)+SA_ALIGN-1) & ~(SA_ALIGN-1)) : SA_ALIGN) + /** delay before firing roam events (ms) */ #define ROAM_DELAY 100 @@ -344,8 +348,8 @@ METHOD(enumerator_t, rt_enumerate, bool, this->types &= ~type; *addr = this->addr; *xtype = i; - this->remaining -= this->addr->sa_len; - this->addr = (void*)this->addr + this->addr->sa_len; + this->remaining -= SA_LEN(this->addr->sa_len); + this->addr = (char*)this->addr + SA_LEN(this->addr->sa_len); return TRUE; } } @@ -941,7 +945,7 @@ static void add_rt_addr(struct rt_msghdr *hdr, int type, host_t *addr) len = *addr->get_sockaddr_len(addr); memcpy((char*)hdr + hdr->rtm_msglen, addr->get_sockaddr(addr), len); - hdr->rtm_msglen += len; + hdr->rtm_msglen += SA_LEN(len); hdr->rtm_addrs |= type; } } @@ -976,7 +980,7 @@ static void add_rt_ifname(struct rt_msghdr *hdr, int type, char *name) { memcpy(sdl.sdl_data, name, sdl.sdl_nlen); memcpy((char*)hdr + hdr->rtm_msglen, &sdl, sdl.sdl_len); - hdr->rtm_msglen += sdl.sdl_len; + hdr->rtm_msglen += SA_LEN(sdl.sdl_len); hdr->rtm_addrs |= type; } } From 4b3fea3d54f884cc9e72bef630ffa9eb00f84b07 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Jun 2013 17:50:51 +0200 Subject: [PATCH 24/26] kernel-pfroute: Alignment of sockaddrs is not always the same --- src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 0dfb2957a..f8eed5775 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -41,7 +41,13 @@ #endif /** properly align sockaddrs */ +#ifdef __APPLE__ +/* Apple always uses 4 bytes */ #define SA_ALIGN 4 +#else +/* while on other platforms like FreeBSD it depends on the architecture */ +#define SA_ALIGN sizeof(long) +#endif #define SA_LEN(len) ((len) > 0 ? (((len)+SA_ALIGN-1) & ~(SA_ALIGN-1)) : SA_ALIGN) /** delay before firing roam events (ms) */ @@ -349,7 +355,8 @@ METHOD(enumerator_t, rt_enumerate, bool, *addr = this->addr; *xtype = i; this->remaining -= SA_LEN(this->addr->sa_len); - this->addr = (char*)this->addr + SA_LEN(this->addr->sa_len); + this->addr = (struct sockaddr*)((char*)this->addr + + SA_LEN(this->addr->sa_len)); return TRUE; } } From 12488efa78bd42c41684236266d64631830012d0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Jun 2013 18:06:46 +0200 Subject: [PATCH 25/26] kernel-pfroute: Simplify route lookup after fixing sockaddr parsing --- .../kernel_pfroute/kernel_pfroute_net.c | 109 +++--------------- 1 file changed, 19 insertions(+), 90 deletions(-) diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index f8eed5775..c05ca56ec 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -1097,48 +1097,6 @@ METHOD(kernel_net_t, del_route, status_t, return manage_route(this, RTM_DELETE, dst_net, prefixlen, gateway, if_name); } -/** - * Get an address on the given interface, preferably src. - */ -static host_t *get_address_on(private_kernel_pfroute_net_t *this, char *ifname, - host_t *src) -{ - enumerator_t *ifaces, *addrs; - iface_entry_t *iface; - addr_entry_t *addr, *found = NULL; - host_t *host = NULL; - - this->lock->read_lock(this->lock); - ifaces = this->ifaces->create_enumerator(this->ifaces); - while (ifaces->enumerate(ifaces, &iface)) - { - if (streq(ifname, iface->ifname)) - { - addrs = iface->addrs->create_enumerator(iface->addrs); - while (addrs->enumerate(addrs, &addr)) - { - if (src->ip_equals(src, addr->ip)) - { - found = addr; - break; - } - else if (!found) - { /* use the first address as fallback if we don't find src */ - found = addr; - } - } - addrs->destroy(addrs); - if (found) - { - host = found->ip->clone(found->ip); - } - break; - } - } - ifaces->destroy(ifaces); - this->lock->unlock(this->lock); - return host; -} /** * Do a route lookup for dest and return either the nexthop or the source * address. @@ -1157,10 +1115,9 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, .rtm_seq = ++this->seq, }, }; - host_t *host = NULL, *gtw = NULL, *dst = NULL, *ifa = NULL; + host_t *host = NULL; enumerator_t *enumerator; struct sockaddr *addr; - char *ifname = NULL; int type; msg.hdr.rtm_msglen = sizeof(struct rt_msghdr); @@ -1208,24 +1165,28 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, sizeof(*this->reply)); while (enumerator->enumerate(enumerator, &type, &addr)) { - if (type == RTAX_DST && this->reply->rtm_flags & RTF_HOST) - { /* probably a cloned/cached direct route */ - dst = host_create_from_sockaddr(addr); - } - if (type == RTAX_GATEWAY) + if (nexthop) { - gtw = host_create_from_sockaddr(addr); + if (type == RTAX_DST && this->reply->rtm_flags & RTF_HOST) + { /* probably a cloned/cached direct route, only use that + * as fallback if no gateway is found */ + host = host ?: host_create_from_sockaddr(addr); + } + if (type == RTAX_GATEWAY) + { /* could actually be a MAC address */ + host_t *gtw = host_create_from_sockaddr(addr); + if (gtw) + { + DESTROY_IF(host); + host = gtw; + } + } } - if (type == RTAX_IFA) + else { - ifa = host_create_from_sockaddr(addr); - } - if (type == RTAX_IFP) - { - struct sockaddr_dl *sdl = (struct sockaddr_dl*)addr; - if (addr->sa_family == AF_LINK && sdl->sdl_nlen) + if (type == RTAX_IFA) { - ifname = strndup(sdl->sdl_data, sdl->sdl_nlen); + host = host_create_from_sockaddr(addr); } } } @@ -1242,43 +1203,11 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, this->condvar->signal(this->condvar); this->mutex->unlock(this->mutex); - DBG3(DBG_KNL, "route to %H: dst %H gw %H src %H if %s", dest, dst, gtw, - ifa, ifname); - if (nexthop) - { - if (gtw) - { - host = gtw->clone(gtw); - } - else if (dst) - { - host = dst->clone(dst); - } - } - else - { - if (ifa) - { - host = ifa->clone(ifa); - } - else if (ifname) - { - host = get_address_on(this, ifname, src); - } - else if (gtw && !gtw->ip_equals(gtw, dest)) - { - host = get_route(this, FALSE, gtw, src); - } - } if (host) { DBG2(DBG_KNL, "using %H as %s to reach %H", host, nexthop ? "nexthop" : "address", dest); } - DESTROY_IF(gtw); - DESTROY_IF(dst); - DESTROY_IF(ifa); - free(ifname); return host; } From 45dcf4df575ba44879a0858a333938788406900b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 15 May 2013 15:56:17 +0200 Subject: [PATCH 26/26] osx-attr: add plugin installing config attributes using SystemConfiguration Currently installs DNS servers only, by prepending IP addresses to the DNS configuration of the primary networking service. --- configure.in | 4 + src/libcharon/Makefile.am | 7 + src/libcharon/plugins/osx_attr/Makefile.am | 18 ++ .../plugins/osx_attr/osx_attr_handler.c | 246 ++++++++++++++++++ .../plugins/osx_attr/osx_attr_handler.h | 49 ++++ .../plugins/osx_attr/osx_attr_plugin.c | 102 ++++++++ .../plugins/osx_attr/osx_attr_plugin.h | 42 +++ 7 files changed, 468 insertions(+) create mode 100644 src/libcharon/plugins/osx_attr/Makefile.am create mode 100644 src/libcharon/plugins/osx_attr/osx_attr_handler.c create mode 100644 src/libcharon/plugins/osx_attr/osx_attr_handler.h create mode 100644 src/libcharon/plugins/osx_attr/osx_attr_plugin.c create mode 100644 src/libcharon/plugins/osx_attr/osx_attr_plugin.h diff --git a/configure.in b/configure.in index a89edb879..696a179f4 100644 --- a/configure.in +++ b/configure.in @@ -225,6 +225,7 @@ ARG_ENABL_SET([gcm], [enables the GCM AEAD wrapper crypto plugin.]) ARG_ENABL_SET([addrblock], [enables RFC 3779 address block constraint support.]) ARG_ENABL_SET([unity], [enables Cisco Unity extension plugin.]) ARG_ENABL_SET([uci], [enable OpenWRT UCI configuration plugin.]) +ARG_ENABL_SET([osx-attr], [enable OS X SystemConfiguration attribute handler.]) ARG_ENABL_SET([android-dns], [enable Android specific DNS handler.]) ARG_ENABL_SET([android-log], [enable Android specific logger plugin.]) ARG_ENABL_SET([maemo], [enable Maemo specific plugin.]) @@ -1066,6 +1067,7 @@ ADD_PLUGIN([tnccs-dynamic], [c charon]) ADD_PLUGIN([medsrv], [c charon]) ADD_PLUGIN([medcli], [c charon]) ADD_PLUGIN([dhcp], [c charon]) +ADD_PLUGIN([osx-attr], [c charon cmd]) ADD_PLUGIN([android-dns], [c charon]) ADD_PLUGIN([android-log], [c charon]) ADD_PLUGIN([ha], [c charon]) @@ -1158,6 +1160,7 @@ AM_CONDITIONAL(USE_STROKE, test x$stroke = xtrue) AM_CONDITIONAL(USE_MEDSRV, test x$medsrv = xtrue) AM_CONDITIONAL(USE_MEDCLI, test x$medcli = xtrue) AM_CONDITIONAL(USE_UCI, test x$uci = xtrue) +AM_CONDITIONAL(USE_OSX_ATTR, test x$osx_attr = xtrue) AM_CONDITIONAL(USE_ANDROID_DNS, test x$android_dns = xtrue) AM_CONDITIONAL(USE_ANDROID_LOG, test x$android_log = xtrue) AM_CONDITIONAL(USE_MAEMO, test x$maemo = xtrue) @@ -1435,6 +1438,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/duplicheck/Makefile src/libcharon/plugins/coupling/Makefile src/libcharon/plugins/radattr/Makefile + src/libcharon/plugins/osx_attr/Makefile src/libcharon/plugins/android_dns/Makefile src/libcharon/plugins/android_log/Makefile src/libcharon/plugins/maemo/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index ae7867f35..32167e2c4 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -457,6 +457,13 @@ if MONOLITHIC endif endif +if USE_OSX_ATTR + SUBDIRS += plugins/osx_attr +if MONOLITHIC + libcharon_la_LIBADD += plugins/osx_attr/libstrongswan-osx-attr.la +endif +endif + if USE_ANDROID_DNS SUBDIRS += plugins/android_dns if MONOLITHIC diff --git a/src/libcharon/plugins/osx_attr/Makefile.am b/src/libcharon/plugins/osx_attr/Makefile.am new file mode 100644 index 000000000..d1ceab41d --- /dev/null +++ b/src/libcharon/plugins/osx_attr/Makefile.am @@ -0,0 +1,18 @@ + +INCLUDES = -I$(top_srcdir)/src/libstrongswan -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/libcharon + +AM_CFLAGS = -rdynamic + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-osx-attr.la +else +plugin_LTLIBRARIES = libstrongswan-osx-attr.la +endif + +libstrongswan_osx_attr_la_SOURCES = \ + osx_attr_plugin.c osx_attr_plugin.h \ + osx_attr_handler.c osx_attr_handler.h + +libstrongswan_osx_attr_la_LDFLAGS = -module -avoid-version \ + -framework SystemConfiguration -framework CoreFoundation diff --git a/src/libcharon/plugins/osx_attr/osx_attr_handler.c b/src/libcharon/plugins/osx_attr/osx_attr_handler.c new file mode 100644 index 000000000..9a3b2701d --- /dev/null +++ b/src/libcharon/plugins/osx_attr/osx_attr_handler.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "osx_attr_handler.h" + +#include +#include + +#include + +typedef struct private_osx_attr_handler_t private_osx_attr_handler_t; + +/** + * Private data of an osx_attr_handler_t object. + */ +struct private_osx_attr_handler_t { + + /** + * Public interface + */ + osx_attr_handler_t public; +}; + +/** + * Create a path to the DNS configuration of the Primary IPv4 Service + */ +static CFStringRef create_dns_path(SCDynamicStoreRef store) +{ + CFStringRef service, path = NULL; + CFDictionaryRef dict; + + /* get primary service */ + dict = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); + if (dict) + { + service = CFDictionaryGetValue(dict, CFSTR("PrimaryService")); + if (service) + { + path = CFStringCreateWithFormat(NULL, NULL, + CFSTR("State:/Network/Service/%@/DNS"), service); + } + else + { + DBG1(DBG_CFG, "SystemConfiguration PrimaryService not known"); + } + CFRelease(dict); + } + else + { + DBG1(DBG_CFG, "getting global IPv4 SystemConfiguration failed"); + } + return path; +} + +/** + * Create a mutable dictionary from path, a new one if not found + */ +static CFMutableDictionaryRef get_dictionary(SCDynamicStoreRef store, + CFStringRef path) +{ + CFDictionaryRef dict; + CFMutableDictionaryRef mut = NULL; + + dict = SCDynamicStoreCopyValue(store, path); + if (dict) + { + if (CFGetTypeID(dict) == CFDictionaryGetTypeID()) + { + mut = CFDictionaryCreateMutableCopy(NULL, 0, dict); + } + CFRelease(dict); + } + if (!mut) + { + mut = CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + } + return mut; +} + +/** + * Create a mutable array from dictionary path, a new one if not found + */ +static CFMutableArrayRef get_array_from_dict(CFDictionaryRef dict, + CFStringRef name) +{ + CFArrayRef arr; + + arr = CFDictionaryGetValue(dict, name); + if (arr && CFGetTypeID(arr) == CFArrayGetTypeID()) + { + return CFArrayCreateMutableCopy(NULL, 0, arr); + } + return CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); +} + +/** + * Add/Remove a DNS server to the configuration + */ +static bool manage_dns(int family, chunk_t data, bool add) +{ + SCDynamicStoreRef store; + CFStringRef path, dns; + CFMutableArrayRef arr; + CFMutableDictionaryRef dict; + CFIndex i; + host_t *server; + char buf[64]; + bool success = FALSE; + + server = host_create_from_chunk(family, data, 0); + if (!server) + { + return FALSE; + } + snprintf(buf, sizeof(buf), "%H", server); + server->destroy(server); + + store = SCDynamicStoreCreate(NULL, CFSTR("osx-attr"), NULL, NULL); + path = create_dns_path(store); + if (path) + { + dict = get_dictionary(store, path); + arr = get_array_from_dict(dict, CFSTR("ServerAddresses")); + dns = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8); + if (add) + { + DBG1(DBG_CFG, "installing %s as DNS server", buf); + CFArrayInsertValueAtIndex(arr, 0, dns); + } + else + { + i = CFArrayGetFirstIndexOfValue(arr, + CFRangeMake(0, CFArrayGetCount(arr)), dns); + if (i >= 0) + { + DBG1(DBG_CFG, "removing %s from DNS servers (%d)", buf, i); + CFArrayRemoveValueAtIndex(arr, i); + } + } + CFRelease(dns); + CFDictionarySetValue(dict, CFSTR("ServerAddresses"), arr); + CFRelease(arr); + + success = SCDynamicStoreSetValue(store, path, dict); + CFRelease(dict); + CFRelease(path); + } + CFRelease(store); + + if (!success) + { + DBG1(DBG_CFG, "adding DNS server to SystemConfiguration failed"); + } + return success; +} + +METHOD(attribute_handler_t, handle, bool, + private_osx_attr_handler_t *this, identification_t *id, + configuration_attribute_type_t type, chunk_t data) +{ + switch (type) + { + case INTERNAL_IP4_DNS: + return manage_dns(AF_INET, data, TRUE); + default: + return FALSE; + } +} + +METHOD(attribute_handler_t, release, void, + private_osx_attr_handler_t *this, identification_t *server, + configuration_attribute_type_t type, chunk_t data) +{ + switch (type) + { + case INTERNAL_IP4_DNS: + manage_dns(AF_INET, data, FALSE); + break; + default: + break; + } +} + +METHOD(enumerator_t, enumerate_dns, bool, + enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) +{ + *type = INTERNAL_IP4_DNS; + *data = chunk_empty; + /* stop enumeration */ + this->enumerate = (void*)return_false; + return TRUE; +} + +METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t *, + private_osx_attr_handler_t *this, identification_t *id, + linked_list_t *vips) +{ + enumerator_t *enumerator; + + INIT(enumerator, + .enumerate = (void*)_enumerate_dns, + .destroy = (void*)free, + ); + return enumerator; +} + +METHOD(osx_attr_handler_t, destroy, void, + private_osx_attr_handler_t *this) +{ + free(this); +} + +/** + * See header + */ +osx_attr_handler_t *osx_attr_handler_create() +{ + private_osx_attr_handler_t *this; + + INIT(this, + .public = { + .handler = { + .handle = _handle, + .release = _release, + .create_attribute_enumerator = _create_attribute_enumerator, + }, + .destroy = _destroy, + }, + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/osx_attr/osx_attr_handler.h b/src/libcharon/plugins/osx_attr/osx_attr_handler.h new file mode 100644 index 000000000..c1f979bcd --- /dev/null +++ b/src/libcharon/plugins/osx_attr/osx_attr_handler.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup osx_attr_handler osx_attr_handler + * @{ @ingroup osx_attr + */ + +#ifndef OSX_ATTR_HANDLER_H_ +#define OSX_ATTR_HANDLER_H_ + +#include + +typedef struct osx_attr_handler_t osx_attr_handler_t; + +/** + * OS X specific attribute handler, using SystemConfiguration framework. + */ +struct osx_attr_handler_t { + + /** + * Implements attribute_handler_t. + */ + attribute_handler_t handler; + + /** + * Destroy a osx_attr_handler_t. + */ + void (*destroy)(osx_attr_handler_t *this); +}; + +/** + * Create an osx_attr_handler_t instance. + */ +osx_attr_handler_t *osx_attr_handler_create(); + +#endif /** OSX_ATTR_HANDLER_H_ @}*/ diff --git a/src/libcharon/plugins/osx_attr/osx_attr_plugin.c b/src/libcharon/plugins/osx_attr/osx_attr_plugin.c new file mode 100644 index 000000000..380483c23 --- /dev/null +++ b/src/libcharon/plugins/osx_attr/osx_attr_plugin.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "osx_attr_plugin.h" +#include "osx_attr_handler.h" + +#include +#include + +typedef struct private_osx_attr_plugin_t private_osx_attr_plugin_t; + +/** + * Private data of an osx_attr_plugin_t object. + */ +struct private_osx_attr_plugin_t { + + /** + * Public interface + */ + osx_attr_plugin_t public; + + /** + * Android specific DNS handler + */ + osx_attr_handler_t *handler; +}; + +METHOD(plugin_t, get_name, char*, + private_osx_attr_plugin_t *this) +{ + return "osx-attr"; +} + +/** + * Register handler + */ +static bool plugin_cb(private_osx_attr_plugin_t *this, + plugin_feature_t *feature, bool reg, void *cb_data) +{ + if (reg) + { + hydra->attributes->add_handler(hydra->attributes, + &this->handler->handler); + } + else + { + hydra->attributes->remove_handler(hydra->attributes, + &this->handler->handler); + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_osx_attr_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "osx-attr"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_osx_attr_plugin_t *this) +{ + this->handler->destroy(this->handler); + free(this); +} + +/** + * See header + */ +plugin_t *osx_attr_plugin_create() +{ + private_osx_attr_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + .handler = osx_attr_handler_create(), + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/osx_attr/osx_attr_plugin.h b/src/libcharon/plugins/osx_attr/osx_attr_plugin.h new file mode 100644 index 000000000..761379386 --- /dev/null +++ b/src/libcharon/plugins/osx_attr/osx_attr_plugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup osx_attr osx_attr + * @ingroup cplugins + * + * @defgroup osx_attr_plugin osx_attr_plugin + * @{ @ingroup osx_attr + */ + +#ifndef OSX_ATTR_PLUGIN_H_ +#define OSX_ATTR_PLUGIN_H_ + +#include + +typedef struct osx_attr_plugin_t osx_attr_plugin_t; + +/** + * Plugin providing an OS X specific configuration attribute handler. + */ +struct osx_attr_plugin_t { + + /** + * Implements plugin interface. + */ + plugin_t plugin; +}; + +#endif /** OSX_ATTR_PLUGIN_H_ @}*/