diff --git a/configure.in b/configure.in index 6a7f9ffb5..696a179f4 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]) @@ -224,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.]) @@ -306,6 +308,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 +1021,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]) @@ -1060,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]) @@ -1152,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) @@ -1163,6 +1172,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 +1428,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 @@ -1427,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 f0736c5ca..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 @@ -485,6 +492,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..d57d1ae7a --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/Makefile.am @@ -0,0 +1,22 @@ + +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 \ + kernel_libipsec_router.h kernel_libipsec_router.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..3740c7643 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -0,0 +1,687 @@ +/* + * 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 "kernel_libipsec_router.h" + +#include +#include +#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; + + /** + * 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; + /** Gateway of the route */ + host_t *gateway; + /** 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); + DESTROY_IF(this->gateway); + 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) +{ + 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; + +/** + * 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 + */ +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_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) +{ + 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); +} + +/** + * 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) + { + traffic_selector_t *multicast, *broadcast = NULL; + bool ignore = FALSE; + + this->mutex->unlock(this->mutex); + 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, + .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, + ); +#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) + { + 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, old->gateway, + 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, route->gateway, + 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) +{ + 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, + 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) +{ + 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, route->gateway, route->src_ip, + route->if_name) != SUCCESS) + { + DBG1(DBG_KNL, "error uninstalling route installed with " + "policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + } + 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) +{ + 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, route->gateway, + 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; +} + +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); + this->policies->destroy_function(this->policies, (void*)policy_entry_destroy); + this->excludes->destroy(this->excludes); + this->mutex->destroy(this->mutex); + free(this); +} + +/* + * Described in header. + */ +kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() +{ + private_kernel_libipsec_ipsec_t *this; + + INIT(this, + .public = { + .interface = { + .get_features = _get_features, + .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, + }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .policies = linked_list_create(), + .excludes = linked_list_create(), + ); + + 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..d5f3bc248 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -0,0 +1,142 @@ +/* + * 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 "kernel_libipsec_router.h" + +#include +#include +#include + +#define TUN_DEFAULT_MTU 1400 + +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; + + /** + * TUN device created by this plugin + */ + tun_device_t *tun; + + /** + * Packet router + */ + kernel_libipsec_router_t *router; +}; + +METHOD(plugin_t, get_name, char*, + private_kernel_libipsec_plugin_t *this) +{ + return "kernel-libipsec"; +} + +/** + * Create the kernel_libipsec_router_t instance + */ +static bool create_router(private_kernel_libipsec_plugin_t *this, + plugin_feature_t *feature, bool reg, void *arg) +{ + if (reg) + { /* registers as packet handler etc. */ + this->router = kernel_libipsec_router_create(); + } + else + { + DESTROY_IF(this->router); + } + 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)create_router, NULL), + PLUGIN_PROVIDE(CUSTOM, "kernel-libipsec-router"), + PLUGIN_DEPENDS(CUSTOM, "libcharon-receiver"), + }; + *features = f; + return countof(f); +} + +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); +} + +/* + * 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; + } + + 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; +} 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_ @}*/ 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..6ce1d4eb0 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c @@ -0,0 +1,365 @@ +/* + * 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 +#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 + */ +struct private_kernel_libipsec_router_t { + + /** + * Public interface + */ + kernel_libipsec_router_t public; + + /** + * Default TUN device if kernel interface does not require separate TUN + * devices per VIP or for tunnels without VIP. + */ + 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 + */ +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) +{ + 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); +} + +/** + * Create an FD set covering all TUN devices and the read end of the notify pipe + */ +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 (tun->read_packet(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"); + } + } +} + +/** + * 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) +{ + 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); + 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() +{ + private_kernel_libipsec_router_t *this; + + INIT(this, + .public = { + .listener = { + .tun = _tun, + }, + .get_tun_name = _get_tun_name, + .destroy = _destroy, + }, + .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); + 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)); + + 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 new file mode 100644 index 000000000..7b2f3c6c5 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h @@ -0,0 +1,65 @@ +/* + * 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_ + +#include + +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 { + + /** + * 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. + * + * @return kernel_libipsec_router_t instance + */ +kernel_libipsec_router_t *kernel_libipsec_router_create(); + +#endif /** KERNEL_LIBIPSEC_ROUTER_H_ @}*/ 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_ @}*/ 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) 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..f73578167 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 @@ -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), }; /** @@ -561,6 +563,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_ @}*/ 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; diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 6c0b457c5..c05ca56ec 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -40,6 +40,16 @@ #error Cannot compile this plugin on systems where 'struct sockaddr' has no sa_len member. #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) */ #define ROAM_DELAY 100 @@ -344,8 +354,9 @@ 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 = (struct sockaddr*)((char*)this->addr + + SA_LEN(this->addr->sa_len)); return TRUE; } } @@ -510,7 +521,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); @@ -812,12 +823,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) @@ -837,7 +842,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; @@ -878,7 +883,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 +909,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; @@ -942,7 +952,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; } } @@ -977,7 +987,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; } } @@ -1046,13 +1056,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); } @@ -1086,8 +1097,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; @@ -1100,7 +1115,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; @@ -1116,6 +1131,12 @@ METHOD(kernel_net_t, get_nexthop, host_t*, 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; } @@ -1144,10 +1165,29 @@ METHOD(kernel_net_t, get_nexthop, host_t*, sizeof(*this->reply)); while (enumerator->enumerate(enumerator, &type, &addr)) { - if (type == RTAX_GATEWAY) + if (nexthop) { - hop = host_create_from_sockaddr(addr); - break; + 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; + } + } + } + else + { + if (type == RTAX_IFA) + { + host = host_create_from_sockaddr(addr); + } } } enumerator->destroy(enumerator); @@ -1163,7 +1203,24 @@ METHOD(kernel_net_t, get_nexthop, host_t*, this->condvar->signal(this->condvar); this->mutex->unlock(this->mutex); - return hop; + if (host) + { + DBG2(DBG_KNL, "using %H as %s to reach %H", host, + nexthop ? "nexthop" : "address", dest); + } + 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); } /** diff --git a/src/libstrongswan/networking/tun_device.c b/src/libstrongswan/networking/tun_device.c index 2191198e2..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; } @@ -390,14 +401,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); 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);