diff options
Diffstat (limited to 'src')
33 files changed, 1218 insertions, 199 deletions
diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index f0ed02ff3..4655cd7fc 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -23,6 +23,7 @@ <uses-sdk android:minSdkVersion="14" /> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:icon="@drawable/ic_launcher" diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk index 326565f2b..40a7a06a3 100644 --- a/src/frontends/android/jni/Android.mk +++ b/src/frontends/android/jni/Android.mk @@ -16,11 +16,14 @@ openssl_PATH := $(LOCAL_PATH)/openssl/include # CFLAGS (partially from a configure run using droid-gcc) strongswan_CFLAGS := \ + -Wall \ + -Wextra \ -Wno-format \ -Wno-pointer-sign \ -Wno-pointer-arith \ -Wno-sign-compare \ -Wno-strict-aliasing \ + -Wno-unused-parameter \ -DHAVE___BOOL \ -DHAVE_STDBOOL_H \ -DHAVE_ALLOCA_H \ diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index f47e39656..b1fa4d1cb 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -11,6 +11,7 @@ backend/android_service.c backend/android_service.h \ charonservice.c charonservice.h \ kernel/android_ipsec.c kernel/android_ipsec.h \ kernel/android_net.c kernel/android_net.h \ +kernel/network_manager.c kernel/network_manager.h \ vpnservice_builder.c vpnservice_builder.h # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 2a115d2f9..b00567f60 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -59,11 +59,6 @@ struct private_android_service_t { char *type; /** - * local ipv4 address - */ - char *local_address; - - /** * gateway */ char *gateway; @@ -362,7 +357,6 @@ METHOD(listener_t, child_updown, bool, { /* disable the hooks registered to catch initiation failures */ this->public.listener.ike_updown = NULL; - this->public.listener.ike_state_change = NULL; if (!setup_tun_device(this, ike_sa, child_sa)) { DBG1(DBG_DMN, "failed to setup TUN device"); @@ -403,19 +397,6 @@ METHOD(listener_t, ike_updown, bool, return TRUE; } -METHOD(listener_t, ike_state_change, bool, - private_android_service_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) -{ - /* this call back is only registered during initiation */ - if (this->ike_sa == ike_sa && state == IKE_DESTROYING) - { - charonservice->update_status(charonservice, - CHARONSERVICE_UNREACHABLE_ERROR); - return FALSE; - } - return TRUE; -} - METHOD(listener_t, alert, bool, private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) @@ -432,6 +413,15 @@ METHOD(listener_t, alert, bool, charonservice->update_status(charonservice, CHARONSERVICE_PEER_AUTH_ERROR); break; + case ALERT_PEER_INIT_UNREACHABLE: + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* only handle this if we are not reestablishing the SA */ + charonservice->update_status(charonservice, + CHARONSERVICE_UNREACHABLE_ERROR); + } + this->lock->unlock(this->lock); + break; default: break; } @@ -455,9 +445,8 @@ METHOD(listener_t, ike_reestablish, bool, if (this->ike_sa == old) { this->ike_sa = new; - /* re-register hooks to detect initiation failures */ + /* re-register hook to detect initiation failures */ this->public.listener.ike_updown = _ike_updown; - this->public.listener.ike_state_change = _ike_state_change; /* the TUN device will be closed when the new CHILD_SA is established */ } return TRUE; @@ -480,13 +469,13 @@ static job_requeue_t initiate(private_android_service_t *this) } }; - ike_cfg = ike_cfg_create(TRUE, TRUE, this->local_address, FALSE, + ike_cfg = ike_cfg_create(TRUE, TRUE, "0.0.0.0", FALSE, charon->socket->get_port(charon->socket, FALSE), this->gateway, FALSE, IKEV2_UDP_PORT); ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); peer_cfg = peer_cfg_create("android", IKEV2, ike_cfg, CERT_SEND_IF_ASKED, - UNIQUE_REPLACE, 1, /* keyingtries */ + UNIQUE_REPLACE, 0, /* keyingtries */ 36000, 0, /* rekey 10h, reauth none */ 600, 600, /* jitter, over 10min */ TRUE, FALSE, /* mobike, aggressive */ @@ -538,10 +527,14 @@ static job_requeue_t initiate(private_android_service_t *this) peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE); child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL, - ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE, - 0, 0, NULL, NULL, 0); - child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); - ts = traffic_selector_create_dynamic(0, 0, 65535); + ACTION_NONE, ACTION_RESTART, ACTION_RESTART, + FALSE, 0, 0, NULL, NULL, 0); + /* create an ESP proposal with the algorithms currently supported by + * libipsec, no PFS for now */ + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128-aes192-aes256-sha1-sha256-sha384-sha512")); + ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0", + 0, "255.255.255.255", 65535); child_cfg->add_traffic_selector(child_cfg, TRUE, ts); ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0", 0, "255.255.255.255", 65535); @@ -588,7 +581,6 @@ METHOD(android_service_t, destroy, void, close_tun_device(this); this->lock->destroy(this->lock); free(this->type); - free(this->local_address); free(this->gateway); free(this->username); if (this->password) @@ -603,8 +595,8 @@ METHOD(android_service_t, destroy, void, * See header */ android_service_t *android_service_create(android_creds_t *creds, char *type, - char *local_address, char *gateway, - char *username, char *password) + char *gateway, char *username, + char *password) { private_android_service_t *this; @@ -614,14 +606,12 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, .ike_rekey = _ike_rekey, .ike_reestablish = _ike_reestablish, .ike_updown = _ike_updown, - .ike_state_change = _ike_state_change, .child_updown = _child_updown, .alert = _alert, }, .destroy = _destroy, }, .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), - .local_address = local_address, .username = username, .password = password, .gateway = gateway, diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h index 52c3dc5c8..1bfdcf994 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h @@ -53,13 +53,12 @@ struct android_service_t { * * @param creds Android specific credential set * @param type VPN type (see VpnType.java) - * @param local_address local ip address * @param gateway gateway address * @param username user name (local identity) * @param password password (if any) */ android_service_t *android_service_create(android_creds_t *creds, char *type, - char *local_address, char *gateway, - char *username, char *password); + char *gateway, char *username, + char *password); #endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index ef4d42edf..1fde9887b 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -38,7 +38,7 @@ #define ANDROID_DEBUG_LEVEL 1 #define ANDROID_RETRASNMIT_TRIES 3 -#define ANDROID_RETRANSMIT_TIMEOUT 3.0 +#define ANDROID_RETRANSMIT_TIMEOUT 2.0 #define ANDROID_RETRANSMIT_BASE 1.4 typedef struct private_charonservice_t private_charonservice_t; @@ -74,9 +74,19 @@ struct private_charonservice_t { vpnservice_builder_t *builder; /** + * NetworkManager instance (accessed via JNI) + */ + network_manager_t *network_manager; + + /** * CharonVpnService reference */ jobject vpn_service; + + /** + * Sockets that were bypassed and we keep track for + */ + linked_list_t *sockets; }; /** @@ -172,8 +182,10 @@ failed: return success; } -METHOD(charonservice_t, bypass_socket, bool, - private_charonservice_t *this, int fd, int family) +/** + * Bypass a single socket + */ +static bool bypass_single_socket(intptr_t fd, private_charonservice_t *this) { JNIEnv *env; jmethodID method_id; @@ -188,7 +200,7 @@ METHOD(charonservice_t, bypass_socket, bool, } if (!(*env)->CallBooleanMethod(env, this->vpn_service, method_id, fd)) { - DBG1(DBG_CFG, "VpnService.protect() failed"); + DBG2(DBG_KNL, "VpnService.protect() failed"); goto failed; } androidjni_detach_thread(); @@ -200,6 +212,19 @@ failed: return FALSE; } +METHOD(charonservice_t, bypass_socket, bool, + private_charonservice_t *this, int fd, int family) +{ + if (fd >= 0) + { + this->sockets->insert_last(this->sockets, (void*)(intptr_t)fd); + return bypass_single_socket((intptr_t)fd, this); + } + this->sockets->invoke_function(this->sockets, (void*)bypass_single_socket, + this); + return TRUE; +} + /** * Converts the given Java array of byte arrays (byte[][]) to a linked list * of chunk_t objects. @@ -330,22 +355,26 @@ METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*, return this->builder; } +METHOD(charonservice_t, get_network_manager, network_manager_t*, + private_charonservice_t *this) +{ + return this->network_manager; +} + /** * Initiate a new connection * - * @param local local ip address (gets owned) * @param gateway gateway address (gets owned) * @param username username (gets owned) * @param password password (gets owned) */ -static void initiate(char *type, char *local, char *gateway, - char *username, char *password) +static void initiate(char *type, char *gateway, char *username, char *password) { private_charonservice_t *this = (private_charonservice_t*)charonservice; this->creds->clear(this->creds); DESTROY_IF(this->service); - this->service = android_service_create(this->creds, type, local, gateway, + this->service = android_service_create(this->creds, type, gateway, username, password); } @@ -400,10 +429,13 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder) .get_user_certificate = _get_user_certificate, .get_user_key = _get_user_key, .get_vpnservice_builder = _get_vpnservice_builder, + .get_network_manager = _get_network_manager, }, .attr = android_attr_create(), .creds = android_creds_create(), .builder = vpnservice_builder_create(builder), + .network_manager = network_manager_create(service), + .sockets = linked_list_create(), .vpn_service = (*env)->NewGlobalRef(env, service), ); charonservice = &this->public; @@ -439,6 +471,8 @@ static void charonservice_deinit(JNIEnv *env) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + this->network_manager->destroy(this->network_manager); + this->sockets->destroy(this->sockets); this->builder->destroy(this->builder); this->creds->destroy(this->creds); this->attr->destroy(this->attr); @@ -555,16 +589,14 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void) * Initiate SA */ JNI_METHOD(CharonVpnService, initiate, void, - jstring jtype, jstring jlocal_address, jstring jgateway, jstring jusername, - jstring jpassword) + jstring jtype, jstring jgateway, jstring jusername, jstring jpassword) { - char *type, *local_address, *gateway, *username, *password; + char *type, *gateway, *username, *password; type = androidjni_convert_jstring(env, jtype); - local_address = androidjni_convert_jstring(env, jlocal_address); gateway = androidjni_convert_jstring(env, jgateway); username = androidjni_convert_jstring(env, jusername); password = androidjni_convert_jstring(env, jpassword); - initiate(type, local_address, gateway, username, password); + initiate(type, gateway, username, password); } diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 376f55014..4b5839f5e 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -32,6 +32,7 @@ #define CHARONSERVICE_H_ #include "vpnservice_builder.h" +#include "kernel/network_manager.h" #include <library.h> #include <utils/linked_list.h> @@ -69,7 +70,9 @@ struct charonservice_t { /** * Install a bypass policy for the given socket using the protect() Method - * of the Android VpnService interface + * of the Android VpnService interface. + * + * Use -1 as fd to re-bypass previously bypassed sockets. * * @param fd socket file descriptor * @param family socket protocol family @@ -111,6 +114,12 @@ struct charonservice_t { */ vpnservice_builder_t *(*get_vpnservice_builder)(charonservice_t *this); + /** + * Get the current network_manager_t object + * + * @return NetworkManager instance + */ + network_manager_t *(*get_network_manager)(charonservice_t *this); }; /** diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c index 08cc61610..85fe5d4c1 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c @@ -79,7 +79,8 @@ METHOD(kernel_ipsec_t, update_sa, status_t, 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; + return ipsec->sas->update_sa(ipsec->sas, spi, protocol, cpi, src, dst, + new_src, new_dst, encap, new_encap, mark); } METHOD(kernel_ipsec_t, query_sa, status_t, diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c index e29f95510..430c95bc8 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c @@ -14,6 +14,15 @@ #include "android_net.h" +#include "../charonservice.h" +#include <hydra.h> +#include <debug.h> +#include <processing/jobs/callback_job.h> +#include <threading/mutex.h> + +/** delay before firing roam events (ms) */ +#define ROAM_DELAY 100 + typedef struct private_kernel_android_net_t private_kernel_android_net_t; struct private_kernel_android_net_t { @@ -22,8 +31,66 @@ struct private_kernel_android_net_t { * Public kernel interface */ kernel_android_net_t public; + + /** + * Reference to NetworkManager object + */ + network_manager_t *network_manager; + + /** + * earliest time of the next roam event + */ + timeval_t next_roam; + + /** + * mutex to check and update roam event time + */ + mutex_t *mutex; }; +/** + * callback function that raises the delayed roam event + */ +static job_requeue_t roam_event() +{ + /* this will fail if no connection is up */ + charonservice->bypass_socket(charonservice, -1, 0); + hydra->kernel_interface->roam(hydra->kernel_interface, TRUE); + return JOB_REQUEUE_NONE; +} + +/** + * Listen for connectivity change events and queue a roam event + */ +static void connectivity_cb(private_kernel_android_net_t *this, + bool disconnected) +{ + timeval_t now; + job_t *job; + + time_monotonic(&now); + this->mutex->lock(this->mutex); + if (!timercmp(&now, &this->next_roam, >)) + { + this->mutex->unlock(this->mutex); + return; + } + timeval_add_ms(&now, ROAM_DELAY); + this->next_roam = now; + this->mutex->unlock(this->mutex); + + job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, NULL, + NULL, NULL); + lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY); +} + +METHOD(kernel_net_t, get_source_addr, host_t*, + private_kernel_android_net_t *this, host_t *dest, host_t *src) +{ + return this->network_manager->get_local_address(this->network_manager, + dest->get_family(dest) == AF_INET); +} + METHOD(kernel_net_t, add_ip, status_t, private_kernel_android_net_t *this, host_t *virtual_ip, host_t *iface_ip) { @@ -34,6 +101,9 @@ METHOD(kernel_net_t, add_ip, status_t, METHOD(kernel_net_t, destroy, void, private_kernel_android_net_t *this) { + this->network_manager->remove_connectivity_cb(this->network_manager, + (void*)connectivity_cb); + this->mutex->destroy(this->mutex); free(this); } @@ -47,7 +117,7 @@ kernel_android_net_t *kernel_android_net_create() INIT(this, .public = { .interface = { - .get_source_addr = (void*)return_null, + .get_source_addr = _get_source_addr, .get_nexthop = (void*)return_null, .get_interface = (void*)return_null, .create_address_enumerator = (void*)enumerator_create_empty, @@ -58,7 +128,12 @@ kernel_android_net_t *kernel_android_net_create() .destroy = _destroy, }, }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .network_manager = charonservice->get_network_manager(charonservice), ); + timerclear(&this->next_roam); + this->network_manager->add_connectivity_cb(this->network_manager, + (void*)connectivity_cb, this); return &this->public; }; diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c new file mode 100644 index 000000000..9c97fbb14 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2012 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 <http://www.fsf.org/copyleft/gpl.txt>. * + * 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 "network_manager.h" + +#include "../android_jni.h" +#include "../charonservice.h" +#include <debug.h> +#include <threading/mutex.h> + +typedef struct private_network_manager_t private_network_manager_t; + +struct private_network_manager_t { + + /** + * Public interface + */ + network_manager_t public; + + /** + * Reference to NetworkManager object + */ + jobject obj; + + /** + * Java class for NetworkManager + */ + jclass cls; + + /** + * Registered callback + */ + struct { + connectivity_cb_t cb; + void *data; + } connectivity_cb; + + /** + * Mutex to access callback + */ + mutex_t *mutex; +}; + +METHOD(network_manager_t, get_local_address, host_t*, + private_network_manager_t *this, bool ipv4) +{ + JNIEnv *env; + jmethodID method_id; + jstring jaddr; + char *addr; + host_t *host; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "getLocalAddress", + "(Z)Ljava/lang/String;"); + if (!method_id) + { + goto failed; + } + jaddr = (*env)->CallObjectMethod(env, this->obj, method_id, ipv4); + if (!jaddr) + { + goto failed; + } + addr = androidjni_convert_jstring(env, jaddr); + androidjni_detach_thread(); + host = host_create_from_string(addr, 0); + free(addr); + return host; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +JNI_METHOD(NetworkManager, networkChanged, void, + bool disconnected) +{ + private_network_manager_t *nm; + + nm = (private_network_manager_t*)charonservice->get_network_manager( + charonservice); + nm->mutex->lock(nm->mutex); + if (nm->connectivity_cb.cb) + { + nm->connectivity_cb.cb(nm->connectivity_cb.data, disconnected); + } + nm->mutex->unlock(nm->mutex); +} + +METHOD(network_manager_t, add_connectivity_cb, void, + private_network_manager_t *this, connectivity_cb_t cb, void *data) +{ + this->mutex->lock(this->mutex); + if (!this->connectivity_cb.cb) + { + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "Register", "()V"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + (*env)->CallVoidMethod(env, this->obj, method_id); + if (!androidjni_exception_occurred(env)) + { + this->connectivity_cb.cb = cb; + this->connectivity_cb.data = data; + } + androidjni_detach_thread(); + } + } + this->mutex->unlock(this->mutex); +} + +/** + * Unregister the NetworkManager via JNI. + * + * this->mutex has to be locked + */ +static void unregister_network_manager(private_network_manager_t *this) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "Unregister", "()V"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + (*env)->CallVoidMethod(env, this->obj, method_id); + androidjni_exception_occurred(env); + } + androidjni_detach_thread(); +} + +METHOD(network_manager_t, remove_connectivity_cb, void, + private_network_manager_t *this, connectivity_cb_t cb) +{ + this->mutex->lock(this->mutex); + if (this->connectivity_cb.cb == cb) + { + this->connectivity_cb.cb = NULL; + unregister_network_manager(this); + } + this->mutex->unlock(this->mutex); +} + +METHOD(network_manager_t, destroy, void, + private_network_manager_t *this) +{ + JNIEnv *env; + + this->mutex->lock(this->mutex); + if (this->connectivity_cb.cb) + { + this->connectivity_cb.cb = NULL; + unregister_network_manager(this); + } + this->mutex->unlock(this->mutex); + + androidjni_attach_thread(&env); + if (this->obj) + { + (*env)->DeleteGlobalRef(env, this->obj); + } + if (this->cls) + { + (*env)->DeleteGlobalRef(env, this->cls); + } + androidjni_detach_thread(); + this->mutex->destroy(this->mutex); + free(this); +} + +/* + * Described in header. + */ +network_manager_t *network_manager_create(jobject context) +{ + private_network_manager_t *this; + JNIEnv *env; + jmethodID method_id; + jobject obj; + jclass cls; + + INIT(this, + .public = { + .get_local_address = _get_local_address, + .add_connectivity_cb = _add_connectivity_cb, + .remove_connectivity_cb = _remove_connectivity_cb, + .destroy = _destroy, + }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + ); + + androidjni_attach_thread(&env); + cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/NetworkManager"); + if (!cls) + { + goto failed; + } + this->cls = (*env)->NewGlobalRef(env, cls); + method_id = (*env)->GetMethodID(env, cls, "<init>", + "(Landroid/content/Context;)V"); + if (!method_id) + { + goto failed; + } + obj = (*env)->NewObject(env, cls, method_id, context); + if (!obj) + { + goto failed; + } + this->obj = (*env)->NewGlobalRef(env, obj); + androidjni_detach_thread(); + return &this->public; + +failed: + DBG1(DBG_KNL, "failed to build NetworkManager object"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + destroy(this); + return NULL; +}; diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h new file mode 100644 index 000000000..634816405 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 network_manager network_manager + * @{ @ingroup kernel_android + */ + +#ifndef NETWORK_MANAGER_H_ +#define NETWORK_MANAGER_H_ + +#include <jni.h> + +#include <library.h> +#include <utils/host.h> + +typedef struct network_manager_t network_manager_t; + +/** + * Callback called if connectivity changes somehow. + * + * Implementation should be quick as the call is made by the Java apps main + * thread. + * + * @param data data supplied during registration + * @param disconnected TRUE if currently disconnected + */ +typedef void (*connectivity_cb_t)(void *data, bool disconnected); + +/** + * NetworkManager, used to listen for network changes and retrieve local IP + * addresses. + * + * Communicates with NetworkManager via JNI + */ +struct network_manager_t { + + /** + * Get a local address + * + * @param ipv4 TRUE to get an IPv4 address + * @return the address or NULL if none available + */ + host_t *(*get_local_address)(network_manager_t *this, bool ipv4); + + /** + * Register a callback that is called if connectivity changes + * + * @note Only the first registered callback is currently used + * + * @param cb callback to register + * @param data data provided to callback + */ + void (*add_connectivity_cb)(network_manager_t *this, connectivity_cb_t cb, + void *data); + + /** + * Unregister a previously registered callback for connectivity changes + * + * @param cb previously registered callback + */ + void (*remove_connectivity_cb)(network_manager_t *this, + connectivity_cb_t cb); + + /** + * Destroy a network_manager_t instance + */ + void (*destroy)(network_manager_t *this); +}; + +/** + * Create a network_manager_t instance + * + * @param context Context object + * @return network_manager_t instance + */ +network_manager_t *network_manager_create(jobject context); + +#endif /** NETWORK_MANAGER_H_ @}*/ diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index 924781948..02db8c494 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -18,14 +18,10 @@ package org.strongswan.android.logic; import java.io.File; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Enumeration; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; @@ -218,9 +214,7 @@ public class CharonVpnService extends VpnService implements Runnable initializeCharon(builder, mLogFile); Log.i(TAG, "charon started"); - String local_address = getLocalIPv4Address(); initiate(mCurrentProfile.getVpnType().getIdentifier(), - local_address != null ? local_address : "0.0.0.0", mCurrentProfile.getGateway(), mCurrentProfile.getUsername(), mCurrentProfile.getPassword()); } @@ -335,15 +329,7 @@ public class CharonVpnService extends VpnService implements Runnable switch (status) { case STATE_CHILD_SA_DOWN: - synchronized (mServiceLock) - { - /* if we are not actively disconnecting we assume the remote terminated - * the connection and call disconnect() to deinitialize charon properly */ - if (mService != null && !mIsDisconnecting) - { - mService.disconnect(); - } - } + /* we ignore this as we use closeaction=restart */ break; case STATE_CHILD_SA_UP: setState(State.CONNECTED); @@ -486,41 +472,7 @@ public class CharonVpnService extends VpnService implements Runnable /** * Initiate VPN, provided by libandroidbridge.so */ - public native void initiate(String type, String local_address, String gateway, - String username, String password); - - /** - * Helper function that retrieves a local IPv4 address. - * - * @return string representation of an IPv4 address, or null if none found - */ - private static String getLocalIPv4Address() - { - try - { - Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); - while (en.hasMoreElements()) - { - NetworkInterface intf = en.nextElement(); - - Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); - while (enumIpAddr.hasMoreElements()) - { - InetAddress inetAddress = enumIpAddr.nextElement(); - if (!inetAddress.isLoopbackAddress() && inetAddress.getAddress().length == 4) - { - return inetAddress.getHostAddress().toString(); - } - } - } - } - catch (SocketException ex) - { - ex.printStackTrace(); - return null; - } - return null; - } + public native void initiate(String type, String gateway, String username, String password); /** * Adapter for VpnService.Builder which is used to access it safely via JNI. @@ -547,7 +499,7 @@ public class CharonVpnService extends VpnService implements Runnable Context context = getApplicationContext(); Intent intent = new Intent(context, MainActivity.class); PendingIntent pending = PendingIntent.getActivity(context, 0, intent, - Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent.FLAG_UPDATE_CURRENT); builder.setConfigureIntent(pending); return builder; } diff --git a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java new file mode 100644 index 000000000..160865fb7 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +package org.strongswan.android.logic; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class NetworkManager extends BroadcastReceiver +{ + private final Context mContext; + private boolean mRegistered; + + public NetworkManager(Context context) + { + mContext = context; + } + + public void Register() + { + mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + public void Unregister() + { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) + { + ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + networkChanged(info == null || !info.isConnected()); + } + + /** + * Notify the native parts about a network change + * + * @param disconnected true if no connection is available at the moment + */ + public native void networkChanged(boolean disconnected); + + /** + * Function that retrieves a local address of the given family. + * + * @param ipv4 true to return an IPv4 address, false for IPv6 + * @return string representation of an IPv4 address, or null if none found + */ + public String getLocalAddress(boolean ipv4) + { + try + { + Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); + if (en == null) + { /* no interfaces at all */ + return null; + } + while (en.hasMoreElements()) + { + NetworkInterface intf = en.nextElement(); + if (intf.isLoopback() || !intf.isUp() || + intf.getName().startsWith("tun")) + { + continue; + } + Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); + while (enumIpAddr.hasMoreElements()) + { + InetAddress inetAddress = enumIpAddr.nextElement(); + if (inetAddress.isLoopbackAddress()) + { + continue; + } + if ((ipv4 && inetAddress instanceof Inet4Address) || + (!ipv4 && inetAddress instanceof Inet6Address)) + { + return inetAddress.getHostAddress(); + } + } + } + } + catch (SocketException ex) + { + ex.printStackTrace(); + return null; + } + return null; + } +} diff --git a/src/libcharon/bus/bus.h b/src/libcharon/bus/bus.h index aba8acdbd..4bde2434b 100644 --- a/src/libcharon/bus/bus.h +++ b/src/libcharon/bus/bus.h @@ -90,6 +90,8 @@ enum alert_t { ALERT_PEER_AUTH_FAILED, /** failed to resolve peer address, no arguments */ ALERT_PEER_ADDR_FAILED, + /** peer did not respond to initial message, current try (int, 0-based) */ + ALERT_PEER_INIT_UNREACHABLE, }; /** diff --git a/src/libcharon/daemon.c b/src/libcharon/daemon.c index 6e977efc4..9ae56a91d 100644 --- a/src/libcharon/daemon.c +++ b/src/libcharon/daemon.c @@ -84,7 +84,8 @@ static void destroy(private_daemon_t *this) { /* terminate all idle threads */ lib->processor->set_threads(lib->processor, 0); - + /* make sure nobody waits for a DNS query */ + lib->hosts->flush(lib->hosts); /* close all IKE_SAs */ if (this->public.ike_sa_manager) { diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 1d49acb52..d9118fc49 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -1690,6 +1690,8 @@ METHOD(ike_sa_t, retransmit, status_t, { /* retry IKE_SA_INIT/Main Mode if we have multiple keyingtries */ u_int32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg); + charon->bus->alert(charon->bus, ALERT_PEER_INIT_UNREACHABLE, + this->keyingtry); this->keyingtry++; if (tries == 0 || tries > this->keyingtry) { diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c index 3f63a8496..e5f070e13 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c @@ -543,12 +543,7 @@ static void queue_route_reinstall(private_kernel_netlink_net_t *this, time_monotonic(&now); if (timercmp(&now, &this->last_route_reinstall, >)) { - now.tv_usec += ROUTE_DELAY * 1000; - while (now.tv_usec > 1000000) - { - now.tv_sec++; - now.tv_usec -= 1000000; - } + timeval_add_ms(&now, ROUTE_DELAY); this->last_route_reinstall = now; job = (job_t*)callback_job_create((callback_job_cb_t)reinstall_routes, @@ -704,12 +699,7 @@ static void fire_roam_event(private_kernel_netlink_net_t *this, bool address) this->roam_lock->unlock(this->roam_lock); return; } - now.tv_usec += ROAM_DELAY * 1000; - while (now.tv_usec > 1000000) - { - now.tv_sec++; - now.tv_usec -= 1000000; - } + timeval_add_ms(&now, ROAM_DELAY); this->next_roam = now; this->roam_lock->unlock(this->roam_lock); diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 16a46bb56..47a8829f7 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -285,12 +285,7 @@ static void fire_roam_event(private_kernel_pfroute_net_t *this, bool address) time_monotonic(&now); if (timercmp(&now, &this->last_roam, >)) { - now.tv_usec += ROAM_DELAY * 1000; - while (now.tv_usec > 1000000) - { - now.tv_sec++; - now.tv_usec -= 1000000; - } + timeval_add_ms(&now, ROAM_DELAY); this->last_roam = now; job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, diff --git a/src/libipsec/ipsec_processor.c b/src/libipsec/ipsec_processor.c index a91d9e074..353f0e7e0 100644 --- a/src/libipsec/ipsec_processor.c +++ b/src/libipsec/ipsec_processor.c @@ -146,7 +146,9 @@ static job_requeue_t process_inbound(private_ipsec_processor_t *this) policy->destroy(policy); break; } - DBG1(DBG_ESP, "discarding inbound IP packet due to policy"); + DBG1(DBG_ESP, "discarding inbound IP packet %H == %H due to " + "policy", ip_packet->get_source(ip_packet), + ip_packet->get_destination(ip_packet)); /* no matching policy found, fall-through */ } case IPPROTO_NONE: diff --git a/src/libipsec/ipsec_sa.c b/src/libipsec/ipsec_sa.c index cccd16404..66852259c 100644 --- a/src/libipsec/ipsec_sa.c +++ b/src/libipsec/ipsec_sa.c @@ -95,6 +95,20 @@ METHOD(ipsec_sa_t, get_destination, host_t*, return this->dst; } +METHOD(ipsec_sa_t, set_source, void, + private_ipsec_sa_t *this, host_t *addr) +{ + this->src->destroy(this->src); + this->src = addr->clone(addr); +} + +METHOD(ipsec_sa_t, set_destination, void, + private_ipsec_sa_t *this, host_t *addr) +{ + this->dst->destroy(this->dst); + this->dst = addr->clone(addr); +} + METHOD(ipsec_sa_t, get_spi, u_int32_t, private_ipsec_sa_t *this) { @@ -202,6 +216,8 @@ ipsec_sa_t *ipsec_sa_create(u_int32_t spi, host_t *src, host_t *dst, .destroy = _destroy, .get_source = _get_source, .get_destination = _get_destination, + .set_source = _set_source, + .set_destination = _set_destination, .get_spi = _get_spi, .get_reqid = _get_reqid, .get_protocol = _get_protocol, diff --git a/src/libipsec/ipsec_sa.h b/src/libipsec/ipsec_sa.h index 5fd03b6e4..271e0129f 100644 --- a/src/libipsec/ipsec_sa.h +++ b/src/libipsec/ipsec_sa.h @@ -52,6 +52,20 @@ struct ipsec_sa_t { host_t *(*get_destination)(ipsec_sa_t *this); /** + * Set the source address for this SA + * + * @param addr source address of this SA (gets cloned) + */ + void (*set_source)(ipsec_sa_t *this, host_t *addr); + + /** + * Set the destination address for this SA + * + * @param addr destination address of this SA (gets cloned) + */ + void (*set_destination)(ipsec_sa_t *this, host_t *addr); + + /** * Get the SPI for this SA * * @return SPI of this SA diff --git a/src/libipsec/ipsec_sa_mgr.c b/src/libipsec/ipsec_sa_mgr.c index e42c77aa5..35ad6c171 100644 --- a/src/libipsec/ipsec_sa_mgr.c +++ b/src/libipsec/ipsec_sa_mgr.c @@ -237,29 +237,29 @@ static bool match_entry_by_sa_ptr(ipsec_sa_entry_t *item, ipsec_sa_t *sa) return item->sa == sa; } -static bool match_entry_by_spi_inbound(ipsec_sa_entry_t *item, u_int32_t spi, - bool inbound) +static bool match_entry_by_spi_inbound(ipsec_sa_entry_t *item, u_int32_t *spi, + bool *inbound) { - return item->sa->get_spi(item->sa) == spi && - item->sa->is_inbound(item->sa) == inbound; + return item->sa->get_spi(item->sa) == *spi && + item->sa->is_inbound(item->sa) == *inbound; } -static bool match_entry_by_spi_src_dst(ipsec_sa_entry_t *item, u_int32_t spi, +static bool match_entry_by_spi_src_dst(ipsec_sa_entry_t *item, u_int32_t *spi, host_t *src, host_t *dst) { - return item->sa->match_by_spi_src_dst(item->sa, spi, src, dst); + return item->sa->match_by_spi_src_dst(item->sa, *spi, src, dst); } static bool match_entry_by_reqid_inbound(ipsec_sa_entry_t *item, - u_int32_t reqid, bool inbound) + u_int32_t *reqid, bool *inbound) { - return item->sa->match_by_reqid(item->sa, reqid, inbound); + return item->sa->match_by_reqid(item->sa, *reqid, *inbound); } -static bool match_entry_by_spi_dst(ipsec_sa_entry_t *item, u_int32_t spi, +static bool match_entry_by_spi_dst(ipsec_sa_entry_t *item, u_int32_t *spi, host_t *dst) { - return item->sa->match_by_spi_dst(item->sa, spi, dst); + return item->sa->match_by_spi_dst(item->sa, *spi, dst); } /** @@ -381,7 +381,7 @@ static bool allocate_spi(private_ipsec_sa_mgr_t *this, u_int32_t spi) if (this->allocated_spis->get(this->allocated_spis, &spi) || this->sas->find_first(this->sas, (void*)match_entry_by_spi_inbound, - NULL, spi, TRUE) == SUCCESS) + NULL, &spi, TRUE) == SUCCESS) { return FALSE; } @@ -471,7 +471,7 @@ METHOD(ipsec_sa_mgr_t, add_sa, status_t, } if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst, - NULL, spi, src, dst) == SUCCESS) + NULL, &spi, src, dst) == SUCCESS) { this->mutex->unlock(this->mutex); DBG1(DBG_ESP, "failed to install SAD entry: already installed"); @@ -487,6 +487,44 @@ METHOD(ipsec_sa_mgr_t, add_sa, status_t, return SUCCESS; } +METHOD(ipsec_sa_mgr_t, update_sa, status_t, + private_ipsec_sa_mgr_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) +{ + ipsec_sa_entry_t *entry = NULL; + + DBG2(DBG_ESP, "updating SAD entry with SPI %.8x from %#H..%#H to %#H..%#H", + ntohl(spi), src, dst, new_src, new_dst); + + if (!new_encap) + { + DBG1(DBG_ESP, "failed to update SAD entry: can't deactivate UDP " + "encapsulation"); + return NOT_SUPPORTED; + } + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst, + (void**)&entry, &spi, src, dst) == SUCCESS && + wait_for_entry(this, entry)) + { + entry->sa->set_source(entry->sa, new_src); + entry->sa->set_destination(entry->sa, new_dst); + /* checkin the entry */ + entry->locked = FALSE; + entry->condvar->signal(entry->condvar); + } + this->mutex->unlock(this->mutex); + + if (!entry) + { + DBG1(DBG_ESP, "failed to update SAD entry: not found"); + return FAILED; + } + return SUCCESS; +} + METHOD(ipsec_sa_mgr_t, del_sa, status_t, private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark) @@ -498,7 +536,7 @@ METHOD(ipsec_sa_mgr_t, del_sa, status_t, enumerator = this->sas->create_enumerator(this->sas); while (enumerator->enumerate(enumerator, (void**)¤t)) { - if (match_entry_by_spi_src_dst(current, spi, src, dst)) + if (match_entry_by_spi_src_dst(current, &spi, src, dst)) { if (wait_remove_entry(this, current)) { @@ -529,7 +567,7 @@ METHOD(ipsec_sa_mgr_t, checkout_by_reqid, ipsec_sa_t*, this->mutex->lock(this->mutex); if (this->sas->find_first(this->sas, (void*)match_entry_by_reqid_inbound, - (void**)&entry, reqid, inbound) == SUCCESS && + (void**)&entry, &reqid, &inbound) == SUCCESS && wait_for_entry(this, entry)) { sa = entry->sa; @@ -546,7 +584,7 @@ METHOD(ipsec_sa_mgr_t, checkout_by_spi, ipsec_sa_t*, this->mutex->lock(this->mutex); if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_dst, - (void**)&entry, spi, dst) == SUCCESS && + (void**)&entry, &spi, dst) == SUCCESS && wait_for_entry(this, entry)) { sa = entry->sa; @@ -609,6 +647,7 @@ ipsec_sa_mgr_t *ipsec_sa_mgr_create() .public = { .get_spi = _get_spi, .add_sa = _add_sa, + .update_sa = _update_sa, .del_sa = _del_sa, .checkout_by_spi = _checkout_by_spi, .checkout_by_reqid = _checkout_by_reqid, diff --git a/src/libipsec/ipsec_sa_mgr.h b/src/libipsec/ipsec_sa_mgr.h index 303b36f0e..db30a86bb 100644 --- a/src/libipsec/ipsec_sa_mgr.h +++ b/src/libipsec/ipsec_sa_mgr.h @@ -86,6 +86,27 @@ struct ipsec_sa_mgr_t { traffic_selector_t *src_ts, traffic_selector_t *dst_ts); /** + * Update the hosts on an installed SA. + * + * @param spi SPI of the SA + * @param protocol protocol for this SA (ESP/AH) + * @param cpi CPI for IPComp, 0 if no IPComp is used + * @param src current source address + * @param dst current destination address + * @param new_src new source address + * @param new_dst new destination address + * @param encap current use of UDP encapsulation + * @param new_encap new use of UDP encapsulation + * @param mark optional mark for this SA + * @return SUCCESS if operation completed + */ + status_t (*update_sa)(ipsec_sa_mgr_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); + + /** * Delete a previously added SA * * @param spi SPI of the SA diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 4912576df..9c7ef1d0a 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -3,8 +3,8 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ -library.c chunk.c debug.c enum.c settings.c printf_hook.c asn1/asn1.c \ -asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \ +library.c chunk.c debug.c enum.c host_resolver.c settings.c printf_hook.c \ +asn1/asn1.c asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \ crypto/crypters/crypter.c crypto/hashers/hasher.c crypto/pkcs7.c crypto/pkcs9.c \ crypto/proposal/proposal_keywords.c crypto/proposal/proposal_keywords_static.c \ crypto/prfs/prf.c crypto/prfs/mac_prf.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 463d57d95..4017bfcc2 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -1,8 +1,8 @@ ipseclib_LTLIBRARIES = libstrongswan.la libstrongswan_la_SOURCES = \ -library.c chunk.c debug.c enum.c settings.c printf_hook.c asn1/asn1.c \ -asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \ +library.c chunk.c debug.c enum.c host_resolver.c settings.c printf_hook.c \ +asn1/asn1.c asn1/asn1_parser.c asn1/oid.c bio/bio_reader.c bio/bio_writer.c \ crypto/crypters/crypter.c crypto/hashers/hasher.c crypto/pkcs7.c crypto/pkcs9.c \ crypto/proposal/proposal_keywords.c crypto/proposal/proposal_keywords_static.c \ crypto/prfs/prf.c crypto/prfs/mac_prf.c \ @@ -31,7 +31,7 @@ utils/optionsfrom.c utils/capabilities.c utils/backtrace.c utils/tun_device.c if USE_DEV_HEADERS strongswan_includedir = ${dev_headers} nobase_strongswan_include_HEADERS = \ -library.h chunk.h debug.h enum.h settings.h printf_hook.h \ +library.h chunk.h debug.h enum.h host_resolver.h settings.h printf_hook.h \ asn1/asn1.h asn1/asn1_parser.h asn1/oid.h bio/bio_reader.h bio/bio_writer.h \ crypto/crypters/crypter.h crypto/hashers/hasher.h crypto/mac.h \ crypto/pkcs7.h crypto/pkcs9.h crypto/proposal/proposal_keywords.h \ diff --git a/src/libstrongswan/host_resolver.c b/src/libstrongswan/host_resolver.c new file mode 100644 index 000000000..55b07d318 --- /dev/null +++ b/src/libstrongswan/host_resolver.c @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2012 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include "host_resolver.h" + +#include <debug.h> +#include <library.h> +#include <threading/condvar.h> +#include <threading/mutex.h> +#include <threading/thread.h> +#include <utils/hashtable.h> +#include <utils/linked_list.h> + +/** + * Default minimum and maximum number of threads + */ +#define MIN_THREADS_DEFAULT 0 +#define MAX_THREADS_DEFAULT 3 + +/** + * Timeout in seconds to wait for new queries until a thread may be stopped + */ +#define NEW_QUERY_WAIT_TIMEOUT 30 + +typedef struct private_host_resolver_t private_host_resolver_t; + +/** + * Private data of host_resolver_t + */ +struct private_host_resolver_t { + + /** + * Public interface + */ + host_resolver_t public; + + /** + * Hashtable to check for queued queries, query_t* + */ + hashtable_t *queries; + + /** + * Queue for queries, query_t* + */ + linked_list_t *queue; + + /** + * Mutex to safely access private data + */ + mutex_t *mutex; + + /** + * Condvar to signal arrival of new queries + */ + condvar_t *new_query; + + /** + * Minimum number of resolver threads + */ + u_int min_threads; + + /** + * Maximum number of resolver threads + */ + u_int max_threads; + + /** + * Current number of threads + */ + u_int threads; + + /** + * Current number of busy threads + */ + u_int busy_threads; + + /** + * Pool of threads, thread_t* + */ + linked_list_t *pool; + + /** + * TRUE if no new queries are accepted + */ + bool disabled; + +}; + +typedef struct { + /** DNS name we are looking for */ + char *name; + /** address family we request */ + int family; + /** Condvar to signal completion of a query */ + condvar_t *done; + /** refcount */ + refcount_t refcount; + /** the result if successful */ + host_t *result; +} query_t; + +/** + * Destroy the given query_t object if refcount is zero + */ +static void query_destroy(query_t *this) +{ + if (ref_put(&this->refcount)) + { + DESTROY_IF(this->result); + this->done->destroy(this->done); + free(this->name); + free(this); + } +} + +/** + * Signals all waiting threads and destroys the query + */ +static void query_signal_and_destroy(query_t *this) +{ + this->done->broadcast(this->done); + query_destroy(this); +} + +/** + * Hash a queued query + */ +static u_int query_hash(query_t *this) +{ + return chunk_hash_inc(chunk_create(this->name, strlen(this->name)), + chunk_hash(chunk_from_thing(this->family))); +} + +/** + * Compare two queued queries + */ +static bool query_equals(query_t *this, query_t *other) +{ + return this->family == other->family && streq(this->name, other->name); +} + +/** + * Main function of resolver threads + */ +static void *resolve_hosts(private_host_resolver_t *this) +{ + struct addrinfo hints, *result; + query_t *query; + int error; + bool old, timed_out; + + while (TRUE) + { + this->mutex->lock(this->mutex); + thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex); + while (this->queue->remove_first(this->queue, + (void**)&query) != SUCCESS) + { + old = thread_cancelability(TRUE); + timed_out = this->new_query->timed_wait(this->new_query, + this->mutex, NEW_QUERY_WAIT_TIMEOUT * 1000); + thread_cancelability(old); + if (this->disabled) + { + thread_cleanup_pop(TRUE); + return NULL; + } + else if (timed_out && (this->threads > this->min_threads)) + { /* terminate this thread by detaching it */ + thread_t *thread = thread_current(); + + this->threads--; + this->pool->remove(this->pool, thread, NULL); + thread_cleanup_pop(TRUE); + thread->detach(thread); + return NULL; + } + } + this->busy_threads++; + thread_cleanup_pop(TRUE); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = query->family; + hints.ai_socktype = SOCK_DGRAM; + + thread_cleanup_push((thread_cleanup_t)query_signal_and_destroy, query); + old = thread_cancelability(TRUE); + error = getaddrinfo(query->name, NULL, &hints, &result); + thread_cancelability(old); + thread_cleanup_pop(FALSE); + + this->mutex->lock(this->mutex); + this->busy_threads--; + if (error != 0) + { + DBG1(DBG_LIB, "resolving '%s' failed: %s", query->name, + gai_strerror(error)); + } + else + { /* result is a linked list, but we use only the first address */ + query->result = host_create_from_sockaddr(result->ai_addr); + freeaddrinfo(result); + } + this->queries->remove(this->queries, query); + query->done->broadcast(query->done); + this->mutex->unlock(this->mutex); + query_destroy(query); + } + return NULL; +} + +/** + * Try to convert IP addresses directly + */ +static host_t *try_numeric_lookup(char *name, int family) +{ + struct addrinfo hints, *result; + int error; + host_t *host; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICHOST; + + error = getaddrinfo(name, NULL, &hints, &result); + if (error != 0) + { /* not an IP address */ + return NULL; + } + else + { /* result is a linked list, but we use only the first address */ + host = host_create_from_sockaddr(result->ai_addr); + freeaddrinfo(result); + } + return host; +} + +METHOD(host_resolver_t, resolve, host_t*, + private_host_resolver_t *this, char *name, int family) +{ + query_t *query, lookup = { + .name = name, + .family = family, + }; + host_t *result; + + if (streq(name, "%any") || streq(name, "0.0.0.0")) + { + return host_create_any(family ? family : AF_INET); + } + if (streq(name, "%any6") || streq(name, "::")) + { + return host_create_any(family ? family : AF_INET6); + } + if (family == AF_INET && strchr(name, ':')) + { /* do not try to convert v6 addresses for v4 family */ + return NULL; + } + result = try_numeric_lookup(name, family); + if (result) + { /* shortcut for numeric IP addresses */ + return result; + } + this->mutex->lock(this->mutex); + if (this->disabled) + { + this->mutex->unlock(this->mutex); + return NULL; + } + query = this->queries->get(this->queries, &lookup); + if (!query) + { + INIT(query, + .name = strdup(name), + .family = family, + .done = condvar_create(CONDVAR_TYPE_DEFAULT), + .refcount = 1, + ); + this->queries->put(this->queries, query, query); + this->queue->insert_last(this->queue, query); + this->new_query->signal(this->new_query); + } + ref_get(&query->refcount); + if (this->busy_threads == this->threads && + this->threads < this->max_threads) + { + thread_t *thread; + + thread = thread_create((thread_main_t)resolve_hosts, this); + if (thread) + { + this->threads++; + this->pool->insert_last(this->pool, thread); + } + } + query->done->wait(query->done, this->mutex); + this->mutex->unlock(this->mutex); + + result = query->result ? query->result->clone(query->result) : NULL; + query_destroy(query); + return result; +} + +METHOD(host_resolver_t, flush, void, + private_host_resolver_t *this) +{ + enumerator_t *enumerator; + query_t *query; + + this->mutex->lock(this->mutex); + enumerator = this->queries->create_enumerator(this->queries); + while (enumerator->enumerate(enumerator, &query, NULL)) + { /* use the hashtable here as we also want to signal dequeued queries */ + this->queries->remove_at(this->queries, enumerator); + query->done->broadcast(query->done); + } + enumerator->destroy(enumerator); + this->queue->destroy_function(this->queue, (void*)query_destroy); + this->queue = linked_list_create(); + this->disabled = TRUE; + /* this will already terminate most idle threads */ + this->new_query->broadcast(this->new_query); + this->mutex->unlock(this->mutex); +} + +METHOD(host_resolver_t, destroy, void, + private_host_resolver_t *this) +{ + thread_t *thread; + + flush(this); + this->pool->invoke_offset(this->pool, offsetof(thread_t, cancel)); + while (this->pool->remove_first(this->pool, (void**)&thread) == SUCCESS) + { + thread->join(thread); + } + this->pool->destroy(this->pool); + this->queue->destroy(this->queue); + this->queries->destroy(this->queries); + this->new_query->destroy(this->new_query); + this->mutex->destroy(this->mutex); + free(this); +} + +/* + * Described in header + */ +host_resolver_t *host_resolver_create() +{ + private_host_resolver_t *this; + + INIT(this, + .public = { + .resolve = _resolve, + .flush = _flush, + .destroy = _destroy, + }, + .queries = hashtable_create((hashtable_hash_t)query_hash, + (hashtable_equals_t)query_equals, 8), + .queue = linked_list_create(), + .pool = linked_list_create(), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .new_query = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + this->min_threads = max(0, lib->settings->get_int(lib->settings, + "libstrongswan.host_resolver.min_threads", + MIN_THREADS_DEFAULT)); + this->max_threads = max(this->min_threads ?: 1, + lib->settings->get_int(lib->settings, + "libstrongswan.host_resolver.max_threads", + MAX_THREADS_DEFAULT)); + return &this->public; +} diff --git a/src/libstrongswan/host_resolver.h b/src/libstrongswan/host_resolver.h new file mode 100644 index 000000000..f7b8c7e9f --- /dev/null +++ b/src/libstrongswan/host_resolver.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 host_resolver host_resolver + * @{ @ingroup libstrongswan + */ + +#ifndef HOST_RESOLVER_H_ +#define HOST_RESOLVER_H_ + +#include "utils/host.h" + +typedef struct host_resolver_t host_resolver_t; + +/** + * Resolve hosts by DNS name but do so in a separate thread (calling + * getaddrinfo(3) directly might block indefinitely, or at least a very long + * time if no DNS servers are reachable). + */ +struct host_resolver_t { + + /** + * Resolve host from the given DNS name. + * + * @param name name to lookup + * @param family requested address family + * @return resolved host or NULL if failed or canceled + */ + host_t *(*resolve)(host_resolver_t *this, char *name, int family); + + /** + * Flush the queue of queries. No new queries will be accepted afterwards. + */ + void (*flush)(host_resolver_t *this); + + /** + * Destroy a host_resolver_t. + */ + void (*destroy)(host_resolver_t *this); +}; + +/** + * Create a host_resolver_t instance. + */ +host_resolver_t *host_resolver_create(); + +#endif /** HOST_RESOLVER_H_ @}*/ diff --git a/src/libstrongswan/library.c b/src/libstrongswan/library.c index 1179b468c..a42d68cbc 100644 --- a/src/libstrongswan/library.c +++ b/src/libstrongswan/library.c @@ -68,6 +68,7 @@ void library_deinit() this->public.scheduler->destroy(this->public.scheduler); this->public.processor->destroy(this->public.processor); this->public.plugins->destroy(this->public.plugins); + this->public.hosts->destroy(this->public.hosts); this->public.settings->destroy(this->public.settings); this->public.credmgr->destroy(this->public.credmgr); this->public.creds->destroy(this->public.creds); @@ -183,6 +184,7 @@ bool library_init(char *settings) this->objects = hashtable_create((hashtable_hash_t)hash, (hashtable_equals_t)equals, 4); this->public.settings = settings_create(settings); + this->public.hosts = host_resolver_create(); this->public.proposal = proposal_keywords_create(); this->public.crypto = crypto_factory_create(); this->public.creds = credential_factory_create(); diff --git a/src/libstrongswan/library.h b/src/libstrongswan/library.h index b79bd91be..5bd0d67eb 100644 --- a/src/libstrongswan/library.h +++ b/src/libstrongswan/library.h @@ -77,6 +77,7 @@ #include "printf_hook.h" #include "utils.h" #include "chunk.h" +#include "host_resolver.h" #include "settings.h" #include "integrity_checker.h" #include "processing/processor.h" @@ -171,6 +172,11 @@ struct library_t { scheduler_t *scheduler; /** + * resolve hosts by DNS name + */ + host_resolver_t *hosts; + + /** * various settings loaded from settings file */ settings_t *settings; diff --git a/src/libstrongswan/threading/mutex.c b/src/libstrongswan/threading/mutex.c index 2ef918a28..df0cd27bf 100644 --- a/src/libstrongswan/threading/mutex.c +++ b/src/libstrongswan/threading/mutex.c @@ -282,13 +282,7 @@ METHOD(condvar_t, timed_wait, bool, ms = timeout % 1000; tv.tv_sec += s; - tv.tv_usec += ms * 1000; - - if (tv.tv_usec > 1000000 /* 1s */) - { - tv.tv_usec -= 1000000; - tv.tv_sec++; - } + timeval_add_ms(&tv, ms); return timed_wait_abs(this, mutex, tv); } diff --git a/src/libstrongswan/threading/rwlock.c b/src/libstrongswan/threading/rwlock.c index 7097a8e8c..9ce9c6a71 100644 --- a/src/libstrongswan/threading/rwlock.c +++ b/src/libstrongswan/threading/rwlock.c @@ -433,13 +433,8 @@ METHOD(rwlock_condvar_t, timed_wait, bool, ms = timeout % 1000; tv.tv_sec += s; - tv.tv_usec += ms * 1000; + timeval_add_ms(&tv, ms); - if (tv.tv_usec > 1000000 /* 1s */) - { - tv.tv_usec -= 1000000; - tv.tv_sec++; - } return timed_wait_abs(this, lock, tv); } diff --git a/src/libstrongswan/utils.h b/src/libstrongswan/utils.h index f47c65ac1..4e2065f63 100644 --- a/src/libstrongswan/utils.h +++ b/src/libstrongswan/utils.h @@ -455,6 +455,22 @@ void closefrom(int lowfd); time_t time_monotonic(timeval_t *tv); /** + * Add the given number of milliseconds to the given timeval struct + * + * @param tv timeval struct to modify + * @param ms number of milliseconds + */ +static inline void timeval_add_ms(timeval_t *tv, u_int ms) +{ + tv->tv_usec += ms * 1000; + while (tv->tv_usec > 1000000 /* 1s */) + { + tv->tv_usec -= 1000000; + tv->tv_sec++; + } +} + +/** * returns null */ void *return_null(); diff --git a/src/libstrongswan/utils/host.c b/src/libstrongswan/utils/host.c index e17b6ad02..1d0614001 100644 --- a/src/libstrongswan/utils/host.c +++ b/src/libstrongswan/utils/host.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Tobias Brunner + * Copyright (C) 2006-2012 Tobias Brunner * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter @@ -16,14 +16,10 @@ * for more details. */ -#define _GNU_SOURCE -#include <sys/socket.h> -#include <netdb.h> -#include <string.h> - #include "host.h" #include <debug.h> +#include <library.h> #define IPV4_LEN 4 #define IPV6_LEN 16 @@ -450,48 +446,14 @@ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr) */ host_t *host_create_from_dns(char *string, int af, u_int16_t port) { - private_host_t *this; - struct addrinfo hints, *result; - int error; - - if (streq(string, "%any")) - { - return host_create_any_port(af ? af : AF_INET, port); - } - if (streq(string, "%any6")) - { - return host_create_any_port(af ? af : AF_INET6, port); - } - if (af == AF_INET && strchr(string, ':')) - { /* do not try to convert v6 addresses for v4 family */ - return NULL; - } + host_t *this; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = af; - error = getaddrinfo(string, NULL, &hints, &result); - if (error != 0) - { - DBG1(DBG_LIB, "resolving '%s' failed: %s", string, gai_strerror(error)); - return NULL; - } - /* result is a linked list, but we use only the first address */ - this = (private_host_t*)host_create_from_sockaddr(result->ai_addr); - freeaddrinfo(result); + this = lib->hosts->resolve(lib->hosts, string, af); if (this) { - switch (this->address.sa_family) - { - case AF_INET: - this->address4.sin_port = htons(port); - break; - case AF_INET6: - this->address6.sin6_port = htons(port); - break; - } - return &this->public; + this->set_port(this, port); } - return NULL; + return this; } /* |