diff options
author | Tobias Brunner <tobias@strongswan.org> | 2012-08-13 12:07:52 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2012-08-13 12:07:52 +0200 |
commit | 09ae3d79ca0f1143736a0de45678915a3ebe5a3f (patch) | |
tree | d304e8e618450d0e4867dfc72e14ed575a442c20 /src | |
parent | e4ef4c9877d5ccb773ace0edf655351428c39572 (diff) | |
parent | efbb5e8c57330ec730825aa31ac5153f1d1c5913 (diff) | |
download | strongswan-09ae3d79ca0f1143736a0de45678915a3ebe5a3f.tar.bz2 strongswan-09ae3d79ca0f1143736a0de45678915a3ebe5a3f.tar.xz |
Merge branch 'android-app'
This branch introduces a userland IPsec implementation (libipsec) and an
Android App which targets the VpnService API that is provided by Android 4+.
The implementation is based on the bachelor thesis 'Userland IPsec for
Android 4' by Giuliano Grassi and Ralf Sager.
Diffstat (limited to 'src')
115 files changed, 12087 insertions, 419 deletions
diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 5b1d03d7c..747fe1df3 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -1,29 +1,71 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.strongswan.android" android:versionCode="1" - android:versionName="1.0" > + android:versionName="1.0.0" > <uses-sdk android:minSdkVersion="14" /> + <uses-permission android:name="android.permission.INTERNET" /> <application android:icon="@drawable/ic_launcher" - android:label="@string/app_name" > + android:label="@string/app_name" + android:theme="@style/ApplicationTheme" > <activity - android:name=".strongSwanActivity" - android:label="@string/app_name" > + android:name=".ui.MainActivity" + android:label="@string/main_activity_name" + android:launchMode="singleTop" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <service android:name=".CharonVpnService" android:permission="android.permission.BIND_VPN_SERVICE"> + <activity + android:name=".ui.VpnProfileDetailActivity" > + </activity> + <activity + android:name=".ui.LogActivity" + android:label="@string/log_title" > + </activity> + + <service + android:name=".logic.VpnStateService" + android:exported="false" > + </service> + <service + android:name=".logic.CharonVpnService" + android:exported="false" + android:permission="android.permission.BIND_VPN_SERVICE" > <intent-filter> - <action android:name="android.net.VpnService"/> + <action android:name="org.strongswan.android.logic.CharonVpnService" /> </intent-filter> </service> + + <provider + android:name=".data.LogContentProvider" + android:authorities="org.strongswan.android.content.log" > + <!-- android:grantUriPermissions="true" combined with a custom permission does + not work (probably too many indirections with ACTION_SEND) so we secure + this provider with a custom ticketing system --> + </provider> </application> -</manifest>
\ No newline at end of file +</manifest> diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index 3b8b98b86..e1806f702 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -3,7 +3,14 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ -charonservice.c +android_jni.c android_jni.h \ +backend/android_attr.c backend/android_attr.h \ +backend/android_creds.c backend/android_creds.h \ +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 \ +vpnservice_builder.c vpnservice_builder.h # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c new file mode 100644 index 000000000..e7cb14fb7 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "android_jni.h" + +#include <library.h> +#include <threading/thread_value.h> + +/** + * JVM + */ +static JavaVM *android_jvm; + +jclass *android_charonvpnservice_class; +jclass *android_charonvpnservice_builder_class; + +/** + * Thread-local variable. Only used because of the destructor + */ +static thread_value_t *androidjni_threadlocal; + +/** + * Thread-local destructor to ensure that a native thread is detached + * from the JVM even if androidjni_detach_thread() is not called. + */ +static void attached_thread_cleanup(void *arg) +{ + (*android_jvm)->DetachCurrentThread(android_jvm); +} + +/* + * Described in header + */ +void androidjni_attach_thread(JNIEnv **env) +{ + if ((*android_jvm)->GetEnv(android_jvm, (void**)env, + JNI_VERSION_1_6) == JNI_OK) + { /* already attached or even a Java thread */ + return; + } + (*android_jvm)->AttachCurrentThread(android_jvm, env, NULL); + /* use a thread-local value with a destructor that automatically detaches + * the thread from the JVM before it terminates, if not done manually */ + androidjni_threadlocal->set(androidjni_threadlocal, (void*)*env); +} + +/* + * Described in header + */ +void androidjni_detach_thread() +{ + if (androidjni_threadlocal->get(androidjni_threadlocal)) + { /* only do this if we actually attached this thread */ + androidjni_threadlocal->set(androidjni_threadlocal, NULL); + (*android_jvm)->DetachCurrentThread(android_jvm); + } +} + +/** + * Called when this library is loaded by the JVM + */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv *env; + + android_jvm = vm; + + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return -1; + } + + androidjni_threadlocal = thread_value_create(attached_thread_cleanup); + + android_charonvpnservice_class = + (*env)->NewGlobalRef(env, (*env)->FindClass(env, + JNI_PACKAGE_STRING "/CharonVpnService")); + android_charonvpnservice_builder_class = + (*env)->NewGlobalRef(env, (*env)->FindClass(env, + JNI_PACKAGE_STRING "/CharonVpnService$BuilderAdapter")); + + return JNI_VERSION_1_6; +} + +/** + * Called when this library is unloaded by the JVM (which never happens on + * Android) + */ +void JNI_OnUnload(JavaVM *vm, void *reserved) +{ + androidjni_threadlocal->destroy(androidjni_threadlocal); +} + diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h new file mode 100644 index 000000000..774d37d7e --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 android_jni android_jni + * @{ @ingroup libandroidbridge + */ + +#ifndef ANDROID_JNI_H_ +#define ANDROID_JNI_H_ + +#include <jni.h> +#include <library.h> + +#define JNI_PACKAGE org_strongswan_android_logic +#define JNI_PACKAGE_STRING "org/strongswan/android/logic" + +#define JNI_METHOD_PP(pack, klass, name, ret, ...) \ + ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) + +#define JNI_METHOD_P(pack, klass, name, ret, ...) \ + JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__) + +#define JNI_METHOD(klass, name, ret, ...) \ + JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__) + +/** + * Java classes + * Initialized in JNI_OnLoad() + */ +extern jclass *android_charonvpnservice_class; +extern jclass *android_charonvpnservice_builder_class; + +/** + * Attach the current thread to the JVM + * + * As local JNI references are not freed until the thread detaches + * androidjni_detach_thread() should be called as soon as possible. + * If it is not called a thread-local destructor ensures that the + * thread is at least detached as soon as it terminates. + * + * @param env JNIEnv + */ +void androidjni_attach_thread(JNIEnv **env); + +/** + * Detach the current thread from the JVM + * + * Call this as soon as possible to ensure that local JNI references are freed. + */ +void androidjni_detach_thread(); + +/** + * Handle exceptions thrown by a JNI call + * + * @param env JNIEnv + * @return TRUE if an exception was thrown + */ +static inline bool androidjni_exception_occurred(JNIEnv *env) +{ + if ((*env)->ExceptionOccurred(env)) + { /* clear any exception, otherwise the VM is terminated */ + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return TRUE; + } + return FALSE; +} + +/** + * Convert a Java string to a C string. Memory is allocated. + * + * @param env JNIEnv + * @param jstr Java string + * @return native C string (allocated) + */ +static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr) +{ + char *str; + jsize len; + + len = (*env)->GetStringUTFLength(env, jstr); + str = malloc(len + 1); + (*env)->GetStringUTFRegion(env, jstr, 0, len, str); + str[len] = '\0'; + return str; +} + +#endif /** ANDROID_JNI_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_attr.c b/src/frontends/android/jni/libandroidbridge/backend/android_attr.c new file mode 100644 index 000000000..e8c506950 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_attr.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "android_attr.h" +#include "../charonservice.h" + +#include <hydra.h> +#include <debug.h> +#include <library.h> + +typedef struct private_android_attr_t private_android_attr_t; + +/** + * Private data of an android_attr_t object. + */ +struct private_android_attr_t { + + /** + * Public interface. + */ + android_attr_t public; +}; + +METHOD(attribute_handler_t, handle, bool, + private_android_attr_t *this, identification_t *server, + configuration_attribute_type_t type, chunk_t data) +{ + vpnservice_builder_t *builder; + host_t *dns; + + switch (type) + { + case INTERNAL_IP4_DNS: + dns = host_create_from_chunk(AF_INET, data, 0); + break; + default: + return FALSE; + } + + if (!dns || dns->is_anyaddr(dns)) + { + DESTROY_IF(dns); + return FALSE; + } + + builder = charonservice->get_vpnservice_builder(charonservice); + builder->add_dns(builder, dns); + dns->destroy(dns); + return TRUE; +} + +METHOD(attribute_handler_t, release, void, + private_android_attr_t *this, identification_t *server, + configuration_attribute_type_t type, chunk_t data) +{ + /* DNS servers cannot be removed from an existing TUN device */ +} + +METHOD(enumerator_t, enumerate_dns, bool, + enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) +{ + *type = INTERNAL_IP4_DNS; + *data = chunk_empty; + this->enumerate = (void*)return_false; + return TRUE; +} + +METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*, + private_android_attr_t *this, identification_t *server, host_t *vip) +{ + enumerator_t *enumerator; + + INIT(enumerator, + .enumerate = (void*)_enumerate_dns, + .destroy = (void*)free, + ); + return enumerator; +} + +METHOD(android_attr_t, destroy, void, + private_android_attr_t *this) +{ + free(this); +} + +/** + * Described in header + */ +android_attr_t *android_attr_create() +{ + private_android_attr_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/frontends/android/jni/libandroidbridge/backend/android_attr.h b/src/frontends/android/jni/libandroidbridge/backend/android_attr.h new file mode 100644 index 000000000..56b02e1ce --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_attr.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 android_attr android_attr + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_ATTR_H_ +#define ANDROID_ATTR_H_ + +#include <library.h> +#include <attributes/attribute_handler.h> + +typedef struct android_attr_t android_attr_t; + +/** + * Handler for DNS configuration + */ +struct android_attr_t { + + /** + * implements the attribute_handler_t interface + */ + attribute_handler_t handler; + + /** + * Destroy a android_attr_t + */ + void (*destroy)(android_attr_t *this); +}; + +/** + * Create a android_attr_t instance. + */ +android_attr_t *android_attr_create(void); + +#endif /** ANDROID_ATTR_H_ @}*/ + diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c new file mode 100644 index 000000000..27023d721 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c @@ -0,0 +1,176 @@ +/* + * 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 "android_creds.h" +#include "../charonservice.h" + +#include <daemon.h> +#include <library.h> +#include <credentials/sets/mem_cred.h> +#include <threading/rwlock.h> + +typedef struct private_android_creds_t private_android_creds_t; + +/** + * Private data of an android_creds_t object + */ +struct private_android_creds_t { + + /** + * Public interface + */ + android_creds_t public; + + /** + * Credential set storing trusted certificates and user credentials + */ + mem_cred_t *creds; + + /** + * read/write lock to make sure certificates are only loaded once + */ + rwlock_t *lock; + + /** + * TRUE if certificates have been loaded via JNI + */ + bool loaded; +}; + +/** + * Load trusted certificates via charonservice (JNI). + */ +static void load_trusted_certificates(private_android_creds_t *this) +{ + linked_list_t *certs; + certificate_t *cert; + chunk_t *current; + + certs = charonservice->get_trusted_certificates(charonservice); + if (certs) + { + while (certs->remove_first(certs, (void**)¤t) == SUCCESS) + { + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (cert) + { + DBG2(DBG_CFG, "loaded CA certificate '%Y'", + cert->get_subject(cert)); + this->creds->add_cert(this->creds, TRUE, cert); + } + chunk_free(current); + free(current); + } + certs->destroy(certs); + } +} + +METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, + private_android_creds_t *this, certificate_type_t cert, key_type_t key, + identification_t *id, bool trusted) +{ + enumerator_t *enumerator; + + if (!trusted || (cert != CERT_ANY && cert != CERT_X509)) + { + return NULL; + } + this->lock->read_lock(this->lock); + if (!this->loaded) + { + this->lock->unlock(this->lock); + this->lock->write_lock(this->lock); + /* check again after acquiring the write lock */ + if (!this->loaded) + { + load_trusted_certificates(this); + this->loaded = TRUE; + } + this->lock->unlock(this->lock); + this->lock->read_lock(this->lock); + } + enumerator = this->creds->set.create_cert_enumerator(&this->creds->set, + cert, key, id, trusted); + return enumerator_create_cleaner(enumerator, (void*)this->lock->unlock, + this->lock); +} + +METHOD(android_creds_t, add_username_password, void, + private_android_creds_t *this, char *username, char *password) +{ + shared_key_t *shared_key; + identification_t *id; + chunk_t secret; + + secret = chunk_create(password, strlen(password)); + shared_key = shared_key_create(SHARED_EAP, chunk_clone(secret)); + id = identification_create_from_string(username); + + this->creds->add_shared(this->creds, shared_key, id, NULL); +} + +METHOD(credential_set_t, create_shared_enumerator, enumerator_t*, + private_android_creds_t *this, shared_key_type_t type, + identification_t *me, identification_t *other) +{ + return this->creds->set.create_shared_enumerator(&this->creds->set, + type, me, other); +} + +METHOD(android_creds_t, clear, void, + private_android_creds_t *this) +{ + this->lock->write_lock(this->lock); + this->creds->clear(this->creds); + this->loaded = FALSE; + this->lock->unlock(this->lock); +} + +METHOD(android_creds_t, destroy, void, + private_android_creds_t *this) +{ + clear(this); + this->creds->destroy(this->creds); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +android_creds_t *android_creds_create() +{ + private_android_creds_t *this; + + INIT(this, + .public = { + .set = { + .create_cert_enumerator = _create_cert_enumerator, + .create_shared_enumerator = _create_shared_enumerator, + .create_private_enumerator = (void*)return_null, + .create_cdp_enumerator = (void*)return_null, + .cache_cert = (void*)nop, + }, + .add_username_password = _add_username_password, + .clear = _clear, + .destroy = _destroy, + }, + .creds = mem_cred_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h new file mode 100644 index 000000000..33de838c1 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h @@ -0,0 +1,67 @@ +/* + * 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 android_creds android_creds + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_CREDS_H_ +#define ANDROID_CREDS_H_ + +#include <library.h> +#include <credentials/credential_set.h> + +typedef struct android_creds_t android_creds_t; + +/** + * Android credential set that provides CA certificates via JNI and supplied + * user credentials. + */ +struct android_creds_t { + + /** + * Implements credential_set_t + */ + credential_set_t set; + + /** + * Add user name and password for EAP authentication + * + * @param username user name + * @param password password + */ + void (*add_username_password)(android_creds_t *this, char *username, + char *password); + + /** + * Clear the cached certificates and stored credentials. + */ + void (*clear)(android_creds_t *this); + + /** + * Destroy a android_creds instance. + */ + void (*destroy)(android_creds_t *this); + +}; + +/** + * Create an android_creds instance. + */ +android_creds_t *android_creds_create(); + +#endif /** ANDROID_CREDS_H_ @}*/ + diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c new file mode 100644 index 000000000..dfc0d2342 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2010-2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 <errno.h> +#include <unistd.h> + +#include "android_service.h" +#include "../charonservice.h" +#include "../vpnservice_builder.h" + +#include <daemon.h> +#include <library.h> +#include <ipsec.h> +#include <processing/jobs/callback_job.h> +#include <threading/rwlock.h> +#include <threading/thread.h> + +typedef struct private_android_service_t private_android_service_t; + +#define TUN_DEFAULT_MTU 1400 + +/** + * private data of Android service + */ +struct private_android_service_t { + + /** + * public interface + */ + android_service_t public; + + /** + * current IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * local ipv4 address + */ + char *local_address; + + /** + * gateway + */ + char *gateway; + + /** + * username + */ + char *username; + + /** + * lock to safely access the TUN device fd + */ + rwlock_t *lock; + + /** + * TUN device file descriptor + */ + int tunfd; + +}; + +/** + * Outbound callback + */ +static void send_esp(void *data, esp_packet_t *packet) +{ + charon->sender->send_no_marker(charon->sender, (packet_t*)packet); +} + +/** + * Inbound callback + */ +static void deliver_plain(private_android_service_t *this, + ip_packet_t *packet) +{ + chunk_t encoding; + ssize_t len; + + encoding = packet->get_encoding(packet); + + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* the TUN device is already closed */ + this->lock->unlock(this->lock); + packet->destroy(packet); + return; + } + len = write(this->tunfd, encoding.ptr, encoding.len); + this->lock->unlock(this->lock); + + if (len < 0 || len != encoding.len) + { + DBG1(DBG_DMN, "failed to write packet to TUN device: %s", + strerror(errno)); + } + packet->destroy(packet); +} + +/** + * Receiver callback + */ +static void receiver_esp_cb(void *data, packet_t *packet) +{ + esp_packet_t *esp_packet; + + esp_packet = esp_packet_create_from_packet(packet); + ipsec->processor->queue_inbound(ipsec->processor, esp_packet); +} + +/** + * Job handling outbound plaintext packets + */ +static job_requeue_t handle_plain(private_android_service_t *this) +{ + ip_packet_t *packet; + chunk_t raw; + fd_set set; + ssize_t len; + int tunfd; + bool old; + + FD_ZERO(&set); + + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* the TUN device is already closed */ + this->lock->unlock(this->lock); + return JOB_REQUEUE_NONE; + } + tunfd = this->tunfd; + FD_SET(tunfd, &set); + this->lock->unlock(this->lock); + + old = thread_cancelability(TRUE); + len = select(tunfd + 1, &set, NULL, NULL, NULL); + thread_cancelability(old); + + if (len < 0) + { + DBG1(DBG_DMN, "select on TUN device failed: %s", strerror(errno)); + return JOB_REQUEUE_NONE; + } + + raw = chunk_alloc(TUN_DEFAULT_MTU); + len = read(tunfd, raw.ptr, raw.len); + if (len < 0) + { + DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno)); + chunk_free(&raw); + return JOB_REQUEUE_FAIR; + } + raw.len = len; + + packet = ip_packet_create(raw); + if (packet) + { + ipsec->processor->queue_outbound(ipsec->processor, packet); + } + else + { + DBG1(DBG_DMN, "invalid IP packet read from TUN device"); + } + return JOB_REQUEUE_DIRECT; +} + +/** + * Add a route to the TUN device builder + */ +static bool add_route(vpnservice_builder_t *builder, host_t *net, + u_int8_t prefix) +{ + /* if route is 0.0.0.0/0, split it into two routes 0.0.0.0/1 and + * 128.0.0.0/1 because otherwise it would conflict with the current default + * route */ + if (net->is_anyaddr(net) && prefix == 0) + { + bool success; + + success = add_route(builder, net, 1); + net = host_create_from_string("128.0.0.0", 0); + success = success && add_route(builder, net, 1); + net->destroy(net); + return success; + } + return builder->add_route(builder, net, prefix); +} + +/** + * Generate and set routes from installed IPsec policies + */ +static bool add_routes(vpnservice_builder_t *builder, child_sa_t *child_sa) +{ + traffic_selector_t *src_ts, *dst_ts; + enumerator_t *enumerator; + bool success = TRUE; + + enumerator = child_sa->create_policy_enumerator(child_sa); + while (success && enumerator->enumerate(enumerator, &src_ts, &dst_ts)) + { + host_t *net; + u_int8_t prefix; + + dst_ts->to_subnet(dst_ts, &net, &prefix); + success = add_route(builder, net, prefix); + net->destroy(net); + } + enumerator->destroy(enumerator); + return success; +} + +/** + * Setup a new TUN device for the supplied SAs, also queues a job that + * reads packets from this device. + * Additional information such as DNS servers are gathered in appropriate + * listeners asynchronously. To be sure every required bit of information is + * available this should be called after the CHILD_SA has been established. + */ +static bool setup_tun_device(private_android_service_t *this, + ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + vpnservice_builder_t *builder; + host_t *vip; + int tunfd; + + DBG1(DBG_DMN, "setting up TUN device for CHILD_SA %s{%u}", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa)); + vip = ike_sa->get_virtual_ip(ike_sa, TRUE); + if (!vip || vip->is_anyaddr(vip)) + { + DBG1(DBG_DMN, "setting up TUN device failed, no virtual IP found"); + return FALSE; + } + + builder = charonservice->get_vpnservice_builder(charonservice); + if (!builder->add_address(builder, vip) || + !add_routes(builder, child_sa) || + !builder->set_mtu(builder, TUN_DEFAULT_MTU)) + { + return FALSE; + } + + tunfd = builder->establish(builder); + if (tunfd == -1) + { + return FALSE; + } + + this->lock->write_lock(this->lock); + this->tunfd = tunfd; + this->lock->unlock(this->lock); + + DBG1(DBG_DMN, "successfully created TUN device"); + + charon->receiver->add_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb, NULL); + ipsec->processor->register_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain, this); + ipsec->processor->register_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp, NULL); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, + NULL, (callback_job_cancel_t)return_false)); + return TRUE; +} + +/** + * Close the current tun device + */ +static void close_tun_device(private_android_service_t *this) +{ + int tunfd; + + this->lock->write_lock(this->lock); + if (this->tunfd < 0) + { /* already closed (or never created) */ + this->lock->unlock(this->lock); + return; + } + tunfd = this->tunfd; + this->tunfd = -1; + this->lock->unlock(this->lock); + + ipsec->processor->unregister_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp); + ipsec->processor->unregister_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain); + charon->receiver->del_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb); + close(tunfd); +} + +METHOD(listener_t, child_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, + bool up) +{ + if (this->ike_sa == ike_sa) + { + if (up) + { + /* 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"); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return FALSE; + + } + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_UP); + } + else + { + close_tun_device(this); + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_DOWN); + return FALSE; + } + } + return TRUE; +} + +METHOD(listener_t, ike_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, bool up) +{ + /* this callback is only registered during initiation, so if the IKE_SA + * goes down we assume an authentication error */ + if (this->ike_sa == ike_sa && !up) + { + charonservice->update_status(charonservice, + CHARONSERVICE_AUTH_ERROR); + return FALSE; + } + 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) +{ + if (this->ike_sa == ike_sa) + { + switch (alert) + { + case ALERT_PEER_ADDR_FAILED: + charonservice->update_status(charonservice, + CHARONSERVICE_LOOKUP_ERROR); + break; + case ALERT_PEER_AUTH_FAILED: + charonservice->update_status(charonservice, + CHARONSERVICE_PEER_AUTH_ERROR); + break; + default: + break; + } + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + this->ike_sa = new; + } + return TRUE; +} + +static job_requeue_t initiate(private_android_service_t *this) +{ + identification_t *gateway, *user; + ike_cfg_t *ike_cfg; + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + traffic_selector_t *ts; + ike_sa_t *ike_sa; + auth_cfg_t *auth; + lifetime_cfg_t lifetime = { + .time = { + .life = 10800, /* 3h */ + .rekey = 10200, /* 2h50min */ + .jitter = 300 /* 5min */ + } + }; + + ike_cfg = ike_cfg_create(TRUE, TRUE, this->local_address, 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 */ + 36000, 0, /* rekey 10h, reauth none */ + 600, 600, /* jitter, over 10min */ + TRUE, FALSE, /* mobike, aggressive */ + 0, 0, /* DPD delay, timeout */ + host_create_from_string("0.0.0.0", 0) /* virt */, + NULL, FALSE, NULL, NULL); /* pool, mediation */ + + + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + user = identification_create_from_string(this->username); + auth->add(auth, AUTH_RULE_IDENTITY, user); + peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + gateway = identification_create_from_string(this->gateway); + auth->add(auth, AUTH_RULE_IDENTITY, gateway); + 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); + 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); + child_cfg->add_traffic_selector(child_cfg, FALSE, ts); + peer_cfg->add_child_cfg(peer_cfg, child_cfg); + + /* get us an IKE_SA */ + ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, + peer_cfg); + if (!ike_sa) + { + peer_cfg->destroy(peer_cfg); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return JOB_REQUEUE_NONE; + } + if (!ike_sa->get_peer_cfg(ike_sa)) + { + ike_sa->set_peer_cfg(ike_sa, peer_cfg); + } + peer_cfg->destroy(peer_cfg); + + /* store the IKE_SA so we can track its progress */ + this->ike_sa = ike_sa; + + /* get an additional reference because initiate consumes one */ + child_cfg->get_ref(child_cfg); + if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + { + DBG1(DBG_CFG, "failed to initiate tunnel"); + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + ike_sa); + return JOB_REQUEUE_NONE; + } + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + return JOB_REQUEUE_NONE; +} + +METHOD(android_service_t, destroy, void, + private_android_service_t *this) +{ + charon->bus->remove_listener(charon->bus, &this->public.listener); + /* make sure the tun device is actually closed */ + close_tun_device(this); + this->lock->destroy(this->lock); + free(this->local_address); + free(this->username); + free(this->gateway); + free(this); +} + +/** + * See header + */ +android_service_t *android_service_create(char *local_address, char *gateway, + char *username) +{ + private_android_service_t *this; + + INIT(this, + .public = { + .listener = { + .ike_rekey = _ike_rekey, + .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, + .gateway = gateway, + .tunfd = -1, + ); + + charon->bus->add_listener(charon->bus, &this->public.listener); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)initiate, this, + NULL, NULL)); + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h new file mode 100644 index 000000000..a7bd8b059 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010-2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 android_service android_service + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_SERVICE_H_ +#define ANDROID_SERVICE_H_ + +#include "android_creds.h" + +#include <library.h> +#include <bus/listeners/listener.h> + +typedef struct android_service_t android_service_t; + +/** + * Service that sets up an IKE_SA/CHILD_SA and handles events + */ +struct android_service_t { + + /** + * Implements listener_t. + */ + listener_t listener; + + /** + * Destroy a android_service_t. + */ + void (*destroy)(android_service_t *this); + +}; + +/** + * Create an Android service instance. Queues a job that starts initiation of a + * new IKE SA. + * + * @param local_address local ip address + * @param gateway gateway address + * @param username user name (local identity) + */ +android_service_t *android_service_create(char *local_address, char *gateway, + char *username); + +#endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 424d50d24..fab99ac10 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager * Copyright (C) 2012 Tobias Brunner * Hochschule fuer Technik Rapperswil * @@ -13,30 +15,78 @@ * for more details. */ +#include <signal.h> #include <string.h> +#include <sys/utsname.h> #include <android/log.h> -#include <jni.h> +#include <errno.h> +#include "charonservice.h" +#include "android_jni.h" +#include "backend/android_attr.h" +#include "backend/android_creds.h" +#include "backend/android_service.h" +#include "kernel/android_ipsec.h" +#include "kernel/android_net.h" + +#include <daemon.h> #include <hydra.h> #include <ipsec.h> -#include <daemon.h> #include <library.h> +#include <threading/thread.h> + +#define ANDROID_DEBUG_LEVEL 1 +#define ANDROID_RETRASNMIT_TRIES 3 +#define ANDROID_RETRANSMIT_TIMEOUT 3.0 +#define ANDROID_RETRANSMIT_BASE 1.4 + +typedef struct private_charonservice_t private_charonservice_t; + +/** + * private data of charonservice + */ +struct private_charonservice_t { -#define JNI_PACKAGE org_strongswan_android + /** + * public interface + */ + charonservice_t public; -#define JNI_METHOD_PP(pack, klass, name, ret, ...) \ - ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) + /** + * android_attr instance + */ + android_attr_t *attr; -#define JNI_METHOD_P(pack, klass, name, ret, ...) \ - JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__) + /** + * android_creds instance + */ + android_creds_t *creds; -#define JNI_METHOD(klass, name, ret, ...) \ - JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__) + /** + * android_service instance + */ + android_service_t *service; + + /** + * VpnService builder (accessed via JNI) + */ + vpnservice_builder_t *builder; + + /** + * CharonVpnService reference + */ + jobject vpn_service; +}; + +/** + * Single instance of charonservice_t. + */ +charonservice_t *charonservice; /** * hook in library for debugging messages */ -extern void (*dbg) (debug_t group, level_t level, char *fmt, ...); +extern void (*dbg)(debug_t group, level_t level, char *fmt, ...); /** * Logging hook for library logs, using android specific logging @@ -45,10 +95,11 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) { va_list args; - if (level <= 4) + if (level <= ANDROID_DEBUG_LEVEL) { char sgroup[16], buffer[8192]; char *current = buffer, *next; + snprintf(sgroup, sizeof(sgroup), "%N", debug_names, group); va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); @@ -68,10 +119,276 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) } /** + * Initialize file logger + */ +static void initialize_logger(char *logfile) +{ + file_logger_t *file_logger; + debug_t group; + FILE *file; + + /* truncate an existing file */ + file = fopen(logfile, "w"); + if (!file) + { + DBG1(DBG_DMN, "opening file %s for logging failed: %s", + logfile, strerror(errno)); + return; + } + /* flush each line */ + setlinebuf(file); + + file_logger = file_logger_create(file, "%b %e %T", FALSE); + for (group = 0; group < DBG_MAX; group++) + { + file_logger->set_level(file_logger, group, ANDROID_DEBUG_LEVEL); + } + charon->file_loggers->insert_last(charon->file_loggers, file_logger); + charon->bus->add_logger(charon->bus, &file_logger->logger); +} + +METHOD(charonservice_t, update_status, bool, + private_charonservice_t *this, android_vpn_state_t code) +{ + JNIEnv *env; + jmethodID method_id; + bool success = FALSE; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "updateStatus", "(I)V"); + if (!method_id) + { + goto failed; + } + (*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)code); + success = !androidjni_exception_occurred(env); + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return success; +} + +METHOD(charonservice_t, bypass_socket, bool, + private_charonservice_t *this, int fd, int family) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "protect", "(I)Z"); + if (!method_id) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->vpn_service, method_id, fd)) + { + DBG1(DBG_CFG, "VpnService.protect() failed"); + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, + private_charonservice_t *this) +{ + JNIEnv *env; + jmethodID method_id; + jobjectArray jcerts; + linked_list_t *list; + jsize i; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getTrustedCertificates", "(Ljava/lang/String;)[[B"); + if (!method_id) + { + goto failed; + } + jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id, NULL); + if (!jcerts) + { + goto failed; + } + list = linked_list_create(); + for (i = 0; i < (*env)->GetArrayLength(env, jcerts); ++i) + { + chunk_t *ca_cert; + jbyteArray jcert; + + ca_cert = malloc_thing(chunk_t); + list->insert_last(list, ca_cert); + + jcert = (*env)->GetObjectArrayElement(env, jcerts, i); + *ca_cert = chunk_alloc((*env)->GetArrayLength(env, jcert)); + (*env)->GetByteArrayRegion(env, jcert, 0, ca_cert->len, ca_cert->ptr); + (*env)->DeleteLocalRef(env, jcert); + } + (*env)->DeleteLocalRef(env, jcerts); + androidjni_detach_thread(); + return list; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*, + private_charonservice_t *this) +{ + return this->builder; +} + +/** + * 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 *local, char *gateway, char *username, char *password) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + + this->creds->clear(this->creds); + this->creds->add_username_password(this->creds, username, password); + memwipe(password, strlen(password)); + free(password); + + DESTROY_IF(this->service); + this->service = android_service_create(local, gateway, username); +} + +/** + * Initialize/deinitialize Android backend + */ +static bool charonservice_register(void *plugin, plugin_feature_t *feature, + bool reg, void *data) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + if (reg) + { + lib->credmgr->add_set(lib->credmgr, &this->creds->set); + hydra->attributes->add_handler(hydra->attributes, + &this->attr->handler); + } + else + { + lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + hydra->attributes->remove_handler(hydra->attributes, + &this->attr->handler); + if (this->service) + { + this->service->destroy(this->service); + this->service = NULL; + } + } + return TRUE; +} + +/** + * Initialize the charonservice object + */ +static void charonservice_init(JNIEnv *env, jobject service, jobject builder) +{ + private_charonservice_t *this; + static plugin_feature_t features[] = { + PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-net"), + PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK((plugin_feature_callback_t)charonservice_register, NULL), + PLUGIN_PROVIDE(CUSTOM, "Android backend"), + PLUGIN_DEPENDS(CUSTOM, "libcharon"), + }; + + INIT(this, + .public = { + .update_status = _update_status, + .bypass_socket = _bypass_socket, + .get_trusted_certificates = _get_trusted_certificates, + .get_vpnservice_builder = _get_vpnservice_builder, + }, + .attr = android_attr_create(), + .creds = android_creds_create(), + .builder = vpnservice_builder_create(builder), + .vpn_service = (*env)->NewGlobalRef(env, service), + ); + charonservice = &this->public; + + lib->plugins->add_static_features(lib->plugins, "androidbridge", features, + countof(features), TRUE); + + lib->settings->set_int(lib->settings, + "charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL); + lib->settings->set_int(lib->settings, + "charon.retransmit_tries", ANDROID_RETRASNMIT_TRIES); + lib->settings->set_double(lib->settings, + "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); + lib->settings->set_double(lib->settings, + "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); + lib->settings->set_bool(lib->settings, + "charon.close_ike_on_child_failure", TRUE); + /* setting the source address breaks the VpnService.protect() function which + * uses SO_BINDTODEVICE internally. the addresses provided to the kernel as + * auxiliary data have precedence over this option causing a routing loop if + * the gateway is contained in the VPN routes. alternatively, providing an + * explicit device (in addition or instead of the source address) in the + * auxiliary data would also work, but we currently don't have that + * information */ + lib->settings->set_bool(lib->settings, + "charon.plugins.socket-default.set_source", FALSE); +} + +/** + * Deinitialize the charonservice object + */ +static void charonservice_deinit(JNIEnv *env) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + + this->builder->destroy(this->builder); + this->creds->destroy(this->creds); + this->attr->destroy(this->attr); + (*env)->DeleteGlobalRef(env, this->vpn_service); + free(this); + charonservice = NULL; +} + +/** + * Handle SIGSEGV/SIGILL signals raised by threads + */ +static void segv_handler(int signal) +{ + dbg_android(DBG_DMN, 1, "thread %u received %d", thread_current_id(), + signal); + exit(1); +} + +/** * Initialize charon and the libraries via JNI */ -JNI_METHOD(CharonVpnService, initializeCharon, void) +JNI_METHOD(CharonVpnService, initializeCharon, void, + jobject builder, jstring jlogfile) { + struct sigaction action; + struct utsname utsname; + char *logfile; + /* logging for library during initialization, as we have no bus yet */ dbg = dbg_android; @@ -97,28 +414,78 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) return; } - if (!libcharon_init("charon") || - !charon->initialize(charon, PLUGINS)) + if (!libcharon_init("charon")) + { + libcharon_deinit(); + libipsec_deinit(); + libhydra_deinit(); + library_deinit(); + return; + } + + logfile = androidjni_convert_jstring(env, jlogfile); + initialize_logger(logfile); + free(logfile); + + charonservice_init(env, this, builder); + + if (uname(&utsname) != 0) + { + memset(&utsname, 0, sizeof(utsname)); + } + DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s %s, %s)", + utsname.sysname, utsname.release, utsname.machine); + + if (!charon->initialize(charon, PLUGINS)) { libcharon_deinit(); + charonservice_deinit(env); libipsec_deinit(); libhydra_deinit(); library_deinit(); return; } + /* add handler for SEGV and ILL etc. */ + action.sa_handler = segv_handler; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGILL, &action, NULL); + sigaction(SIGBUS, &action, NULL); + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + /* start daemon (i.e. the threads in the thread-pool) */ charon->start(charon); } /** - * Initialize charon and the libraries via JNI + * Deinitialize charon and all libraries */ JNI_METHOD(CharonVpnService, deinitializeCharon, void) { + /* deinitialize charon before we destroy our own objects */ libcharon_deinit(); + charonservice_deinit(env); libipsec_deinit(); libhydra_deinit(); library_deinit(); } +/** + * Initiate SA + */ +JNI_METHOD(CharonVpnService, initiate, void, + jstring jlocal_address, jstring jgateway, jstring jusername, + jstring jpassword) +{ + char *local_address, *gateway, *username, *password; + + 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(local_address, gateway, username, password); +} diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h new file mode 100644 index 000000000..706eaa220 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 libandroidbridge libandroidbridge + * + * @defgroup android_backend backend + * @ingroup libandroidbridge + * + * @defgroup android_kernel kernel + * @ingroup libandroidbridge + * + * @defgroup charonservice charonservice + * @{ @ingroup libandroidbridge + */ + +#ifndef CHARONSERVICE_H_ +#define CHARONSERVICE_H_ + +#include "vpnservice_builder.h" + +#include <library.h> +#include <utils/linked_list.h> + +typedef enum android_vpn_state_t android_vpn_state_t; +typedef struct charonservice_t charonservice_t; + +/** + * VPN status codes. As defined in CharonVpnService.java + */ +enum android_vpn_state_t { + CHARONSERVICE_CHILD_STATE_UP = 1, + CHARONSERVICE_CHILD_STATE_DOWN, + CHARONSERVICE_AUTH_ERROR, + CHARONSERVICE_PEER_AUTH_ERROR, + CHARONSERVICE_LOOKUP_ERROR, + CHARONSERVICE_UNREACHABLE_ERROR, + CHARONSERVICE_GENERIC_ERROR, +}; + +/** + * Public interface of charonservice. + * + * Used to communicate with CharonVpnService via JNI + */ +struct charonservice_t { + + /** + * Update the status in the Java domain (UI) + * + * @param code status code + * @return TRUE on success + */ + bool (*update_status)(charonservice_t *this, android_vpn_state_t code); + + /** + * Install a bypass policy for the given socket using the protect() Method + * of the Android VpnService interface + * + * @param fd socket file descriptor + * @param family socket protocol family + * @return TRUE if operation successful + */ + bool (*bypass_socket)(charonservice_t *this, int fd, int family); + + /** + * Get a list of trusted certificates via JNI + * + * @return list of DER encoded certificates (as chunk_t*), + * NULL on failure + */ + linked_list_t *(*get_trusted_certificates)(charonservice_t *this); + + /** + * Get the current vpnservice_builder_t object + * + * @return VpnService.Builder instance + */ + vpnservice_builder_t *(*get_vpnservice_builder)(charonservice_t *this); + +}; + +/** + * The single instance of charonservice_t. + * + * Set between JNI calls to initializeCharon() and deinitializeCharon(). + */ +extern charonservice_t *charonservice; + +#endif /** CHARONSERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c new file mode 100644 index 000000000..08cc61610 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "android_ipsec.h" +#include "../charonservice.h" + +#include <debug.h> +#include <library.h> +#include <hydra.h> +#include <ipsec.h> + +typedef struct private_kernel_android_ipsec_t private_kernel_android_ipsec_t; + +struct private_kernel_android_ipsec_t { + + /** + * Public kernel interface + */ + kernel_android_ipsec_t public; + + /** + * Listener for lifetime expire events + */ + ipsec_event_listener_t ipsec_listener; +}; + +/** + * Callback registrered with libipsec. + */ +void expire(u_int32_t reqid, u_int8_t protocol, u_int32_t spi, bool hard) +{ + hydra->kernel_interface->expire(hydra->kernel_interface, reqid, protocol, + spi, hard); +} + +METHOD(kernel_ipsec_t, get_spi, status_t, + private_kernel_android_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_android_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_android_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 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, encap, esn, inbound, src_ts, + dst_ts); +} + +METHOD(kernel_ipsec_t, update_sa, status_t, + private_kernel_android_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_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, mark_t mark, u_int64_t *bytes) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, del_sa, status_t, + private_kernel_android_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_android_ipsec_t *this) +{ + return ipsec->sas->flush_sas(ipsec->sas); +} + +METHOD(kernel_ipsec_t, add_policy, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority) +{ + return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, + dst_ts, direction, type, sa, mark, + priority); +} + +METHOD(kernel_ipsec_t, query_policy, status_t, + private_kernel_android_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_android_ipsec_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t priority) +{ + return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, + direction, reqid, mark, priority); +} + +METHOD(kernel_ipsec_t, flush_policies, status_t, + private_kernel_android_ipsec_t *this) +{ + ipsec->policies->flush_policies(ipsec->policies); + return SUCCESS; +} + +METHOD(kernel_ipsec_t, bypass_socket, bool, + private_kernel_android_ipsec_t *this, int fd, int family) +{ + return charonservice->bypass_socket(charonservice, fd, family); +} + +METHOD(kernel_ipsec_t, enable_udp_decap, bool, + private_kernel_android_ipsec_t *this, int fd, int family, u_int16_t port) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, destroy, void, + private_kernel_android_ipsec_t *this) +{ + ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener); + free(this); +} + +/* + * Described in header. + */ +kernel_android_ipsec_t *kernel_android_ipsec_create() +{ + private_kernel_android_ipsec_t *this; + + INIT(this, + .public = { + .interface = { + .get_spi = _get_spi, + .get_cpi = _get_cpi, + .add_sa = _add_sa, + .update_sa = _update_sa, + .query_sa = _query_sa, + .del_sa = _del_sa, + .flush_sas = _flush_sas, + .add_policy = _add_policy, + .query_policy = _query_policy, + .del_policy = _del_policy, + .flush_policies = _flush_policies, + .bypass_socket = _bypass_socket, + .enable_udp_decap = _enable_udp_decap, + .destroy = _destroy, + }, + }, + .ipsec_listener = { + .expire = expire, + }, + ); + + ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h new file mode 100644 index 000000000..3a2e8343f --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 kernel_android_ipsec kernel_android_ipsec + * @{ @ingroup kernel_android + */ + +#ifndef KERNEL_ANDROID_IPSEC_H_ +#define KERNEL_ANDROID_IPSEC_H_ + +#include <library.h> +#include <kernel/kernel_ipsec.h> + +typedef struct kernel_android_ipsec_t kernel_android_ipsec_t; + +/** + * Implementation of the ipsec interface using libipsec on Android + */ +struct kernel_android_ipsec_t { + + /** + * Implements kernel_ipsec_t interface + */ + kernel_ipsec_t interface; +}; + +/** + * Create a android ipsec interface instance. + * + * @return kernel_android_ipsec_t instance + */ +kernel_android_ipsec_t *kernel_android_ipsec_create(); + +#endif /** KERNEL_ANDROID_IPSEC_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c new file mode 100644 index 000000000..e29f95510 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c @@ -0,0 +1,64 @@ +/* + * 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 "android_net.h" + +typedef struct private_kernel_android_net_t private_kernel_android_net_t; + +struct private_kernel_android_net_t { + + /** + * Public kernel interface + */ + kernel_android_net_t public; +}; + +METHOD(kernel_net_t, add_ip, status_t, + private_kernel_android_net_t *this, host_t *virtual_ip, host_t *iface_ip) +{ + /* we get the IP from the IKE_SA once the CHILD_SA is established */ + return SUCCESS; +} + +METHOD(kernel_net_t, destroy, void, + private_kernel_android_net_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +kernel_android_net_t *kernel_android_net_create() +{ + private_kernel_android_net_t *this; + + INIT(this, + .public = { + .interface = { + .get_source_addr = (void*)return_null, + .get_nexthop = (void*)return_null, + .get_interface = (void*)return_null, + .create_address_enumerator = (void*)enumerator_create_empty, + .add_ip = _add_ip, + .del_ip = (void*)return_failed, + .add_route = (void*)return_failed, + .del_route = (void*)return_failed, + .destroy = _destroy, + }, + }, + ); + + return &this->public; +}; diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h new file mode 100644 index 000000000..470029fad --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h @@ -0,0 +1,49 @@ +/* + * 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 kernel_android_net kernel_android_net + * @{ @ingroup kernel_android + */ + +#ifndef KERNEL_ANDROID_NET_H_ +#define KERNEL_ANDROID_NET_H_ + +#include <library.h> +#include <kernel/kernel_net.h> + +typedef struct kernel_android_net_t kernel_android_net_t; + +/** + * Implementation of the kernel-net interface. This currently consists of only + * noops because a kernel_net_t implementation is required and we can't use + * kernel_netlink_net_t at the moment. + */ +struct kernel_android_net_t { + + /** + * Implements kernel_net_t interface + */ + kernel_net_t interface; +}; + +/** + * Create a android net interface instance. + * + * @return kernel_android_net_t instance + */ +kernel_android_net_t *kernel_android_net_create(); + +#endif /** KERNEL_ANDROID_NET_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c new file mode 100644 index 000000000..6ff732520 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "vpnservice_builder.h" +#include "android_jni.h" + +#include <debug.h> +#include <library.h> + +typedef struct private_vpnservice_builder_t private_vpnservice_builder_t; + +/** + * private data of vpnservice_builder + */ +struct private_vpnservice_builder_t { + + /** + * public interface + */ + vpnservice_builder_t public; + + /** + * Java object + */ + jobject builder; +}; + +METHOD(vpnservice_builder_t, add_address, bool, + private_vpnservice_builder_t *this, host_t *addr) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding interface address %H", addr); + + if (addr->get_family(addr) != AF_INET) + { + goto failed; + } + if (snprintf(buf, sizeof(buf), "%H", addr) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addAddress", "(Ljava/lang/String;I)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, 32)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add address"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, set_mtu, bool, + private_vpnservice_builder_t *this, int mtu) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: setting MTU to %d", mtu); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "setMtu", "(I)Z"); + if (!method_id) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, mtu)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to set MTU"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, add_route, bool, + private_vpnservice_builder_t *this, host_t *net, int prefix) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding route %+H/%d", net, prefix); + + if (net->get_family(net) != AF_INET) + { + goto failed; + } + if (snprintf(buf, sizeof(buf), "%+H", net) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addRoute", "(Ljava/lang/String;I)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add route"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, add_dns, bool, + private_vpnservice_builder_t *this, host_t *dns) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding DNS server %H", dns); + + if (dns->get_family(dns) != AF_INET) + { + goto failed; + } + if (snprintf(buf, sizeof(buf), "%H", dns) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addDnsServer", "(Ljava/lang/String;)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add DNS server"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, establish, int, + private_vpnservice_builder_t *this) +{ + JNIEnv *env; + jmethodID method_id; + int fd; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: building TUN device"); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "establish", "()I"); + if (!method_id) + { + goto failed; + } + fd = (*env)->CallIntMethod(env, this->builder, method_id); + if (fd == -1) + { + goto failed; + } + androidjni_detach_thread(); + return fd; + +failed: + DBG1(DBG_LIB, "builder: failed to build TUN device"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return -1; +} + +METHOD(vpnservice_builder_t, destroy, void, + private_vpnservice_builder_t *this) +{ + JNIEnv *env; + + androidjni_attach_thread(&env); + (*env)->DeleteGlobalRef(env, this->builder); + androidjni_detach_thread(); + free(this); +} + +vpnservice_builder_t *vpnservice_builder_create(jobject builder) +{ + JNIEnv *env; + private_vpnservice_builder_t *this; + + INIT(this, + .public = { + .add_address = _add_address, + .add_route = _add_route, + .add_dns = _add_dns, + .set_mtu = _set_mtu, + .establish = _establish, + .destroy = _destroy, + }, + ); + + androidjni_attach_thread(&env); + this->builder = (*env)->NewGlobalRef(env, builder); + androidjni_detach_thread(); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h new file mode 100644 index 000000000..82efd05f7 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 vpnservice_builder vpnservice_builder + * @{ @ingroup libandroidbridge + */ + +#ifndef VPNSERVICE_BUILDER_H_ +#define VPNSERVICE_BUILDER_H_ + +#include <jni.h> + +#include <library.h> +#include <utils/host.h> + +typedef struct vpnservice_builder_t vpnservice_builder_t; + +/** + * VpnService.Builder, used to build a TUN device. + * + * Communicates with CharonVpnService.BuilderAdapter via JNI + */ +struct vpnservice_builder_t { + + /** + * Add an interface address + * + * @param addr the desired interface address + * @return TRUE on success + */ + bool (*add_address)(vpnservice_builder_t *this, host_t *addr); + + /** + * Add a route + * + * @param net the network address + * @param prefix_length the prefix length + * @return TRUE on success + */ + bool (*add_route)(vpnservice_builder_t *this, host_t *net, int prefix); + + /** + * Add a DNS server + * + * @param dns the address of the DNS server + * @return TRUE on success + */ + bool (*add_dns)(vpnservice_builder_t *this, host_t *dns); + + /** + * Set the MTU for the TUN device + * + * @param mtu the MTU to set + * @return TRUE on success + */ + bool (*set_mtu)(vpnservice_builder_t *this, int mtu); + + /** + * Build the TUN device + * + * @return the TUN file descriptor, -1 if failed + */ + int (*establish)(vpnservice_builder_t *this); + + /** + * Destroy a vpnservice_builder + */ + void (*destroy)(vpnservice_builder_t *this); + +}; + +/** + * Create a vpnservice_builder instance + * + * @param builder CharonVpnService.BuilderAdapter object + * @return vpnservice_builder_t instance + */ +vpnservice_builder_t *vpnservice_builder_create(jobject builder); + +#endif /** VPNSERVICE_BUILDER_H_ @}*/ diff --git a/src/frontends/android/res/drawable-hdpi/ic_launcher.png b/src/frontends/android/res/drawable-hdpi/ic_launcher.png Binary files differindex 8074c4c57..7cd1df4ee 100644 --- a/src/frontends/android/res/drawable-hdpi/ic_launcher.png +++ b/src/frontends/android/res/drawable-hdpi/ic_launcher.png diff --git a/src/frontends/android/res/drawable-ldpi/ic_launcher.png b/src/frontends/android/res/drawable-ldpi/ic_launcher.png Binary files differdeleted file mode 100644 index 1095584ec..000000000 --- a/src/frontends/android/res/drawable-ldpi/ic_launcher.png +++ /dev/null diff --git a/src/frontends/android/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/res/drawable-mdpi/ic_launcher.png Binary files differindex a07c69fa5..200ee9677 100644 --- a/src/frontends/android/res/drawable-mdpi/ic_launcher.png +++ b/src/frontends/android/res/drawable-mdpi/ic_launcher.png diff --git a/src/frontends/android/res/drawable-xhdpi/ic_launcher.png b/src/frontends/android/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000..2eb6db1b6 --- /dev/null +++ b/src/frontends/android/res/drawable-xhdpi/ic_launcher.png diff --git a/src/frontends/android/res/drawable/vpn_state_background.xml b/src/frontends/android/res/drawable/vpn_state_background.xml new file mode 100644 index 000000000..24f469add --- /dev/null +++ b/src/frontends/android/res/drawable/vpn_state_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + + <solid + android:color="#333" /> + +</shape>
\ No newline at end of file diff --git a/src/frontends/android/res/layout/log_activity.xml b/src/frontends/android/res/layout/log_activity.xml new file mode 100644 index 000000000..80fee09fb --- /dev/null +++ b/src/frontends/android/res/layout/log_activity.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <fragment + class="org.strongswan.android.ui.LogFragment" + android:id="@+id/log_frag" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</FrameLayout> diff --git a/src/frontends/android/res/layout/log_fragment.xml b/src/frontends/android/res/layout/log_fragment.xml new file mode 100644 index 000000000..c2e187a66 --- /dev/null +++ b/src/frontends/android/res/layout/log_fragment.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <org.strongswan.android.ui.LogScrollView + android:id="@+id/scroll_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="10dp" + android:scrollbarFadeDuration="0" + android:scrollbarAlwaysDrawVerticalTrack="true" > + + <TextView + android:id="@+id/log_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="9sp" + android:typeface="monospace" > + </TextView> + + </org.strongswan.android.ui.LogScrollView> + +</LinearLayout> diff --git a/src/frontends/android/res/layout/login_dialog.xml b/src/frontends/android/res/layout/login_dialog.xml new file mode 100644 index 000000000..0262af0a3 --- /dev/null +++ b/src/frontends/android/res/layout/login_dialog.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical" + android:padding="5dp" + xmlns:android="http://schemas.android.com/apk/res/android"> + + <TextView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/profile_username_label" + android:textStyle="bold" /> + + <EditText + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:id="@+id/username" + android:enabled="false" + android:inputType="none" /> + + <TextView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/profile_password_label" + android:textStyle="bold" /> + + <EditText + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:id="@+id/password" + android:inputType="textPassword|textNoSuggestions" + android:singleLine="true" /> + +</LinearLayout> diff --git a/src/frontends/android/res/layout/main.xml b/src/frontends/android/res/layout/main.xml index bc12cd823..1c7973e20 100644 --- a/src/frontends/android/res/layout/main.xml +++ b/src/frontends/android/res/layout/main.xml @@ -1,12 +1,34 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" android:orientation="vertical" > - <TextView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:text="@string/hello" /> + <fragment + class="org.strongswan.android.ui.VpnStateFragment" + android:id="@+id/vpn_state_frag" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + class="org.strongswan.android.ui.VpnProfileListFragment" + android:id="@+id/profile_list_frag" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml new file mode 100644 index 000000000..4952ebaa5 --- /dev/null +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="10dp" > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_name_label" /> + + <EditText + android:id="@+id/name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="textNoSuggestions" + android:hint="@string/profile_name_hint" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_gateway_label" /> + + <EditText + android:id="@+id/gateway" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="textNoSuggestions" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_username_label" /> + + <EditText + android:id="@+id/username" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="textNoSuggestions" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_password_label" /> + + <EditText + android:id="@+id/password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="textPassword|textNoSuggestions" + android:hint="@string/profile_password_hint" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_ca_label" /> + + <CheckBox + android:id="@+id/ca_auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_ca_auto_label" /> + + <CheckBox + android:id="@+id/ca_show_all" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:text="@string/profile_ca_show_all" /> + + <Spinner + android:id="@+id/ca_spinner" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="5dp" /> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/src/frontends/android/res/layout/profile_list_fragment.xml b/src/frontends/android/res/layout/profile_list_fragment.xml new file mode 100644 index 000000000..50d628bfa --- /dev/null +++ b/src/frontends/android/res/layout/profile_list_fragment.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="10dp" + android:paddingTop="10dp" + android:paddingLeft="5dp" + android:paddingRight="5dp" > + + <ListView + android:id="@+id/profile_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:dividerHeight="1dp" + android:divider="?android:attr/listDivider" + android:scrollbarAlwaysDrawVerticalTrack="true" /> + + <TextView android:id="@+id/profile_list_empty" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginLeft="15dp" + android:text="@string/no_profiles"/> + +</FrameLayout> diff --git a/src/frontends/android/res/layout/profile_list_item.xml b/src/frontends/android/res/layout/profile_list_item.xml new file mode 100644 index 000000000..f55c8357a --- /dev/null +++ b/src/frontends/android/res/layout/profile_list_item.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="6dip" + android:paddingTop="4dip" + android:background="?android:attr/activatedBackgroundIndicator" > + + <TextView + android:id="@+id/profile_item_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="15dp" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@+id/profile_item_gateway" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:textColorSecondary" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_marginLeft="15dp" /> + + <TextView + android:id="@+id/profile_item_username" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:textColorSecondary" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_marginLeft="15dp" /> + +</LinearLayout> diff --git a/src/frontends/android/res/layout/trusted_certificates_item.xml b/src/frontends/android/res/layout/trusted_certificates_item.xml new file mode 100644 index 000000000..48d77757d --- /dev/null +++ b/src/frontends/android/res/layout/trusted_certificates_item.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <TextView + android:id="@+id/certificate_name" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:textSize="18sp" /> + +</TableLayout>
\ No newline at end of file diff --git a/src/frontends/android/res/layout/vpn_state_fragment.xml b/src/frontends/android/res/layout/vpn_state_fragment.xml new file mode 100644 index 000000000..6353f3289 --- /dev/null +++ b/src/frontends/android/res/layout/vpn_state_fragment.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="5dp" + android:background="@drawable/vpn_state_background" + android:orientation="vertical" > + + <GridLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:layout_marginLeft="20dp" + android:layout_marginRight="20dp" + android:layout_marginTop="10dp" + android:columnCount="2" + android:rowCount="2" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="5dp" + android:gravity="top" + android:text="@string/state_label" + android:textColor="?android:textColorPrimary" + android:textSize="20sp" /> + + <TextView + android:id="@+id/vpn_state" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="top" + android:text="@string/state_disabled" + android:textColor="?android:textColorSecondary" + android:textSize="20sp" /> + + <TextView + android:id="@+id/vpn_profile_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="5dp" + android:gravity="top" + android:text="@string/profile_label" + android:textColor="?android:textColorPrimary" + android:textSize="20sp" + android:visibility="gone" > + </TextView> + + <TextView + android:id="@+id/vpn_profile_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="top" + android:textSize="20sp" + android:visibility="gone" > + </TextView> + </GridLayout> + + <Button + android:id="@+id/action" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:layout_marginLeft="20dp" + android:layout_marginRight="20dp" + android:text="@string/disconnect" + style="?android:attr/borderlessButtonStyle" > + </Button> + + <View + android:layout_width="match_parent" + android:layout_height="2dp" + android:background="?android:attr/listDivider" /> + +</LinearLayout> diff --git a/src/frontends/android/res/menu/log.xml b/src/frontends/android/res/menu/log.xml new file mode 100644 index 000000000..1af5bd397 --- /dev/null +++ b/src/frontends/android/res/menu/log.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/menu_send_log" + android:title="@string/send_log" + android:showAsAction="ifRoom|withText" /> + +</menu> diff --git a/src/frontends/android/res/menu/main.xml b/src/frontends/android/res/menu/main.xml new file mode 100644 index 000000000..4063110da --- /dev/null +++ b/src/frontends/android/res/menu/main.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item + android:id="@+id/menu_reload_certs" + android:title="@string/reload_trusted_certs" + android:showAsAction="withText" /> + + <item + android:id="@+id/menu_show_log" + android:title="@string/show_log" + android:showAsAction="withText" /> + +</menu>
\ No newline at end of file diff --git a/src/frontends/android/res/menu/profile_edit.xml b/src/frontends/android/res/menu/profile_edit.xml new file mode 100644 index 000000000..e69020ed0 --- /dev/null +++ b/src/frontends/android/res/menu/profile_edit.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item + android:id="@+id/menu_accept" + android:title="@string/profile_edit_save" + android:showAsAction="always|withText" /> + + <item + android:id="@+id/menu_cancel" + android:title="@string/profile_edit_cancel" + android:showAsAction="ifRoom" /> + +</menu> diff --git a/src/frontends/android/res/menu/profile_list.xml b/src/frontends/android/res/menu/profile_list.xml new file mode 100644 index 000000000..57c9a86a4 --- /dev/null +++ b/src/frontends/android/res/menu/profile_list.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:id="@+id/add_profile" + android:title="@string/add_profile" + android:showAsAction="always|withText" /> + +</menu> diff --git a/src/frontends/android/res/menu/profile_list_context.xml b/src/frontends/android/res/menu/profile_list_context.xml new file mode 100644 index 000000000..e674ae856 --- /dev/null +++ b/src/frontends/android/res/menu/profile_list_context.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:id="@+id/edit_profile" + android:title="@string/edit_profile" ></item> + + <item android:id="@+id/delete_profile" + android:title="@string/delete_profile" ></item> + +</menu>
\ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml new file mode 100644 index 000000000..0e0ddd583 --- /dev/null +++ b/src/frontends/android/res/values-de/strings.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> +<resources> + + <!-- Application --> + <string name="app_name">strongSwan VPN Client</string> + <string name="main_activity_name">strongSwan</string> + <string name="reload_trusted_certs">CA-Zertifikate neu laden</string> + <string name="show_log">Log anzeigen</string> + + <!-- Log view --> + <string name="log_title">Log</string> + <string name="send_log">Logdatei senden</string> + <string name="empty_log">Logdatei ist leer</string> + <string name="log_mail_subject">strongSwan %1$s Logdatei</string> + + <!-- VPN profile list --> + <string name="no_profiles">Keine VPN Profile vorhanden.</string> + <string name="add_profile">Profil hinzufügen</string> + <string name="edit_profile">Bearbeiten</string> + <string name="delete_profile">Löschen</string> + <string name="select_profiles">Profile auswählen</string> + <string name="profiles_deleted">Ausgewählte Profile gelöscht</string> + <string name="no_profile_selected">Kein Profil ausgewählt</string> + <string name="one_profile_selected">Ein Profil ausgewählt</string> + <string name="x_profiles_selected">%1$d Profile ausgewählt</string> + + <!-- VPN profile details --> + <string name="profile_edit_save">Speichern</string> + <string name="profile_edit_cancel">Abbrechen</string> + <string name="profile_name_label">Profilname:</string> + <string name="profile_name_hint">(Gateway-Adresse verwenden)</string> + <string name="profile_gateway_label">Gateway:</string> + <string name="profile_username_label">Benutzername:</string> + <string name="profile_password_label">Passwort:</string> + <string name="profile_password_hint">(anfordern wenn benötigt)</string> + <string name="profile_ca_label">CA-Zertifikat:</string> + <string name="profile_ca_auto_label">Automatisch wählen</string> + <string name="profile_ca_show_all">Alle Zertifikate anzeigen</string> + <!-- Warnings/Notifications in the details view --> + <string name="alert_text_no_input_gateway">Bitte geben Sie hier die Gateway-Adresse ein</string> + <string name="alert_text_no_input_username">Bitte geben Sie hier Ihren Benutzernamen ein</string> + <string name="alert_text_nocertfound_title">Kein CA-Zertifikat ausgewählt</string> + <string name="alert_text_nocertfound">Bitte wählen Sie eines aus oder aktivieren Sie <i>Automatisch wählen</i></string> + + <!-- VPN state fragment --> + <string name="state_label">Status:</string> + <string name="profile_label">Profil:</string> + <string name="disconnect">Trennen</string> + <string name="state_connecting">Verbinden…</string> + <string name="state_connected">Verbunden</string> + <string name="state_disconnecting">Trennen…</string> + <string name="state_disabled">Kein aktives Profil</string> + <string name="state_error">Fehler</string> + + <!-- Dialogs --> + <string name="login_title">Passwort eingeben um zu verbinden</string> + <string name="login_confirm">Verbinden</string> + <string name="error_introduction">Fehler beim Aufsetzen des VPN:</string> + <string name="error_lookup_failed">Gateway-Adresse konnte nicht aufgelöst werden.</string> + <string name="error_unreachable">Gateway ist nicht erreichbar.</string> + <string name="error_peer_auth_failed">Authentifizierung des Gateway ist fehlgeschlagen.</string> + <string name="error_auth_failed">Benutzerauthentifizierung ist fehlgeschlagen.</string> + <string name="error_generic">Unbekannter Fehler während des Verbindens.</string> + <string name="connecting_title">Verbinden: %1$s</string> + <string name="connecting_message">Verbinde mit \""%1$s\".</string> + +</resources> diff --git a/src/frontends/android/res/values/colors.xml b/src/frontends/android/res/values/colors.xml new file mode 100644 index 000000000..be64d5d5a --- /dev/null +++ b/src/frontends/android/res/values/colors.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<resources> + + <color + name="error_text">#D9192C</color> + + <color + name="success_text">#99CC00</color> + +</resources> diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index f4df7613e..a83e219a7 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -1,7 +1,83 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012 Giuliano Grassi + Copyright (C) 2012 Ralf Sager + 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. +--> <resources> - <string name="hello">Hello World, strongSwanActivity!</string> - <string name="app_name">strongSwan</string> + <!-- Application --> + <string name="app_name">strongSwan VPN Client</string> + <string name="main_activity_name">strongSwan</string> + <string name="reload_trusted_certs">Reload CA certificates</string> + <string name="show_log">View log</string> + + <!-- Log view --> + <string name="log_title">Log</string> + <string name="send_log">Send log file</string> + <string name="empty_log">Log file is empty</string> + <string name="log_mail_subject">strongSwan %1$s Log File</string> + + <!-- VPN profile list --> + <string name="no_profiles">No VPN profiles.</string> + <string name="add_profile">Add VPN profile</string> + <string name="edit_profile">Edit</string> + <string name="delete_profile">Delete</string> + <string name="select_profiles">Select profiles</string> + <string name="profiles_deleted">Selected profiles deleted</string> + <string name="no_profile_selected">No profile selected</string> + <string name="one_profile_selected">One profile selected</string> + <string name="x_profiles_selected">%1$d profiles selected"</string> + + <!-- VPN profile details --> + <string name="profile_edit_save">Save</string> + <string name="profile_edit_cancel">Cancel</string> + <string name="profile_name_label">Profile Name:</string> + <string name="profile_name_hint">(use gateway address)</string> + <string name="profile_gateway_label">Gateway:</string> + <string name="profile_username_label">Username:</string> + <string name="profile_password_label">Password:</string> + <string name="profile_password_hint">(prompt when needed)</string> + <string name="profile_ca_label">CA certificate:</string> + <string name="profile_ca_auto_label">Select automatically</string> + <string name="profile_ca_show_all">Show all certificates</string> + <!-- Warnings/Notifications in the details view --> + <string name="alert_text_no_input_gateway">Please enter the gateway address here</string> + <string name="alert_text_no_input_username">Please enter your username here</string> + <string name="alert_text_nocertfound_title">No CA certificate selected</string> + <string name="alert_text_nocertfound">Please select one or activate <i>Select automatically</i></string> + + <!-- VPN state fragment --> + <string name="state_label">Status:</string> + <string name="profile_label">Profile:</string> + <string name="disconnect">Disconnect</string> + <string name="state_connecting">Connecting…</string> + <string name="state_connected">Connected</string> + <string name="state_disconnecting">Disconnecting…</string> + <string name="state_disabled">No active VPN</string> + <string name="state_error">Error</string> + + <!-- Dialogs --> + <string name="login_title">Enter password to connect</string> + <string name="login_confirm">Connect</string> + <string name="error_introduction">Failed to establish VPN:</string> + <string name="error_lookup_failed">Gateway address lookup failed.</string> + <string name="error_unreachable">Gateway is unreachable.</string> + <string name="error_peer_auth_failed">Verifying gateway authentication failed.</string> + <string name="error_auth_failed">User authentication failed.</string> + <string name="error_generic">Unspecified failure while connecting.</string> + <string name="connecting_title">Connecting: %1$s</string> + <string name="connecting_message">Establishing VPN with \""%1$s\".</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/frontends/android/res/values/styles.xml b/src/frontends/android/res/values/styles.xml new file mode 100644 index 000000000..739ba7000 --- /dev/null +++ b/src/frontends/android/res/values/styles.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="ApplicationTheme" parent="@android:style/Theme.Holo"> + </style> + +</resources> diff --git a/src/frontends/android/src/org/strongswan/android/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/CharonVpnService.java deleted file mode 100644 index d917d3eae..000000000 --- a/src/frontends/android/src/org/strongswan/android/CharonVpnService.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.strongswan.android; - -import android.content.Intent; -import android.net.VpnService; - -public class CharonVpnService extends VpnService -{ - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - // called whenever the service is started with startService - // create our own thread because we are running in the calling processes - // main thread - return super.onStartCommand(intent, flags, startId); - } - - @Override - public void onCreate() - { - // onCreate is only called once - initializeCharon(); - super.onCreate(); - } - - @Override - public void onDestroy() - { - // called once the service is to be destroyed - deinitializeCharon(); - super.onDestroy(); - } - - /** - * Initialization of charon, provided by libandroidbridge.so - */ - public native void initializeCharon(); - - /** - * Deinitialize charon, provided by libandroidbridge.so - */ - public native void deinitializeCharon(); - - /* - * The libraries are extracted to /data/data/org.strongswan.android/... - * during installation. - */ - static - { - System.loadLibrary("crypto"); - System.loadLibrary("strongswan"); - System.loadLibrary("hydra"); - System.loadLibrary("charon"); - System.loadLibrary("ipsec"); - System.loadLibrary("androidbridge"); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java new file mode 100644 index 000000000..370a8d5e4 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java @@ -0,0 +1,149 @@ +/* + * 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.data; + +import java.io.File; +import java.io.FileNotFoundException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.ConcurrentHashMap; + +import org.strongswan.android.logic.CharonVpnService; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.provider.OpenableColumns; + +public class LogContentProvider extends ContentProvider +{ + private static final String AUTHORITY = "org.strongswan.android.content.log"; + /* an Uri is valid for 30 minutes */ + private static final long URI_VALIDITY = 30 * 60 * 1000; + private static ConcurrentHashMap<Uri, Long> mUris = new ConcurrentHashMap<Uri, Long>(); + private File mLogFile; + + public LogContentProvider() + { + } + + @Override + public boolean onCreate() + { + mLogFile = new File(getContext().getFilesDir(), CharonVpnService.LOG_FILE); + return true; + } + + /** + * The log file can only be accessed by Uris created with this method + * @return null if failed to create the Uri + */ + public static Uri createContentUri() + { + SecureRandom random; + try + { + random = SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + return null; + } + Uri uri = Uri.parse("content://" + AUTHORITY + "/" + random.nextLong()); + mUris.put(uri, SystemClock.uptimeMillis()); + return uri; + } + + @Override + public String getType(Uri uri) + { + /* MIME type for our log file */ + return "text/plain"; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) + { + /* this is called by apps to find out the name and size of the file. + * since we only provide a single file this is simple to implement */ + if (projection == null || projection.length < 1) + { + return null; + } + Long timestamp = mUris.get(uri); + if (timestamp == null) + { /* don't check the validity as this information is not really private */ + return null; + } + MatrixCursor cursor = new MatrixCursor(projection, 1); + if (OpenableColumns.DISPLAY_NAME.equals(cursor.getColumnName(0))) + { + cursor.newRow().add(CharonVpnService.LOG_FILE); + } + else if (OpenableColumns.SIZE.equals(cursor.getColumnName(0))) + { + cursor.newRow().add(mLogFile.length()); + } + else + { + return null; + } + return cursor; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException + { + Long timestamp = mUris.get(uri); + if (timestamp != null) + { + long elapsed = SystemClock.uptimeMillis() - timestamp; + if (elapsed > 0 && elapsed < URI_VALIDITY) + { /* we fail if clock wrapped, should happen rarely though */ + return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY); + } + mUris.remove(uri); + } + return super.openFile(uri, mode); + } + + @Override + public Uri insert(Uri uri, ContentValues values) + { + /* not supported */ + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) + { + /* not supported */ + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) + { + /* not supported */ + return 0; + } +} diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java new file mode 100644 index 000000000..053f91555 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.data; + +public class VpnProfile implements Cloneable +{ + private String mName, mGateway, mUsername, mPassword, mCertificate; + private long mId = -1; + + public long getId() + { + return mId; + } + + public void setId(long id) + { + this.mId = id; + } + + public String getName() + { + return mName; + } + + public void setName(String name) + { + this.mName = name; + } + + public String getGateway() + { + return mGateway; + } + + public void setGateway(String gateway) + { + this.mGateway = gateway; + } + + public String getUsername() + { + return mUsername; + } + + public void setUsername(String username) + { + this.mUsername = username; + } + + public String getPassword() + { + return mPassword; + } + + public void setPassword(String password) + { + this.mPassword = password; + } + + public String getCertificateAlias() + { + return mCertificate; + } + + public void setCertificateAlias(String certificate) + { + this.mCertificate = certificate; + } + + @Override + public String toString() + { + return mName; + } + + @Override + public boolean equals(Object o) + { + if (o != null && o instanceof VpnProfile) + { + return this.mId == ((VpnProfile)o).getId(); + } + return false; + } + + @Override + public VpnProfile clone() + { + try + { + return (VpnProfile)super.clone(); + } + catch (CloneNotSupportedException e) + { + throw new AssertionError(); + } + } +} diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java new file mode 100644 index 000000000..18632ad6f --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.data; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class VpnProfileDataSource +{ + private static final String TAG = VpnProfileDataSource.class.getSimpleName(); + public static final String KEY_ID = "_id"; + public static final String KEY_NAME = "name"; + public static final String KEY_GATEWAY = "gateway"; + public static final String KEY_USERNAME = "username"; + public static final String KEY_PASSWORD = "password"; + public static final String KEY_CERTIFICATE = "certificate"; + + private DatabaseHelper mDbHelper; + private SQLiteDatabase mDatabase; + private final Context mContext; + + private static final String DATABASE_NAME = "strongswan.db"; + private static final String TABLE_VPNPROFILE = "vpnprofile"; + + private static final int DATABASE_VERSION = 1; + + public static final String DATABASE_CREATE = + "CREATE TABLE " + TABLE_VPNPROFILE + " (" + + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + KEY_NAME + " TEXT NOT NULL," + + KEY_GATEWAY + " TEXT NOT NULL," + + KEY_USERNAME + " TEXT NOT NULL," + + KEY_PASSWORD + " TEXT," + + KEY_CERTIFICATE + " TEXT" + + ");"; + private final String[] ALL_COLUMNS = new String[] { + KEY_ID, + KEY_NAME, + KEY_GATEWAY, + KEY_USERNAME, + KEY_PASSWORD, + KEY_CERTIFICATE + }; + + private static class DatabaseHelper extends SQLiteOpenHelper + { + public DatabaseHelper(Context context) + { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase database) + { + database.execSQL(DATABASE_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + Log.w(TAG, "Upgrading database from version " + oldVersion + + " to " + newVersion + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_VPNPROFILE); + onCreate(db); + } + } + + /** + * Construct a new VPN profile data source. The context is used to + * open/create the database. + * @param context context used to access the database + */ + public VpnProfileDataSource(Context context) + { + this.mContext = context; + } + + /** + * Open the VPN profile data source. The database is automatically created + * if it does not yet exist. If that fails an exception is thrown. + * @return itself (allows to chain initialization calls) + * @throws SQLException if the database could not be opened or created + */ + public VpnProfileDataSource open() throws SQLException + { + if (mDbHelper == null) + { + mDbHelper = new DatabaseHelper(mContext); + mDatabase = mDbHelper.getWritableDatabase(); + } + return this; + } + + /** + * Close the data source. + */ + public void close() + { + if (mDbHelper != null) + { + mDbHelper.close(); + mDbHelper = null; + } + } + + /** + * Insert the given VPN profile into the database. On success the Id of + * the object is updated and the object returned. + * + * @param profile the profile to add + * @return the added VPN profile or null, if failed + */ + public VpnProfile insertProfile(VpnProfile profile) + { + ContentValues values = ContentValuesFromVpnProfile(profile); + long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values); + if (insertId == -1) + { + return null; + } + profile.setId(insertId); + return profile; + } + + /** + * Updates the given VPN profile in the database. + * @param profile the profile to update + * @return true if update succeeded, false otherwise + */ + public boolean updateVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + ContentValues values = ContentValuesFromVpnProfile(profile); + return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; + } + + /** + * Delete the given VPN profile from the database. + * @param profile the profile to delete + * @return true if deleted, false otherwise + */ + public boolean deleteVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; + } + + /** + * Get a single VPN profile from the database. + * @param id the ID of the VPN profile + * @return the profile or null, if not found + */ + public VpnProfile getVpnProfile(long id) + { + VpnProfile profile = null; + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, + KEY_ID + "=" + id, null, null, null, null); + if (cursor.moveToFirst()) + { + profile = VpnProfileFromCursor(cursor); + } + cursor.close(); + return profile; + } + + /** + * Get a list of all VPN profiles stored in the database. + * @return list of VPN profiles + */ + public List<VpnProfile> getAllVpnProfiles() + { + List<VpnProfile> vpnProfiles = new ArrayList<VpnProfile>(); + + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + VpnProfile vpnProfile = VpnProfileFromCursor(cursor); + vpnProfiles.add(vpnProfile); + cursor.moveToNext(); + } + cursor.close(); + return vpnProfiles; + } + + private VpnProfile VpnProfileFromCursor(Cursor cursor) + { + VpnProfile profile = new VpnProfile(); + profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID))); + profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME))); + profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY))); + profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME))); + profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); + profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); + return profile; + } + + private ContentValues ContentValuesFromVpnProfile(VpnProfile profile) + { + ContentValues values = new ContentValues(); + values.put(KEY_NAME, profile.getName()); + values.put(KEY_GATEWAY, profile.getGateway()); + values.put(KEY_USERNAME, profile.getUsername()); + values.put(KEY_PASSWORD, profile.getPassword()); + values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); + return values; + } +} diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java new file mode 100644 index 000000000..c9c1ad02a --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -0,0 +1,595 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.io.File; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +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; +import org.strongswan.android.logic.VpnStateService.ErrorState; +import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.ui.MainActivity; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.VpnService; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +public class CharonVpnService extends VpnService implements Runnable +{ + private static final String TAG = CharonVpnService.class.getSimpleName(); + public static final String LOG_FILE = "charon.log"; + + private String mLogFile; + private VpnProfileDataSource mDataSource; + private Thread mConnectionHandler; + private VpnProfile mCurrentProfile; + private volatile String mCurrentCertificateAlias; + private VpnProfile mNextProfile; + private volatile boolean mProfileUpdated; + private volatile boolean mTerminate; + private volatile boolean mIsDisconnecting; + private VpnStateService mService; + private final Object mServiceLock = new Object(); + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { /* since the service is local this is theoretically only called when the process is terminated */ + synchronized (mServiceLock) + { + mService = null; + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + synchronized (mServiceLock) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + } + /* we are now ready to start the handler thread */ + mConnectionHandler.start(); + } + }; + + /** + * as defined in charonservice.h + */ + static final int STATE_CHILD_SA_UP = 1; + static final int STATE_CHILD_SA_DOWN = 2; + static final int STATE_AUTH_ERROR = 3; + static final int STATE_PEER_AUTH_ERROR = 4; + static final int STATE_LOOKUP_ERROR = 5; + static final int STATE_UNREACHABLE_ERROR = 6; + static final int STATE_GENERIC_ERROR = 7; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) + { + if (intent != null) + { + Bundle bundle = intent.getExtras(); + VpnProfile profile = null; + if (bundle != null) + { + profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID)); + if (profile != null) + { + String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD); + profile.setPassword(password); + } + } + setNextProfile(profile); + } + return START_NOT_STICKY; + } + + @Override + public void onCreate() + { + mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE; + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + /* use a separate thread as main thread for charon */ + mConnectionHandler = new Thread(this); + /* the thread is started when the service is bound */ + bindService(new Intent(this, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + } + + @Override + public void onRevoke() + { /* the system revoked the rights grated with the initial prepare() call. + * called when the user clicks disconnect in the system's VPN dialog */ + setNextProfile(null); + } + + @Override + public void onDestroy() + { + mTerminate = true; + setNextProfile(null); + try + { + mConnectionHandler.join(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + if (mService != null) + { + unbindService(mServiceConnection); + } + mDataSource.close(); + } + + /** + * Set the profile that is to be initiated next. Notify the handler thread. + * + * @param profile the profile to initiate + */ + private void setNextProfile(VpnProfile profile) + { + synchronized (this) + { + this.mNextProfile = profile; + mProfileUpdated = true; + notifyAll(); + } + } + + @Override + public void run() + { + while (true) + { + synchronized (this) + { + try + { + while (!mProfileUpdated) + { + wait(); + } + + mProfileUpdated = false; + stopCurrentConnection(); + if (mNextProfile == null) + { + setProfile(null); + setState(State.DISABLED); + if (mTerminate) + { + break; + } + } + else + { + mCurrentProfile = mNextProfile; + mNextProfile = null; + + /* store this in a separate (volatile) variable to avoid + * a possible deadlock during deinitialization */ + mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); + + setProfile(mCurrentProfile); + setError(ErrorState.NO_ERROR); + setState(State.CONNECTING); + mIsDisconnecting = false; + + BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName()); + initializeCharon(builder, mLogFile); + Log.i(TAG, "charon started"); + + String local_address = getLocalIPv4Address(); + initiate(local_address != null ? local_address : "0.0.0.0", + mCurrentProfile.getGateway(), mCurrentProfile.getUsername(), + mCurrentProfile.getPassword()); + } + } + catch (InterruptedException ex) + { + stopCurrentConnection(); + setState(State.DISABLED); + } + } + } + } + + /** + * Stop any existing connection by deinitializing charon. + */ + private void stopCurrentConnection() + { + synchronized (this) + { + if (mCurrentProfile != null) + { + setState(State.DISCONNECTING); + mIsDisconnecting = true; + deinitializeCharon(); + Log.i(TAG, "charon stopped"); + mCurrentProfile = null; + } + } + } + + /** + * Update the VPN profile on the state service. Called by the handler thread. + * + * @param profile currently active VPN profile + */ + private void setProfile(VpnProfile profile) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setProfile(profile); + } + } + } + + /** + * Update the current VPN state on the state service. Called by the handler + * thread and any of charon's threads. + * + * @param state current state + */ + private void setState(State state) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setState(state); + } + } + } + + /** + * Set an error on the state service. Called by the handler thread and any + * of charon's threads. + * + * @param error error state + */ + private void setError(ErrorState error) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setError(error); + } + } + } + + /** + * Set an error on the state service and disconnect the current connection. + * This is not done by calling stopCurrentConnection() above, but instead + * is done asynchronously via state service. + * + * @param error error state + */ + private void setErrorDisconnect(ErrorState error) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setError(error); + if (!mIsDisconnecting) + { + mService.disconnect(); + } + } + } + } + + /** + * Updates the state of the current connection. + * Called via JNI by different threads (but not concurrently). + * + * @param status new state + */ + public void updateStatus(int status) + { + 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(); + } + } + break; + case STATE_CHILD_SA_UP: + setState(State.CONNECTED); + break; + case STATE_AUTH_ERROR: + setErrorDisconnect(ErrorState.AUTH_FAILED); + break; + case STATE_PEER_AUTH_ERROR: + setErrorDisconnect(ErrorState.PEER_AUTH_FAILED); + break; + case STATE_LOOKUP_ERROR: + setErrorDisconnect(ErrorState.LOOKUP_FAILED); + break; + case STATE_UNREACHABLE_ERROR: + setErrorDisconnect(ErrorState.UNREACHABLE); + break; + case STATE_GENERIC_ERROR: + setErrorDisconnect(ErrorState.GENERIC_ERROR); + break; + default: + Log.e(TAG, "Unknown status code received"); + break; + } + } + + /** + * Function called via JNI to generate a list of DER encoded CA certificates + * as byte array. + * + * @param hash optional alias (only hash part), if given matching certificates are returned + * @return a list of DER encoded CA certificates + */ + private byte[][] getTrustedCertificates(String hash) + { + ArrayList<byte[]> certs = new ArrayList<byte[]>(); + TrustedCertificateManager certman = TrustedCertificateManager.getInstance(); + try + { + if (hash != null) + { + String alias = "user:" + hash + ".0"; + X509Certificate cert = certman.getCACertificateFromAlias(alias); + if (cert == null) + { + alias = "system:" + hash + ".0"; + cert = certman.getCACertificateFromAlias(alias); + } + if (cert == null) + { + return null; + } + certs.add(cert.getEncoded()); + } + else + { + String alias = this.mCurrentCertificateAlias; + if (alias != null) + { + X509Certificate cert = certman.getCACertificateFromAlias(alias); + if (cert == null) + { + return null; + } + certs.add(cert.getEncoded()); + } + else + { + for (X509Certificate cert : certman.getAllCACertificates().values()) + { + certs.add(cert.getEncoded()); + } + } + } + } + catch (CertificateEncodingException e) + { + e.printStackTrace(); + return null; + } + return certs.toArray(new byte[certs.size()][]); + } + + /** + * Initialization of charon, provided by libandroidbridge.so + * + * @param builder BuilderAdapter for this connection + * @param logfile absolute path to the logfile + */ + public native void initializeCharon(BuilderAdapter builder, String logfile); + + /** + * Deinitialize charon, provided by libandroidbridge.so + */ + public native void deinitializeCharon(); + + /** + * Initiate VPN, provided by libandroidbridge.so + */ + public native void initiate(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; + } + + /** + * Adapter for VpnService.Builder which is used to access it safely via JNI. + * There is a corresponding C object to access it from native code. + */ + public class BuilderAdapter + { + VpnService.Builder builder; + + public BuilderAdapter(String name) + { + builder = new CharonVpnService.Builder(); + builder.setSession(name); + + /* even though the option displayed in the system dialog says "Configure" + * we just use our main Activity */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, MainActivity.class); + PendingIntent pending = PendingIntent.getActivity(context, 0, intent, + Intent.FLAG_ACTIVITY_NEW_TASK); + builder.setConfigureIntent(pending); + } + + public synchronized boolean addAddress(String address, int prefixLength) + { + try + { + builder.addAddress(address, prefixLength); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addDnsServer(String address) + { + try + { + builder.addDnsServer(address); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addRoute(String address, int prefixLength) + { + try + { + builder.addRoute(address, prefixLength); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addSearchDomain(String domain) + { + try + { + builder.addSearchDomain(domain); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean setMtu(int mtu) + { + try + { + builder.setMtu(mtu); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized int establish() + { + ParcelFileDescriptor fd; + try + { + fd = builder.establish(); + } + catch (Exception ex) + { + ex.printStackTrace(); + return -1; + } + if (fd == null) + { + return -1; + } + return fd.detachFd(); + } + } + + /* + * The libraries are extracted to /data/data/org.strongswan.android/... + * during installation. + */ + static + { + System.loadLibrary("crypto"); + System.loadLibrary("strongswan"); + System.loadLibrary("hydra"); + System.loadLibrary("charon"); + System.loadLibrary("ipsec"); + System.loadLibrary("androidbridge"); + } +} diff --git a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java new file mode 100644 index 000000000..74868dc44 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import android.util.Log; + +public class TrustedCertificateManager +{ + private static final String TAG = TrustedCertificateManager.class.getSimpleName(); + private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); + private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>(); + private boolean mLoaded; + + /** + * Private constructor to prevent instantiation from other classes. + */ + private TrustedCertificateManager() + { + } + + /** + * This is not instantiated until the first call to getInstance() + */ + private static class Singleton { + public static final TrustedCertificateManager mInstance = new TrustedCertificateManager(); + } + + /** + * Get the single instance of the CA certificate manager. + * @return CA certificate manager + */ + public static TrustedCertificateManager getInstance() + { + return Singleton.mInstance; + } + + /** + * Forces a load/reload of the cached CA certificates. + * As this takes a while it should be called asynchronously. + * @return reference to itself + */ + public TrustedCertificateManager reload() + { + Log.d(TAG, "Force reload of cached CA certificates"); + this.mLock.writeLock().lock(); + loadCertificates(); + this.mLock.writeLock().unlock(); + return this; + } + + /** + * Ensures that the certificates are loaded but does not force a reload. + * As this takes a while if the certificates are not loaded yet it should + * be called asynchronously. + * @return reference to itself + */ + public TrustedCertificateManager load() + { + Log.d(TAG, "Ensure cached CA certificates are loaded"); + this.mLock.writeLock().lock(); + if (!this.mLoaded) + { + loadCertificates(); + } + this.mLock.writeLock().unlock(); + return this; + } + + /** + * Opens the CA certificate KeyStore and loads the cached certificates. + * The lock must be locked when calling this method. + */ + private void loadCertificates() + { + Log.d(TAG, "Load cached CA certificates"); + try + { + KeyStore store = KeyStore.getInstance("AndroidCAStore"); + store.load(null, null); + this.mCACerts = fetchCertificates(store); + this.mLoaded = true; + Log.d(TAG, "Cached CA certificates loaded"); + } + catch (Exception ex) + { + ex.printStackTrace(); + this.mCACerts = new Hashtable<String, X509Certificate>(); + } + } + + /** + * Load all X.509 certificates from the given KeyStore. + * @param store KeyStore to load certificates from + * @return Hashtable mapping aliases to certificates + */ + private Hashtable<String, X509Certificate> fetchCertificates(KeyStore store) + { + Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>(); + try + { + Enumeration<String> aliases = store.aliases(); + while (aliases.hasMoreElements()) + { + String alias = aliases.nextElement(); + Certificate cert; + cert = store.getCertificate(alias); + if (cert != null && cert instanceof X509Certificate) + { + certs.put(alias, (X509Certificate)cert); + } + } + } + catch (KeyStoreException ex) + { + ex.printStackTrace(); + } + return certs; + } + + /** + * Retrieve the CA certificate with the given alias. + * @param alias alias of the certificate to get + * @return the certificate, null if not found + */ + public X509Certificate getCACertificateFromAlias(String alias) + { + X509Certificate certificate = null; + + if (this.mLock.readLock().tryLock()) + { + certificate = this.mCACerts.get(alias); + this.mLock.readLock().unlock(); + } + else + { /* if we cannot get the lock load it directly from the KeyStore, + * should be fast for a single certificate */ + try + { + KeyStore store = KeyStore.getInstance("AndroidCAStore"); + store.load(null, null); + Certificate cert = store.getCertificate(alias); + if (cert != null && cert instanceof X509Certificate) + { + certificate = (X509Certificate)cert; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + } + return certificate; + } + + /** + * Get all CA certificates (from the system and user keystore). + * @return Hashtable mapping aliases to certificates + */ + @SuppressWarnings("unchecked") + public Hashtable<String, X509Certificate> getAllCACertificates() + { + Hashtable<String, X509Certificate> certs; + this.mLock.readLock().lock(); + certs = (Hashtable<String, X509Certificate>)this.mCACerts.clone(); + this.mLock.readLock().unlock(); + return certs; + } + + /** + * Get only the CA certificates installed by the user. + * @return Hashtable mapping aliases to certificates + */ + public Hashtable<String, X509Certificate> getUserCACertificates() + { + Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>(); + this.mLock.readLock().lock(); + for (String alias : this.mCACerts.keySet()) + { + if (alias.startsWith("user:")) + { + certs.put(alias, this.mCACerts.get(alias)); + } + } + this.mLock.readLock().unlock(); + return certs; + } +} diff --git a/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java new file mode 100644 index 000000000..1c14cb601 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java @@ -0,0 +1,264 @@ +/* + * 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.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import org.strongswan.android.data.VpnProfile; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; + +public class VpnStateService extends Service +{ + private final List<VpnStateListener> mListeners = new ArrayList<VpnStateListener>(); + private final IBinder mBinder = new LocalBinder(); + private Handler mHandler; + private VpnProfile mProfile; + private State mState = State.DISABLED; + private ErrorState mError = ErrorState.NO_ERROR; + + public enum State + { + DISABLED, + CONNECTING, + CONNECTED, + DISCONNECTING, + } + + public enum ErrorState + { + NO_ERROR, + AUTH_FAILED, + PEER_AUTH_FAILED, + LOOKUP_FAILED, + UNREACHABLE, + GENERIC_ERROR, + } + + /** + * Listener interface for bound clients that are interested in changes to + * this Service. + */ + public interface VpnStateListener + { + public void stateChanged(); + } + + /** + * Simple Binder that allows to directly access this Service class itself + * after binding to it. + */ + public class LocalBinder extends Binder + { + public VpnStateService getService() + { + return VpnStateService.this; + } + } + + @Override + public void onCreate() + { + /* this handler allows us to notify listeners from the UI thread and + * not from the threads that actually report any state changes */ + mHandler = new Handler(); + } + + @Override + public IBinder onBind(Intent intent) + { + return mBinder; + } + + @Override + public void onDestroy() + { + } + + /** + * Register a listener with this Service. We assume this is called from + * the main thread so no synchronization is happening. + * + * @param listener listener to register + */ + public void registerListener(VpnStateListener listener) + { + mListeners.add(listener); + } + + /** + * Unregister a listener from this Service. + * + * @param listener listener to unregister + */ + public void unregisterListener(VpnStateListener listener) + { + mListeners.remove(listener); + } + + /** + * Get the current VPN profile. + * + * @return profile + */ + public VpnProfile getProfile() + { /* only updated from the main thread so no synchronization needed */ + return mProfile; + } + + /** + * Get the current state. + * + * @return state + */ + public State getState() + { /* only updated from the main thread so no synchronization needed */ + return mState; + } + + /** + * Get the current error, if any. + * + * @return error + */ + public ErrorState getErrorState() + { /* only updated from the main thread so no synchronization needed */ + return mError; + } + + /** + * Disconnect any existing connection and shutdown the daemon, the + * VpnService is not stopped but it is reset so new connections can be + * started. + */ + public void disconnect() + { + /* as soon as the TUN device is created by calling establish() on the + * VpnService.Builder object the system binds to the service and keeps + * bound until the file descriptor of the TUN device is closed. thus + * calling stopService() here would not stop (destroy) the service yet, + * instead we call startService() with an empty Intent which shuts down + * the daemon (and closes the TUN device, if any) */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, CharonVpnService.class); + context.startService(intent); + } + + /** + * Update state and notify all listeners about the change. By using a Handler + * this is done from the main UI thread and not the initial reporter thread. + * Also, in doing the actual state change from the main thread, listeners + * see all changes and none are skipped. + * + * @param change the state update to perform before notifying listeners, returns true if state changed + */ + private void notifyListeners(final Callable<Boolean> change) + { + mHandler.post(new Runnable() { + @Override + public void run() + { + try + { + if (change.call()) + { /* otherwise there is no need to notify the listeners */ + for (VpnStateListener listener : mListeners) + { + listener.stateChanged(); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + } + + /** + * Set the VPN profile currently active. Listeners are not notified. + * + * May be called from threads other than the main thread. + * + * @param profile current profile + */ + public void setProfile(final VpnProfile profile) + { + /* even though we don't notify the listeners the update is done from the + * same handler so updates are predictable for listeners */ + mHandler.post(new Runnable() { + @Override + public void run() + { + VpnStateService.this.mProfile = profile; + } + }); + } + + /** + * Update the state and notify all listeners, if changed. + * + * May be called from threads other than the main thread. + * + * @param state new state + */ + public void setState(final State state) + { + notifyListeners(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception + { + if (VpnStateService.this.mState != state) + { + VpnStateService.this.mState = state; + return true; + } + return false; + } + }); + } + + /** + * Set the current error state and notify all listeners, if changed. + * + * May be called from threads other than the main thread. + * + * @param error error state + */ + public void setError(final ErrorState error) + { + notifyListeners(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception + { + if (VpnStateService.this.mError != error) + { + VpnStateService.this.mError = error; + return true; + } + return false; + } + }); + } +} diff --git a/src/frontends/android/src/org/strongswan/android/strongSwanActivity.java b/src/frontends/android/src/org/strongswan/android/strongSwanActivity.java deleted file mode 100644 index fabf71897..000000000 --- a/src/frontends/android/src/org/strongswan/android/strongSwanActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.strongswan.android; - -import android.app.Activity; -import android.content.Intent; -import android.net.VpnService; -import android.os.Bundle; - -public class strongSwanActivity extends Activity -{ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - startVpnService(); - } - - private void startVpnService() - { - Intent intent = VpnService.prepare(this); - if (intent != null) - { - startActivityForResult(intent, 0); - } - else - { - onActivityResult(0, RESULT_OK, null); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - if (resultCode == RESULT_OK) - { - Intent intent = new Intent(this, CharonVpnService.class); - startService(intent); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java new file mode 100644 index 000000000..a5efecc09 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java @@ -0,0 +1,86 @@ +/* + * 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.ui; + +import java.io.File; + +import org.strongswan.android.R; +import org.strongswan.android.data.LogContentProvider; +import org.strongswan.android.logic.CharonVpnService; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +public class LogActivity extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.log_activity); + + getActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.log, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + finish(); + return true; + case R.id.menu_send_log: + File logfile = new File(getFilesDir(), CharonVpnService.LOG_FILE); + if (!logfile.exists() || logfile.length() == 0) + { + Toast.makeText(this, getString(R.string.empty_log), Toast.LENGTH_SHORT).show(); + return true; + } + + String version = ""; + try + { + version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } + catch (NameNotFoundException e) + { + e.printStackTrace(); + } + + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_EMAIL, new String[] { MainActivity.CONTACT_EMAIL }); + intent.putExtra(Intent.EXTRA_SUBJECT, String.format(getString(R.string.log_mail_subject), version)); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_STREAM, LogContentProvider.createContentUri()); + startActivity(Intent.createChooser(intent, getString(R.string.send_log))); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java new file mode 100644 index 000000000..8740e0c46 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java @@ -0,0 +1,227 @@ +/* + * 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.ui; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.StringReader; + +import org.strongswan.android.R; +import org.strongswan.android.logic.CharonVpnService; + +import android.app.Fragment; +import android.os.Bundle; +import android.os.FileObserver; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class LogFragment extends Fragment implements Runnable +{ + private String mLogFilePath; + private Handler mLogHandler; + private TextView mLogView; + private LogScrollView mScrollView; + private BufferedReader mReader; + private Thread mThread; + private volatile boolean mRunning; + private FileObserver mDirectoryObserver; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; + /* use a handler to update the log view */ + mLogHandler = new Handler(); + + mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.log_fragment, null); + mLogView = (TextView)view.findViewById(R.id.log_view); + mScrollView = (LogScrollView)view.findViewById(R.id.scroll_view); + return view; + } + + @Override + public void onStart() + { + super.onStart(); + startLogReader(); + mDirectoryObserver.startWatching(); + } + + @Override + public void onStop() + { + super.onStop(); + mDirectoryObserver.stopWatching(); + stopLogReader(); + } + + /** + * Start reading from the log file + */ + private void startLogReader() + { + try + { + mReader = new BufferedReader(new FileReader(mLogFilePath)); + } + catch (FileNotFoundException e) + { + mReader = new BufferedReader(new StringReader("")); + } + + mLogView.setText(""); + mRunning = true; + mThread = new Thread(this); + mThread.start(); + } + + /** + * Stop reading from the log file + */ + private void stopLogReader() + { + try + { + mRunning = false; + mThread.interrupt(); + mThread.join(); + } + catch (InterruptedException e) + { + } + } + + /** + * Write the given log line to the TextView. We strip the prefix off to save + * some space (it is not that helpful for regular users anyway). + * + * @param line log line to log + */ + public void logLine(final String line) + { + mLogHandler.post(new Runnable() { + @Override + public void run() + { + /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */ + mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n'); + /* calling autoScroll() directly does not work, probably because content + * is not yet updated, so we post this to be done later */ + mScrollView.post(new Runnable() { + @Override + public void run() + { + mScrollView.autoScroll(); + } + }); + } + }); + } + + @Override + public void run() + { + while (mRunning) + { + try + { /* this works as long as the file is not truncated */ + String line = mReader.readLine(); + if (line == null) + { /* wait until there is more to log */ + Thread.sleep(1000); + } + else + { + logLine(line); + } + } + catch (Exception e) + { + break; + } + } + } + + /** + * FileObserver that checks for changes regarding the log file. Since charon + * truncates it (for which there is no explicit event) we check for any modification + * to the file, keep track of the file size and reopen it if it got smaller. + */ + private class LogDirectoryObserver extends FileObserver + { + private final File mFile; + private long mSize; + + public LogDirectoryObserver(String path) + { + super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE); + mFile = new File(mLogFilePath); + mSize = mFile.length(); + } + + @Override + public void onEvent(int event, String path) + { + if (path == null || !path.equals(CharonVpnService.LOG_FILE)) + { + return; + } + switch (event) + { /* even though we only subscribed for these we check them, + * as strange events are sometimes received */ + case FileObserver.CREATE: + case FileObserver.DELETE: + restartLogReader(); + break; + case FileObserver.MODIFY: + /* if the size got smaller reopen the log file, as it was probably truncated */ + long size = mFile.length(); + if (size < mSize) + { + restartLogReader(); + } + mSize = size; + break; + } + } + + private void restartLogReader() + { + /* we are called from a separate thread, so we use the handler */ + mLogHandler.post(new Runnable() { + @Override + public void run() + { + stopLogReader(); + startLogReader(); + } + }); + } + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java b/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java new file mode 100644 index 000000000..7eee820ce --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ScrollView; + +public class LogScrollView extends ScrollView +{ + private boolean mAutoScroll = true; + + public LogScrollView(Context context) + { + super(context); + } + + public LogScrollView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public LogScrollView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) + { + /* disable auto-scrolling when the user starts scrolling around */ + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) + { + mAutoScroll = false; + } + return super.onTouchEvent(ev); + } + + /** + * Call this to move newly added content into view by scrolling to the bottom. + * Nothing happens if auto-scrolling is disabled. + */ + public void autoScroll() + { + if (mAutoScroll) + { + fullScroll(View.FOCUS_DOWN); + } + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) + { + super.onScrollChanged(l, t, oldl, oldt); + /* if the user scrolls to the bottom we enable auto-scrolling again */ + if (t == getChildAt(getChildCount() - 1).getHeight() - getHeight()) + { + mAutoScroll = true; + } + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java new file mode 100644 index 000000000..80f1a27b3 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.CharonVpnService; +import org.strongswan.android.logic.TrustedCertificateManager; +import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.VpnService; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.EditText; + +public class MainActivity extends Activity implements OnVpnProfileSelectedListener +{ + public static final String CONTACT_EMAIL = "android@strongswan.org"; + private static final int PREPARE_VPN_SERVICE = 0; + private VpnProfile activeProfile; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setContentView(R.layout.main); + + ActionBar bar = getActionBar(); + bar.setDisplayShowTitleEnabled(false); + + /* load CA certificates in a background task */ + new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.menu_reload_certs: + new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true); + return true; + case R.id.menu_show_log: + Intent logIntent = new Intent(this, LogActivity.class); + startActivity(logIntent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Prepare the VpnService. If this succeeds the current VPN profile is + * started. + */ + protected void prepareVpnService() + { + Intent intent = VpnService.prepare(this); + if (intent != null) + { + startActivityForResult(intent, PREPARE_VPN_SERVICE); + } + else + { + onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case PREPARE_VPN_SERVICE: + if (resultCode == RESULT_OK && activeProfile != null) + { + Intent intent = new Intent(this, CharonVpnService.class); + intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId()); + /* submit the password as the profile might not store one */ + intent.putExtra(VpnProfileDataSource.KEY_PASSWORD, activeProfile.getPassword()); + this.startService(intent); + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onVpnProfileSelected(VpnProfile profile) + { + activeProfile = profile; + if (activeProfile.getPassword() == null) + { + new LoginDialog().show(getFragmentManager(), "LoginDialog"); + } + else + { + prepareVpnService(); + } + } + + /** + * Class that loads or reloads the cached CA certificates. + */ + private class CertificateLoadTask extends AsyncTask<Boolean, Void, TrustedCertificateManager> + { + @Override + protected void onPreExecute() + { + setProgressBarIndeterminateVisibility(true); + } + @Override + protected TrustedCertificateManager doInBackground(Boolean... params) + { + if (params.length > 0 && params[0]) + { /* force a reload of the certificates */ + return TrustedCertificateManager.getInstance().reload(); + } + return TrustedCertificateManager.getInstance().load(); + } + @Override + protected void onPostExecute(TrustedCertificateManager result) + { + setProgressBarIndeterminateVisibility(false); + } + } + + private class LoginDialog extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.login_dialog, null); + EditText username = (EditText)view.findViewById(R.id.username); + username.setText(activeProfile.getUsername()); + final EditText password = (EditText)view.findViewById(R.id.password); + + Builder adb = new AlertDialog.Builder(MainActivity.this); + adb.setView(view); + adb.setTitle(getString(R.string.login_title)); + adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) + { + /* let's work on a clone of the profile when updating the password */ + activeProfile = activeProfile.clone(); + activeProfile.setPassword(password.getText().toString().trim()); + prepareVpnService(); + } + }); + adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + } + }); + return adb.create(); + } + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java new file mode 100644 index 000000000..05ba5e8b3 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui; + +import java.security.cert.X509Certificate; +import java.util.Hashtable; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.TrustedCertificateManager; +import org.strongswan.android.ui.adapter.TrustedCertificateAdapter; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.Spinner; + +public class VpnProfileDetailActivity extends Activity +{ + private VpnProfileDataSource mDataSource; + private Long mId; + private VpnProfile mProfile; + private boolean mCertsLoaded; + private String mCertAlias; + private Spinner mCertSpinner; + private TrustedCertificateAdapter.CertEntry mSelectedCert; + private EditText mName; + private EditText mGateway; + private EditText mUsername; + private EditText mPassword; + private CheckBox mCheckAll; + private CheckBox mCheckAuto; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + + /* the title is set when we load the profile, if any */ + getActionBar().setDisplayHomeAsUpEnabled(true); + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + + setContentView(R.layout.profile_detail_view); + + mName = (EditText)findViewById(R.id.name); + mPassword = (EditText)findViewById(R.id.password); + mGateway = (EditText)findViewById(R.id.gateway); + mUsername = (EditText)findViewById(R.id.username); + + mCheckAll = (CheckBox)findViewById(R.id.ca_show_all); + mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); + mCertSpinner = (Spinner)findViewById(R.id.ca_spinner); + + mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + updateCertSpinner(); + } + }); + + mCheckAll.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + Hashtable<String, X509Certificate> certs; + certs = isChecked ? TrustedCertificateManager.getInstance().getAllCACertificates() + : TrustedCertificateManager.getInstance().getUserCACertificates(); + mCertSpinner.setAdapter(new TrustedCertificateAdapter(VpnProfileDetailActivity.this, certs)); + mSelectedCert = (TrustedCertificateAdapter.CertEntry)mCertSpinner.getSelectedItem(); + } + }); + + mCertSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, + int pos, long id) + { + mSelectedCert = (TrustedCertificateAdapter.CertEntry)parent.getSelectedItem(); + } + + @Override + public void onNothingSelected(AdapterView<?> arg0) + { + mSelectedCert = null; + } + }); + + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); + if (mId == null) + { + Bundle extras = getIntent().getExtras(); + mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID); + } + + loadProfileData(); + + new CertificateLoadTask().execute(); + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + mDataSource.close(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putLong(VpnProfileDataSource.KEY_ID, mId); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.profile_edit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + case R.id.menu_cancel: + finish(); + return true; + case R.id.menu_accept: + saveProfile(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Show an alert in case the previously selected certificate is not found anymore + * or the user did not select a certificate in the spinner. + */ + private void showCertificateAlert() + { + AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this); + adb.setTitle(R.string.alert_text_nocertfound_title); + adb.setMessage(R.string.alert_text_nocertfound); + adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.cancel(); + } + }); + adb.show(); + } + + /** + * Asynchronously executed task which confirms that the certificates are loaded. + * They are loaded from the main Activity already but might not be ready yet, or + * unloaded again. + * + * Once loaded the CA certificate spinner and checkboxes are updated + * accordingly. + */ + private class CertificateLoadTask extends AsyncTask<Void, Void, TrustedCertificateManager> + { + @Override + protected void onPreExecute() + { + setProgressBarIndeterminateVisibility(true); + } + + @Override + protected TrustedCertificateManager doInBackground(Void... params) + { + return TrustedCertificateManager.getInstance().load(); + } + + @Override + protected void onPostExecute(TrustedCertificateManager result) + { + TrustedCertificateAdapter adapter; + if (mCertAlias != null && mCertAlias.startsWith("system:")) + { + mCheckAll.setChecked(true); + adapter = new TrustedCertificateAdapter(VpnProfileDetailActivity.this, + result.getAllCACertificates()); + } + else + { + mCheckAll.setChecked(false); + adapter = new TrustedCertificateAdapter(VpnProfileDetailActivity.this, + result.getUserCACertificates()); + } + mCertSpinner.setAdapter(adapter); + + if (mCertAlias != null) + { + int position = adapter.getItemPosition(mCertAlias); + if (position == -1) + { /* previously selected certificate is not here anymore */ + showCertificateAlert(); + } + else + { + mCertSpinner.setSelection(position); + } + } + + mSelectedCert = (TrustedCertificateAdapter.CertEntry)mCertSpinner.getSelectedItem(); + + setProgressBarIndeterminateVisibility(false); + mCertsLoaded = true; + updateCertSpinner(); + } + } + + /** + * Update the CA certificate selection UI depending on whether the + * certificate should be automatically selected or not. + */ + private void updateCertSpinner() + { + if (!mCheckAuto.isChecked()) + { + if (mCertsLoaded) + { + mCertSpinner.setEnabled(true); + mCertSpinner.setVisibility(View.VISIBLE); + mCheckAll.setEnabled(true); + mCheckAll.setVisibility(View.VISIBLE); + } + } + else + { + mCertSpinner.setEnabled(false); + mCertSpinner.setVisibility(View.GONE); + mCheckAll.setEnabled(false); + mCheckAll.setVisibility(View.GONE); + } + } + + /** + * Save or update the profile depending on whether we actually have a + * profile object or not (this was created in updateProfileData) + */ + private void saveProfile() + { + if (verifyInput()) + { + if (mProfile != null) + { + updateProfileData(); + mDataSource.updateVpnProfile(mProfile); + } + else + { + mProfile = new VpnProfile(); + updateProfileData(); + mDataSource.insertProfile(mProfile); + } + setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId())); + finish(); + } + } + + /** + * Verify the user input and display error messages. + * @return true if the input is valid + */ + private boolean verifyInput() + { + boolean valid = true; + if (mGateway.getText().toString().trim().isEmpty()) + { + mGateway.setError(getString(R.string.alert_text_no_input_gateway)); + valid = false; + } + if (mUsername.getText().toString().trim().isEmpty()) + { + mUsername.setError(getString(R.string.alert_text_no_input_username)); + valid = false; + } + if (!mCheckAuto.isChecked() && mSelectedCert == null) + { + showCertificateAlert(); + valid = false; + } + return valid; + } + + /** + * Update the profile object with the data entered by the user + */ + private void updateProfileData() + { + /* the name is optional, we default to the gateway if none is given */ + String name = mName.getText().toString().trim(); + String gateway = mGateway.getText().toString().trim(); + mProfile.setName(name.isEmpty() ? gateway : name); + mProfile.setGateway(gateway); + mProfile.setUsername(mUsername.getText().toString().trim()); + String password = mPassword.getText().toString().trim(); + password = password.isEmpty() ? null : password; + mProfile.setPassword(password); + String certAlias = mCheckAuto.isChecked() ? null : mSelectedCert.mAlias; + mProfile.setCertificateAlias(certAlias); + } + + /** + * Load an existing profile if we got an ID + */ + private void loadProfileData() + { + getActionBar().setTitle(R.string.add_profile); + if (mId != null) + { + mProfile = mDataSource.getVpnProfile(mId); + if (mProfile != null) + { + mName.setText(mProfile.getName()); + mGateway.setText(mProfile.getGateway()); + mUsername.setText(mProfile.getUsername()); + mPassword.setText(mProfile.getPassword()); + mCertAlias = mProfile.getCertificateAlias(); + getActionBar().setTitle(mProfile.getName()); + } + else + { + Log.e(VpnProfileDetailActivity.class.getSimpleName(), + "VPN profile with id " + mId + " not found"); + finish(); + } + } + mCheckAll.setChecked(false); + mCheckAuto.setChecked(mCertAlias == null); + updateCertSpinner(); + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java new file mode 100644 index 000000000..1052558f2 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.ui.adapter.VpnProfileAdapter; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.Toast; + +public class VpnProfileListFragment extends Fragment +{ + private static final int ADD_REQUEST = 1; + private static final int EDIT_REQUEST = 2; + + private List<VpnProfile> mVpnProfiles; + private VpnProfileDataSource mDataSource; + private VpnProfileAdapter mListAdapter; + private ListView mListView; + private OnVpnProfileSelectedListener mListener; + + /** + * The activity containing this fragment should implement this interface + */ + public interface OnVpnProfileSelectedListener { + public void onVpnProfileSelected(VpnProfile profile); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.profile_list_fragment, null); + + mListView = (ListView)view.findViewById(R.id.profile_list); + mListView.setEmptyView(view.findViewById(R.id.profile_list_empty)); + mListView.setOnItemClickListener(mVpnProfileClicked); + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mListView.setMultiChoiceModeListener(mVpnProfileSelected); + mListView.setAdapter(mListAdapter); + + return view; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + Context context = getActivity().getApplicationContext(); + + mDataSource = new VpnProfileDataSource(this.getActivity()); + mDataSource.open(); + + /* cached list of profiles used as backend for the ListView */ + mVpnProfiles = mDataSource.getAllVpnProfiles(); + + mListAdapter = new VpnProfileAdapter(context, R.layout.profile_list_item, mVpnProfiles); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + mDataSource.close(); + } + + @Override + public void onAttach(Activity activity) + { + super.onAttach(activity); + + if (activity instanceof OnVpnProfileSelectedListener) + { + mListener = (OnVpnProfileSelectedListener)activity; + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { + inflater.inflate(R.menu.profile_list, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.add_profile: + Intent connectionIntent = new Intent(getActivity(), + VpnProfileDetailActivity.class); + startActivityForResult(connectionIntent, ADD_REQUEST); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case ADD_REQUEST: + case EDIT_REQUEST: + if (resultCode != Activity.RESULT_OK) + { + return; + } + long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0); + VpnProfile profile = mDataSource.getVpnProfile(id); + if (profile != null) + { /* in case this was an edit, we remove it first */ + mVpnProfiles.remove(profile); + mVpnProfiles.add(profile); + mListAdapter.notifyDataSetChanged(); + } + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> a, View v, int position, long id) + { + if (mListener != null) + { + mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position)); + } + } + }; + + private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() { + private HashSet<Integer> mSelected; + private MenuItem mEditProfile; + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) + { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) + { + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) + { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.profile_list_context, menu); + mEditProfile = menu.findItem(R.id.edit_profile); + mSelected = new HashSet<Integer>(); + mode.setTitle(R.string.select_profiles); + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) + { + switch (item.getItemId()) + { + case R.id.edit_profile: + { + int position = mSelected.iterator().next(); + VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); + Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); + connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); + startActivityForResult(connectionIntent, EDIT_REQUEST); + break; + } + case R.id.delete_profile: + { + ArrayList<VpnProfile> profiles = new ArrayList<VpnProfile>(); + for (int position : mSelected) + { + profiles.add((VpnProfile)mListView.getItemAtPosition(position)); + } + for (VpnProfile profile : profiles) + { + mDataSource.deleteVpnProfile(profile); + mVpnProfiles.remove(profile); + } + mListAdapter.notifyDataSetChanged(); + Toast.makeText(VpnProfileListFragment.this.getActivity(), + R.string.profiles_deleted, Toast.LENGTH_SHORT).show(); + break; + } + default: + return false; + } + mode.finish(); + return true; + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) + { + if (checked) + { + mSelected.add(position); + } + else + { + mSelected.remove(position); + } + final int checkedCount = mSelected.size(); + mEditProfile.setEnabled(checkedCount == 1); + switch (checkedCount) + { + case 0: + mode.setSubtitle(R.string.no_profile_selected); + break; + case 1: + mode.setSubtitle(R.string.one_profile_selected); + break; + default: + mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount)); + break; + } + } + }; +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java new file mode 100644 index 000000000..738ed111f --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.ErrorState; +import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.logic.VpnStateService.VpnStateListener; + +import android.app.AlertDialog; +import android.app.Fragment; +import android.app.ProgressDialog; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +public class VpnStateFragment extends Fragment implements VpnStateListener +{ + private static final String KEY_ERROR = "error"; + private static final String KEY_NAME = "name"; + + private TextView mProfileNameView; + private TextView mProfileView; + private TextView mStateView; + private int stateBaseColor; + private Button mActionButton; + private ProgressDialog mProgressDialog; + private State mState; + private AlertDialog mErrorDialog; + private ErrorState mError; + private String mErrorProfileName; + private VpnStateService mService; + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + mService.registerListener(VpnStateFragment.this); + updateView(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + /* bind to the service only seems to work from the ApplicationContext */ + Context context = getActivity().getApplicationContext(); + context.bindService(new Intent(context, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + + mError = ErrorState.NO_ERROR; + if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR)) + { + mError = (ErrorState)savedInstanceState.getSerializable(KEY_ERROR); + mErrorProfileName = savedInstanceState.getString(KEY_NAME); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + outState.putSerializable(KEY_ERROR, mError); + outState.putString(KEY_NAME, mErrorProfileName); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.vpn_state_fragment, null); + + mActionButton = (Button)view.findViewById(R.id.action); + mActionButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) + { + if (mService != null) + { + mService.disconnect(); + } + } + }); + enableActionButton(false); + + mStateView = (TextView)view.findViewById(R.id.vpn_state); + stateBaseColor = mStateView.getCurrentTextColor(); + mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label); + mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name); + + return view; + } + + @Override + public void onStart() + { + super.onStart(); + if (mService != null) + { + updateView(); + } + } + + @Override + public void onStop() + { + super.onStop(); + hideErrorDialog(); + hideProgressDialog(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + if (mService != null) + { + mService.unregisterListener(this); + getActivity().getApplicationContext().unbindService(mServiceConnection); + } + } + + @Override + public void stateChanged() + { + updateView(); + } + + public void updateView() + { + State state = mService.getState(); + ErrorState error = ErrorState.NO_ERROR; + String name = "", gateway = ""; + + if (state != State.DISABLED) + { + VpnProfile profile = mService.getProfile(); + if (profile != null) + { + name = profile.getName(); + gateway = profile.getGateway(); + } + error = mService.getErrorState(); + } + + if (reportError(name, state, error)) + { + return; + } + + if (state == mState) + { /* avoid unnecessary updates */ + return; + } + + hideProgressDialog(); + enableActionButton(false); + mProfileNameView.setText(name); + mState = state; + + switch (state) + { + case DISABLED: + showProfile(false); + mStateView.setText(R.string.state_disabled); + mStateView.setTextColor(stateBaseColor); + break; + case CONNECTING: + showProfile(true); + showConnectDialog(name, gateway); + mStateView.setText(R.string.state_connecting); + mStateView.setTextColor(stateBaseColor); + break; + case CONNECTED: + showProfile(true); + enableActionButton(true); + mStateView.setText(R.string.state_connected); + mStateView.setTextColor(getResources().getColor(R.color.success_text)); + break; + case DISCONNECTING: + showProfile(true); + showDisconnectDialog(name); + mStateView.setText(R.string.state_disconnecting); + mStateView.setTextColor(stateBaseColor); + break; + } + } + + private boolean reportError(String name, State state, ErrorState error) + { + if (mError != ErrorState.NO_ERROR) + { /* we are currently reporting an error which was not yet dismissed */ + error = mError; + name = mErrorProfileName; + } + else if (error != ErrorState.NO_ERROR && (state == State.CONNECTING || state == State.CONNECTED)) + { /* while initiating we report errors */ + mError = error; + mErrorProfileName = name; + } + else + { /* ignore all other errors */ + error = ErrorState.NO_ERROR; + } + if (error == ErrorState.NO_ERROR) + { + hideErrorDialog(); + return false; + } + else if (mErrorDialog != null) + { /* we already show the dialog */ + return true; + } + hideProgressDialog(); + mProfileNameView.setText(name); + showProfile(true); + enableActionButton(false); + mStateView.setText(R.string.state_error); + mStateView.setTextColor(getResources().getColor(R.color.error_text)); + switch (error) + { + case AUTH_FAILED: + showErrorDialog(R.string.error_auth_failed); + break; + case PEER_AUTH_FAILED: + showErrorDialog(R.string.error_peer_auth_failed); + break; + case LOOKUP_FAILED: + showErrorDialog(R.string.error_lookup_failed); + break; + case UNREACHABLE: + showErrorDialog(R.string.error_unreachable); + break; + default: + showErrorDialog(R.string.error_generic); + break; + } + return true; + } + + private void showProfile(boolean show) + { + mProfileView.setVisibility(show ? View.VISIBLE : View.GONE); + mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE); + } + + private void enableActionButton(boolean enable) + { + mActionButton.setEnabled(enable); + mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE); + } + + private void hideProgressDialog() + { + if (mProgressDialog != null) + { + mProgressDialog.dismiss(); + mProgressDialog = null; + } + } + + private void hideErrorDialog() + { + if (mErrorDialog != null) + { + mErrorDialog.dismiss(); + mErrorDialog = null; + } + } + + private void showConnectDialog(String profile, String gateway) + { + mProgressDialog = new ProgressDialog(getActivity()); + mProgressDialog.setTitle(String.format(getString(R.string.connecting_title), profile)); + mProgressDialog.setMessage(String.format(getString(R.string.connecting_message), gateway)); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setCancelable(false); + mProgressDialog.setButton(getString(android.R.string.cancel), + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (mService != null) + { + mService.disconnect(); + } + } + }); + mProgressDialog.show(); + } + + private void showDisconnectDialog(String profile) + { + mProgressDialog = new ProgressDialog(getActivity()); + mProgressDialog.setMessage(getString(R.string.state_disconnecting)); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setCancelable(false); + mProgressDialog.show(); + } + + private void showErrorDialog(int textid) + { + mErrorDialog = new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.error_introduction) + " " + getString(textid)) + .setCancelable(false) + .setNeutralButton(R.string.show_log, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + Intent logIntent = new Intent(getActivity(), LogActivity.class); + startActivity(logIntent); + } + }) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.dismiss(); + } + }).create(); + mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) + { /* clear the error */ + mError = ErrorState.NO_ERROR; + mErrorDialog = null; + updateView(); + } + }); + mErrorDialog.show(); + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java new file mode 100644 index 000000000..ae94adc52 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui.adapter; + +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Map.Entry; + +import org.strongswan.android.R; + +import android.content.Context; +import android.net.http.SslCertificate; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +public class TrustedCertificateAdapter extends BaseAdapter +{ + private final ArrayList<CertEntry> mContent; + private final Context mContext; + + public class CertEntry implements Comparable<CertEntry> + { + public X509Certificate mCert; + public String mAlias; + public String mDisplayName; + + public CertEntry(String alias, X509Certificate cert) + { + mCert = cert; + mAlias = alias; + } + + public String getDisplayText() + { + if (mDisplayName == null) + { + SslCertificate cert = new SslCertificate(mCert); + String o = cert.getIssuedTo().getOName(); + String ou = cert.getIssuedTo().getUName(); + String cn = cert.getIssuedTo().getCName(); + if (!o.isEmpty()) + { + mDisplayName = o; + if (!cn.isEmpty()) + { + mDisplayName = mDisplayName + ", " + cn; + } + else if (!ou.isEmpty()) + { + mDisplayName = mDisplayName + ", " + ou; + } + } + else if (!cn.isEmpty()) + { + mDisplayName = cn; + } + else + { + mDisplayName = cert.getIssuedTo().getDName(); + } + } + return mDisplayName; + } + + @Override + public int compareTo(CertEntry another) + { + return getDisplayText().compareToIgnoreCase(another.getDisplayText()); + } + } + + public TrustedCertificateAdapter(Context context, + Hashtable<String, X509Certificate> content) + { + mContext = context; + mContent = new ArrayList<TrustedCertificateAdapter.CertEntry>(); + for (Entry<String, X509Certificate> entry : content.entrySet()) + { + mContent.add(new CertEntry(entry.getKey(), entry.getValue())); + } + Collections.sort(mContent); + } + + @Override + public int getCount() + { + return mContent.size(); + } + + @Override + public Object getItem(int position) + { + return mContent.get(position); + } + + /** + * Returns the position (index) of the entry with the given alias. + * + * @param alias alias of the item to find + * @return the position (index) in the list + */ + public int getItemPosition(String alias) + { + for (int i = 0; i < mContent.size(); i++) + { + if (mContent.get(i).mAlias.equals(alias)) + { + return i; + } + } + return -1; + } + + @Override + public long getItemId(int position) + { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + LayoutInflater inflater = LayoutInflater.from(mContext); + final View certView = inflater.inflate(R.layout.trusted_certificates_item, null); + final TextView certText = (TextView)certView.findViewById(R.id.certificate_name); + certText.setText(mContent.get(position).getDisplayText()); + return certView; + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java new file mode 100644 index 000000000..39e3e586a --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.ui.adapter; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class VpnProfileAdapter extends ArrayAdapter<VpnProfile> +{ + private final int resource; + private final List<VpnProfile> items; + + public VpnProfileAdapter(Context context, int resource, + List<VpnProfile> items) + { + super(context, resource, items); + this.resource = resource; + this.items = items; + sortItems(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View vpnProfileView; + if (convertView != null) + { + vpnProfileView = convertView; + } + else + { + LayoutInflater inflater = LayoutInflater.from(getContext()); + vpnProfileView = inflater.inflate(resource, null); + } + VpnProfile profile = getItem(position); + TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name); + tv.setText(profile.getName()); + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); + tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway()); + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); + tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); + return vpnProfileView; + } + + @Override + public void notifyDataSetChanged() + { + sortItems(); + super.notifyDataSetChanged(); + } + + private void sortItems() + { + Collections.sort(this.items, new Comparator<VpnProfile>() { + @Override + public int compare(VpnProfile lhs, VpnProfile rhs) + { + return lhs.getName().compareToIgnoreCase(rhs.getName()); + } + }); + } +} diff --git a/src/libcharon/Android.mk b/src/libcharon/Android.mk index 5e93e235f..a4ac87182 100644 --- a/src/libcharon/Android.mk +++ b/src/libcharon/Android.mk @@ -44,7 +44,7 @@ encoding/payloads/vendor_id_payload.c encoding/payloads/vendor_id_payload.h \ encoding/payloads/hash_payload.c encoding/payloads/hash_payload.h \ kernel/kernel_handler.c kernel/kernel_handler.h \ network/receiver.c network/receiver.h network/sender.c network/sender.h \ -network/packet.c network/packet.h network/socket.c network/socket.h \ +network/socket.c network/socket.h \ network/socket_manager.c network/socket_manager.h \ processing/jobs/acquire_job.c processing/jobs/acquire_job.h \ processing/jobs/delete_child_sa_job.c processing/jobs/delete_child_sa_job.h \ diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index fd910f73f..eada68bf5 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -42,7 +42,7 @@ encoding/payloads/vendor_id_payload.c encoding/payloads/vendor_id_payload.h \ encoding/payloads/hash_payload.c encoding/payloads/hash_payload.h \ kernel/kernel_handler.c kernel/kernel_handler.h \ network/receiver.c network/receiver.h network/sender.c network/sender.h \ -network/packet.c network/packet.h network/socket.c network/socket.h \ +network/socket.c network/socket.h \ network/socket_manager.c network/socket_manager.h \ processing/jobs/acquire_job.c processing/jobs/acquire_job.h \ processing/jobs/delete_child_sa_job.c processing/jobs/delete_child_sa_job.h \ diff --git a/src/libcharon/daemon.c b/src/libcharon/daemon.c index 612796a78..6e977efc4 100644 --- a/src/libcharon/daemon.c +++ b/src/libcharon/daemon.c @@ -102,7 +102,6 @@ static void destroy(private_daemon_t *this) /* cancel all threads and wait for their termination */ lib->processor->cancel(lib->processor); - DESTROY_IF(this->public.receiver); #ifdef ME DESTROY_IF(this->public.connect_manager); DESTROY_IF(this->public.mediation_manager); @@ -118,7 +117,6 @@ static void destroy(private_daemon_t *this) DESTROY_IF(this->public.eap); DESTROY_IF(this->public.xauth); DESTROY_IF(this->public.backends); - DESTROY_IF(this->public.sender); DESTROY_IF(this->public.socket); DESTROY_IF(this->public.caps); @@ -142,17 +140,44 @@ METHOD(daemon_t, start, void, DEFAULT_THREADS, charon->name)); } + +/** + * Initialize/deinitialize sender and receiver + */ +static bool sender_receiver_cb(void *plugin, plugin_feature_t *feature, + bool reg, private_daemon_t *this) +{ + if (reg) + { + this->public.receiver = receiver_create(); + if (!this->public.receiver) + { + return FALSE; + } + this->public.sender = sender_create(); + } + else + { + DESTROY_IF(this->public.receiver); + DESTROY_IF(this->public.sender); + } + return TRUE; +} + METHOD(daemon_t, initialize, bool, private_daemon_t *this, char *plugins) { - static plugin_feature_t features[] = { + plugin_feature_t features[] = { PLUGIN_PROVIDE(CUSTOM, "libcharon"), - PLUGIN_DEPENDS(HASHER, HASH_SHA1), - PLUGIN_DEPENDS(RNG, RNG_STRONG), PLUGIN_DEPENDS(NONCE_GEN), + PLUGIN_DEPENDS(CUSTOM, "libcharon-receiver"), PLUGIN_DEPENDS(CUSTOM, "kernel-ipsec"), PLUGIN_DEPENDS(CUSTOM, "kernel-net"), - PLUGIN_DEPENDS(CUSTOM, "socket"), + PLUGIN_CALLBACK((plugin_feature_callback_t)sender_receiver_cb, this), + PLUGIN_PROVIDE(CUSTOM, "libcharon-receiver"), + PLUGIN_DEPENDS(HASHER, HASH_SHA1), + PLUGIN_DEPENDS(RNG, RNG_STRONG), + PLUGIN_DEPENDS(CUSTOM, "socket"), }; lib->plugins->add_static_features(lib->plugins, charon->name, features, countof(features), TRUE); @@ -170,12 +195,6 @@ METHOD(daemon_t, initialize, bool, { return FALSE; } - this->public.sender = sender_create(); - this->public.receiver = receiver_create(); - if (this->public.receiver == NULL) - { - return FALSE; - } /* Queue start_action job */ lib->processor->queue_job(lib->processor, (job_t*)start_action_job_create()); diff --git a/src/libcharon/encoding/message.h b/src/libcharon/encoding/message.h index 6f3c7967f..6d558daf6 100644 --- a/src/libcharon/encoding/message.h +++ b/src/libcharon/encoding/message.h @@ -27,11 +27,11 @@ typedef struct message_t message_t; #include <library.h> -#include <network/packet.h> #include <encoding/payloads/ike_header.h> #include <encoding/payloads/notify_payload.h> #include <sa/keymat.h> #include <sa/ike_sa_id.h> +#include <utils/packet.h> #include <utils/linked_list.h> /** diff --git a/src/libcharon/network/receiver.c b/src/libcharon/network/receiver.c index 3a52f8dc3..b270d65df 100644 --- a/src/libcharon/network/receiver.c +++ b/src/libcharon/network/receiver.c @@ -22,12 +22,12 @@ #include <daemon.h> #include <network/socket.h> -#include <network/packet.h> #include <processing/jobs/job.h> #include <processing/jobs/process_message_job.h> #include <processing/jobs/callback_job.h> #include <crypto/hashers/hasher.h> #include <threading/mutex.h> +#include <utils/packet.h> /** lifetime of a cookie, in seconds */ #define COOKIE_LIFETIME 10 diff --git a/src/libcharon/network/receiver.h b/src/libcharon/network/receiver.h index 93b3d3c0c..9e8edee45 100644 --- a/src/libcharon/network/receiver.h +++ b/src/libcharon/network/receiver.h @@ -26,8 +26,8 @@ typedef struct receiver_t receiver_t; #include <library.h> -#include <network/packet.h> #include <utils/host.h> +#include <utils/packet.h> /** * Callback called for any received UDP encapsulated ESP packet. diff --git a/src/libcharon/network/sender.c b/src/libcharon/network/sender.c index a919a0263..641dd5333 100644 --- a/src/libcharon/network/sender.c +++ b/src/libcharon/network/sender.c @@ -87,7 +87,6 @@ METHOD(sender_t, send_no_marker, void, src = packet->get_source(packet); dst = packet->get_destination(packet); - DBG1(DBG_NET, "sending packet: from %#H to %#H", src, dst); if (this->send_delay) { @@ -124,6 +123,8 @@ METHOD(sender_t, send_, void, /* if neither source nor destination port is 500 we add a Non-ESP marker */ src = packet->get_source(packet); dst = packet->get_destination(packet); + DBG1(DBG_NET, "sending packet: from %#H to %#H", src, dst); + if (dst->get_port(dst) != IKEV2_UDP_PORT && src->get_port(src) != IKEV2_UDP_PORT) { diff --git a/src/libcharon/network/sender.h b/src/libcharon/network/sender.h index c4f18d73b..9b5c325cc 100644 --- a/src/libcharon/network/sender.h +++ b/src/libcharon/network/sender.h @@ -26,7 +26,7 @@ typedef struct sender_t sender_t; #include <library.h> -#include <network/packet.h> +#include <utils/packet.h> /** * Callback job responsible for sending IKE packets over the socket. diff --git a/src/libcharon/network/socket.h b/src/libcharon/network/socket.h index 4a4ef52e6..b8850c6ed 100644 --- a/src/libcharon/network/socket.h +++ b/src/libcharon/network/socket.h @@ -27,7 +27,7 @@ typedef struct socket_t socket_t; #include <library.h> -#include <network/packet.h> +#include <utils/packet.h> #include <utils/enumerator.h> #include <plugins/plugin.h> diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index d9e4ca582..1e3d00f02 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -488,7 +488,7 @@ METHOD(ike_sa_t, send_keepalive, void, data.ptr[0] = 0xFF; data.len = 1; packet->set_data(packet, data); - DBG1(DBG_IKE, "sending keep alive"); + DBG1(DBG_IKE, "sending keep alive to %#H", this->other_host); charon->sender->send_no_marker(charon->sender, packet); diff = 0; } diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index e52355962..de9e0ede4 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -43,6 +43,7 @@ typedef struct ike_sa_t ike_sa_t; #include <config/peer_cfg.h> #include <config/ike_cfg.h> #include <credentials/auth_cfg.h> +#include <utils/packet.h> /** * Timeout in seconds after that a half open IKE_SA gets deleted. diff --git a/src/libcharon/sa/ikev2/tasks/ike_mobike.h b/src/libcharon/sa/ikev2/tasks/ike_mobike.h index a7e3fe7e3..3b447af51 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_mobike.h +++ b/src/libcharon/sa/ikev2/tasks/ike_mobike.h @@ -26,7 +26,7 @@ typedef struct ike_mobike_t ike_mobike_t; #include <library.h> #include <sa/ike_sa.h> #include <sa/task.h> -#include <network/packet.h> +#include <utils/packet.h> /** * Task of type ike_mobike, detects and handles MOBIKE extension. diff --git a/src/libhydra/kernel/kernel_ipsec.c b/src/libhydra/kernel/kernel_ipsec.c index 9b38297cc..1a32ab4e7 100644 --- a/src/libhydra/kernel/kernel_ipsec.c +++ b/src/libhydra/kernel/kernel_ipsec.c @@ -17,28 +17,6 @@ #include <hydra.h> -ENUM(ipsec_mode_names, MODE_TRANSPORT, MODE_DROP, - "TRANSPORT", - "TUNNEL", - "BEET", - "PASS", - "DROP" -); - -ENUM(policy_dir_names, POLICY_IN, POLICY_FWD, - "in", - "out", - "fwd" -); - -ENUM(ipcomp_transform_names, IPCOMP_NONE, IPCOMP_LZJH, - "IPCOMP_NONE", - "IPCOMP_OUI", - "IPCOMP_DEFLATE", - "IPCOMP_LZS", - "IPCOMP_LZJH" -); - /** * See header */ diff --git a/src/libhydra/kernel/kernel_ipsec.h b/src/libhydra/kernel/kernel_ipsec.h index 500a77cad..ee0ade2aa 100644 --- a/src/libhydra/kernel/kernel_ipsec.h +++ b/src/libhydra/kernel/kernel_ipsec.h @@ -24,159 +24,14 @@ #ifndef KERNEL_IPSEC_H_ #define KERNEL_IPSEC_H_ -typedef enum ipsec_mode_t ipsec_mode_t; -typedef enum policy_dir_t policy_dir_t; -typedef enum policy_type_t policy_type_t; -typedef enum policy_priority_t policy_priority_t; -typedef enum ipcomp_transform_t ipcomp_transform_t; typedef struct kernel_ipsec_t kernel_ipsec_t; -typedef struct ipsec_sa_cfg_t ipsec_sa_cfg_t; -typedef struct lifetime_cfg_t lifetime_cfg_t; -typedef struct mark_t mark_t; #include <utils/host.h> -#include <crypto/prf_plus.h> +#include <ipsec/ipsec_types.h> #include <selectors/traffic_selector.h> #include <plugins/plugin.h> /** - * Mode of an IPsec SA. - */ -enum ipsec_mode_t { - /** not using any encapsulation */ - MODE_NONE = 0, - /** transport mode, no inner address */ - MODE_TRANSPORT = 1, - /** tunnel mode, inner and outer addresses */ - MODE_TUNNEL, - /** BEET mode, tunnel mode but fixed, bound inner addresses */ - MODE_BEET, - /** passthrough policy for traffic without an IPsec SA */ - MODE_PASS, - /** drop policy discarding traffic */ - MODE_DROP -}; - -/** - * enum names for ipsec_mode_t. - */ -extern enum_name_t *ipsec_mode_names; - -/** - * Direction of a policy. These are equal to those - * defined in xfrm.h, but we want to stay implementation - * neutral here. - */ -enum policy_dir_t { - /** Policy for inbound traffic */ - POLICY_IN = 0, - /** Policy for outbound traffic */ - POLICY_OUT = 1, - /** Policy for forwarded traffic */ - POLICY_FWD = 2, -}; - -/** - * enum names for policy_dir_t. - */ -extern enum_name_t *policy_dir_names; - -/** - * Type of a policy. - */ -enum policy_type_t { - /** Normal IPsec policy */ - POLICY_IPSEC = 1, - /** Passthrough policy (traffic is ignored by IPsec) */ - POLICY_PASS, - /** Drop policy (traffic is discarded) */ - POLICY_DROP, -}; - -/** - * High-level priority of a policy. - */ -enum policy_priority_t { - /** Default priority */ - POLICY_PRIORITY_DEFAULT, - /** Priority for trap policies */ - POLICY_PRIORITY_ROUTED, - /** Priority for fallback drop policies */ - POLICY_PRIORITY_FALLBACK, -}; - -/** - * IPComp transform IDs, as in RFC 4306 - */ -enum ipcomp_transform_t { - IPCOMP_NONE = 0, - IPCOMP_OUI = 1, - IPCOMP_DEFLATE = 2, - IPCOMP_LZS = 3, - IPCOMP_LZJH = 4, -}; - -/** - * enum strings for ipcomp_transform_t. - */ -extern enum_name_t *ipcomp_transform_names; - -/** - * This struct contains details about IPsec SA(s) tied to a policy. - */ -struct ipsec_sa_cfg_t { - /** mode of SA (tunnel, transport) */ - ipsec_mode_t mode; - /** unique ID */ - u_int32_t reqid; - /** details about ESP/AH */ - struct { - /** TRUE if this protocol is used */ - bool use; - /** SPI for ESP/AH */ - u_int32_t spi; - } esp, ah; - /** details about IPComp */ - struct { - /** the IPComp transform used */ - u_int16_t transform; - /** CPI for IPComp */ - u_int16_t cpi; - } ipcomp; -}; - -/** - * A lifetime_cfg_t defines the lifetime limits of an SA. - * - * Set any of these values to 0 to ignore. - */ -struct lifetime_cfg_t { - struct { - /** Limit before the SA gets invalid. */ - u_int64_t life; - /** Limit before the SA gets rekeyed. */ - u_int64_t rekey; - /** The range of a random value subtracted from rekey. */ - u_int64_t jitter; - } time, bytes, packets; -}; - -/** - * A mark_t defines an optional mark in an IPsec SA. - */ -struct mark_t { - /** Mark value */ - u_int32_t value; - /** Mark mask */ - u_int32_t mask; -}; - -/** - * Special mark value that uses the reqid of the CHILD_SA as mark - */ -#define MARK_REQID (0xFFFFFFFF) - -/** * Interface to the ipsec subsystem of the kernel. * * The kernel ipsec interface handles the communication with the kernel diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 99ff69106..81f4632ef 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -3,14 +3,23 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ -ipsec.c ipsec.h +ipsec.c ipsec.h \ +esp_context.c esp_context.h \ +esp_packet.c esp_packet.h \ +ip_packet.c ip_packet.h \ +ipsec_event_listener.h \ +ipsec_event_relay.c ipsec_event_relay.h \ +ipsec_policy.c ipsec_policy.h \ +ipsec_policy_mgr.c ipsec_policy_mgr.h \ +ipsec_processor.c ipsec_processor.h \ +ipsec_sa.c ipsec_sa.h \ +ipsec_sa_mgr.c ipsec_sa_mgr.h # build libipsec --------------------------------------------------------------- LOCAL_C_INCLUDES += \ $(libvstr_PATH) \ $(strongswan_PATH)/src/include \ - $(strongswan_PATH)/src/libhydra \ $(strongswan_PATH)/src/libstrongswan LOCAL_CFLAGS := $(strongswan_CFLAGS) @@ -23,7 +32,7 @@ LOCAL_ARM_MODE := arm LOCAL_PRELINK_MODULE := false -LOCAL_SHARED_LIBRARIES += libstrongswan libhydra +LOCAL_SHARED_LIBRARIES += libstrongswan include $(BUILD_SHARED_LIBRARY) diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 0b8faf724..35b8d7916 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -1,11 +1,22 @@ ipseclib_LTLIBRARIES = libipsec.la libipsec_la_SOURCES = \ -ipsec.c ipsec.h +ipsec.c ipsec.h \ +esp_context.c esp_context.h \ +esp_packet.c esp_packet.h \ +ip_packet.c ip_packet.h \ +ipsec_event_listener.h \ +ipsec_event_relay.c ipsec_event_relay.h \ +ipsec_policy.c ipsec_policy.h \ +ipsec_policy_mgr.c ipsec_policy_mgr.h \ +ipsec_processor.c ipsec_processor.h \ +ipsec_sa.c ipsec_sa.h \ +ipsec_sa_mgr.c ipsec_sa_mgr.h libipsec_la_LIBADD = -INCLUDES = -I$(top_srcdir)/src/libstrongswan +INCLUDES = \ + -I$(top_srcdir)/src/libstrongswan EXTRA_DIST = Android.mk diff --git a/src/libipsec/esp_context.c b/src/libipsec/esp_context.c new file mode 100644 index 000000000..c7fb7ab2f --- /dev/null +++ b/src/libipsec/esp_context.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 <limits.h> + +#include "esp_context.h" + +#include <library.h> +#include <debug.h> +#include <crypto/crypters/crypter.h> +#include <crypto/signers/signer.h> + +/** + * Should be a multiple of 8 + */ +#define ESP_DEFAULT_WINDOW_SIZE 128 + +typedef struct private_esp_context_t private_esp_context_t; + +/** + * Private additions to esp_context_t. + */ +struct private_esp_context_t { + + /** + * Public members + */ + esp_context_t public; + + /** + * Crypter used to encrypt/decrypt ESP packets + */ + crypter_t *crypter; + + /** + * Signer to authenticate ESP packets + */ + signer_t *signer; + + /** + * The highest sequence number that was successfully verified + * and authenticated, or assigned in an outbound context + */ + u_int32_t last_seqno; + + /** + * The bit in the window of the highest authenticated sequence number + */ + u_int seqno_index; + + /** + * The size of the anti-replay window (in bits) + */ + u_int window_size; + + /** + * The anti-replay window buffer + */ + chunk_t window; + + /** + * TRUE in case of an inbound ESP context + */ + bool inbound; +}; + +/** + * Set or unset a bit in the window. + */ +static inline void set_window_bit(private_esp_context_t *this, + u_int index, bool set) +{ + u_int i = index / CHAR_BIT; + + if (set) + { + this->window.ptr[i] |= 1 << (index % CHAR_BIT); + } + else + { + this->window.ptr[i] &= ~(1 << (index % CHAR_BIT)); + } +} + +/** + * Get a bit from the window. + */ +static inline bool get_window_bit(private_esp_context_t *this, u_int index) +{ + u_int i = index / CHAR_BIT; + + return this->window.ptr[i] & (1 << index % CHAR_BIT); +} + +/** + * Returns TRUE if the supplied seqno is not already marked in the window + */ +static bool check_window(private_esp_context_t *this, u_int32_t seqno) +{ + u_int offset; + + offset = this->last_seqno - seqno; + offset = (this->seqno_index - offset) % this->window_size; + return !get_window_bit(this, offset); +} + +METHOD(esp_context_t, verify_seqno, bool, + private_esp_context_t *this, u_int32_t seqno) +{ + if (!this->inbound) + { + return FALSE; + } + + if (seqno > this->last_seqno) + { /* |----------------------------------------| + * <---------^ ^ or <---------^ ^ + * WIN H S WIN H S + */ + return TRUE; + } + else if (seqno > 0 && this->window_size > this->last_seqno - seqno) + { /* |----------------------------------------| + * <---------^ or <---------^ + * WIN ^ H WIN ^ H + * S S + */ + return check_window(this, seqno); + } + else + { /* |----------------------------------------| + * ^ <---------^ + * S WIN H + */ + return FALSE; + } +} + +METHOD(esp_context_t, set_authenticated_seqno, void, + private_esp_context_t *this, u_int32_t seqno) +{ + u_int i, shift; + + if (!this->inbound) + { + return; + } + + if (seqno > this->last_seqno) + { /* shift the window to the new highest authenticated seqno */ + shift = seqno - this->last_seqno; + shift = shift < this->window_size ? shift : this->window_size; + for (i = 0; i < shift; ++i) + { + this->seqno_index = (this->seqno_index + 1) % this->window_size; + set_window_bit(this, this->seqno_index, FALSE); + } + set_window_bit(this, this->seqno_index, TRUE); + this->last_seqno = seqno; + } + else + { /* seqno is inside the window, set the corresponding window bit */ + i = this->last_seqno - seqno; + set_window_bit(this, (this->seqno_index - i) % this->window_size, TRUE); + } +} + +METHOD(esp_context_t, get_seqno, u_int32_t, + private_esp_context_t *this) +{ + return this->last_seqno; +} + +METHOD(esp_context_t, next_seqno, bool, + private_esp_context_t *this, u_int32_t *seqno) +{ + if (this->inbound || this->last_seqno == UINT32_MAX) + { /* inbound or segno would cycle */ + return FALSE; + } + *seqno = ++this->last_seqno; + return TRUE; +} + +METHOD(esp_context_t, get_signer, signer_t *, + private_esp_context_t *this) +{ + return this->signer; +} + +METHOD(esp_context_t, get_crypter, crypter_t *, + private_esp_context_t *this) +{ + return this->crypter; +} + +METHOD(esp_context_t, destroy, void, + private_esp_context_t *this) +{ + chunk_free(&this->window); + DESTROY_IF(this->crypter); + DESTROY_IF(this->signer); + free(this); +} + +/** + * Described in header. + */ +esp_context_t *esp_context_create(int enc_alg, chunk_t enc_key, + int int_alg, chunk_t int_key, bool inbound) +{ + private_esp_context_t *this; + + INIT(this, + .public = { + .get_crypter = _get_crypter, + .get_signer = _get_signer, + .get_seqno = _get_seqno, + .next_seqno = _next_seqno, + .verify_seqno = _verify_seqno, + .set_authenticated_seqno = _set_authenticated_seqno, + .destroy = _destroy, + }, + .inbound = inbound, + .window_size = ESP_DEFAULT_WINDOW_SIZE, + ); + + switch(enc_alg) + { + case ENCR_AES_CBC: + this->crypter = lib->crypto->create_crypter(lib->crypto, enc_alg, + enc_key.len); + break; + default: + break; + } + if (!this->crypter) + { + DBG1(DBG_ESP, "failed to create ESP context: unsupported encryption " + "algorithm"); + destroy(this); + return NULL; + } + if (!this->crypter->set_key(this->crypter, enc_key)) + { + DBG1(DBG_ESP, "failed to create ESP context: setting encryption key " + "failed"); + destroy(this); + return NULL; + } + + switch(int_alg) + { + case AUTH_HMAC_SHA1_96: + case AUTH_HMAC_SHA2_256_128: + case AUTH_HMAC_SHA2_384_192: + case AUTH_HMAC_SHA2_512_256: + this->signer = lib->crypto->create_signer(lib->crypto, int_alg); + break; + default: + break; + } + if (!this->signer) + { + DBG1(DBG_ESP, "failed to create ESP context: unsupported integrity " + "algorithm"); + destroy(this); + return NULL; + } + if (!this->signer->set_key(this->signer, int_key)) + { + DBG1(DBG_ESP, "failed to create ESP context: setting signature key " + "failed"); + destroy(this); + return NULL; + } + + if (inbound) + { + this->window = chunk_alloc(this->window_size / CHAR_BIT + 1); + memset(this->window.ptr, 0, this->window.len); + } + return &this->public; +} + + diff --git a/src/libipsec/esp_context.h b/src/libipsec/esp_context.h new file mode 100644 index 000000000..db247dced --- /dev/null +++ b/src/libipsec/esp_context.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 esp_context esp_context + * @{ @ingroup libipsec + */ + +#ifndef ESP_CONTEXT_H_ +#define ESP_CONTEXT_H_ + +#include <library.h> +#include <crypto/crypters/crypter.h> +#include <crypto/signers/signer.h> + +typedef struct esp_context_t esp_context_t; + +/** + * ESP context, handles sequence numbers and maintains cryptographic primitives + */ +struct esp_context_t { + + /** + * Get the crypter. + * + * @return crypter + */ + crypter_t *(*get_crypter)(esp_context_t *this); + + /** + * Get the signer. + * + * @return signer + */ + signer_t *(*get_signer)(esp_context_t *this); + + /** + * Get the current outbound ESP sequence number or the highest authenticated + * inbound sequence number. + * + * @return current sequence number, in host byte order + */ + u_int32_t (*get_seqno)(esp_context_t *this); + + /** + * Allocate the next outbound ESP sequence number. + * + * @param seqno the sequence number, in host byte order + * @return FALSE if the sequence number cycled or inbound context + */ + bool (*next_seqno)(esp_context_t *this, u_int32_t *seqno); + + /** + * Verify an ESP sequence number. Checks whether a packet with this + * sequence number was already received, using the anti-replay window. + * This operation does not modify the internal state. After the sequence + * number is successfully verified and the ESP packet is authenticated, + * set_authenticated_seqno() should be called. + * + * @param seqno the sequence number to verify, in host byte order + * @return TRUE when sequence number is valid + */ + bool (*verify_seqno)(esp_context_t *this, u_int32_t seqno); + + /** + * Adds a sequence number that was successfully verified and + * authenticated. A user MUST call verify_seqno() immediately before + * calling this method. + * + * @param seqno verified and authenticated seq number in host byte order + */ + void (*set_authenticated_seqno)(esp_context_t *this, + u_int32_t seqno); + + /** + * Destroy an esp_context_t + */ + void (*destroy)(esp_context_t *this); + +}; + +/** + * Create an esp_context_t instance + * + * @param enc_alg encryption algorithm + * @param enc_key encryption key + * @param int_alg integrity protection algorithm + * @param int_key integrity protection key + * @param inbound TRUE to create an inbound ESP context + * @return ESP context instance, or NULL if creation fails + */ +esp_context_t *esp_context_create(int enc_alg, chunk_t enc_key, int int_alg, + chunk_t int_key, bool inbound); + +#endif /** ESP_CONTEXT_H_ @}*/ + diff --git a/src/libipsec/esp_packet.c b/src/libipsec/esp_packet.c new file mode 100644 index 000000000..bfcab95eb --- /dev/null +++ b/src/libipsec/esp_packet.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "esp_packet.h" + +#include <library.h> +#include <debug.h> +#include <crypto/crypters/crypter.h> +#include <crypto/signers/signer.h> +#include <bio/bio_reader.h> +#include <bio/bio_writer.h> + +#include <netinet/in.h> + +typedef struct private_esp_packet_t private_esp_packet_t; + +/** + * Private additions to esp_packet_t. + */ +struct private_esp_packet_t { + + /** + * Public members + */ + esp_packet_t public; + + /** + * Raw ESP packet + */ + packet_t *packet; + + /** + * Payload of this packet + */ + ip_packet_t *payload; + + /** + * Next Header info (e.g. IPPROTO_IPIP) + */ + u_int8_t next_header; + +}; + +/** + * Forward declaration for clone() + */ +static private_esp_packet_t *esp_packet_create_internal(packet_t *packet); + +METHOD(packet_t, set_source, void, + private_esp_packet_t *this, host_t *src) +{ + return this->packet->set_source(this->packet, src); +} + +METHOD2(esp_packet_t, packet_t, get_source, host_t*, + private_esp_packet_t *this) +{ + return this->packet->get_source(this->packet); +} + +METHOD(packet_t, set_destination, void, + private_esp_packet_t *this, host_t *dst) +{ + return this->packet->set_destination(this->packet, dst); +} + +METHOD2(esp_packet_t, packet_t, get_destination, host_t*, + private_esp_packet_t *this) +{ + return this->packet->get_destination(this->packet); +} + +METHOD(packet_t, get_data, chunk_t, + private_esp_packet_t *this) +{ + return this->packet->get_data(this->packet); +} + +METHOD(packet_t, set_data, void, + private_esp_packet_t *this, chunk_t data) +{ + return this->packet->set_data(this->packet, data); +} + +METHOD(packet_t, skip_bytes, void, + private_esp_packet_t *this, size_t bytes) +{ + return this->packet->skip_bytes(this->packet, bytes); +} + +METHOD(packet_t, clone, packet_t*, + private_esp_packet_t *this) +{ + private_esp_packet_t *pkt; + + pkt = esp_packet_create_internal(this->packet->clone(this->packet)); + pkt->payload = this->payload ? this->payload->clone(this->payload) : NULL; + pkt->next_header = this->next_header; + return &pkt->public.packet; +} + +METHOD(esp_packet_t, parse_header, bool, + private_esp_packet_t *this, u_int32_t *spi) +{ + bio_reader_t *reader; + u_int32_t seq; + + reader = bio_reader_create(this->packet->get_data(this->packet)); + if (!reader->read_uint32(reader, spi) || + !reader->read_uint32(reader, &seq)) + { + DBG1(DBG_ESP, "failed to parse ESP header: invalid length"); + reader->destroy(reader); + return FALSE; + } + reader->destroy(reader); + + DBG2(DBG_ESP, "parsed ESP header with SPI %.8x [seq %u]", *spi, seq); + *spi = htonl(*spi); + return TRUE; +} + +/** + * Check padding as specified in RFC 4303 + */ +static bool check_padding(chunk_t padding) +{ + size_t i; + + for (i = 0; i < padding.len; ++i) + { + if (padding.ptr[i] != (u_int8_t)(i + 1)) + { + return FALSE; + } + } + return TRUE; +} + +/** + * Remove the padding from the payload and set the next header info + */ +static bool remove_padding(private_esp_packet_t *this, chunk_t plaintext) +{ + u_int8_t next_header, pad_length; + chunk_t padding, payload; + bio_reader_t *reader; + + reader = bio_reader_create(plaintext); + if (!reader->read_uint8_end(reader, &next_header) || + !reader->read_uint8_end(reader, &pad_length)) + { + DBG1(DBG_ESP, "parsing ESP payload failed: invalid length"); + goto failed; + } + if (!reader->read_data_end(reader, pad_length, &padding) || + !check_padding(padding)) + { + DBG1(DBG_ESP, "parsing ESP payload failed: invalid padding"); + goto failed; + } + this->payload = ip_packet_create(reader->peek(reader)); + reader->destroy(reader); + if (!this->payload) + { + DBG1(DBG_ESP, "parsing ESP payload failed: unsupported payload"); + return FALSE; + } + this->next_header = next_header; + payload = this->payload->get_encoding(this->payload); + + DBG3(DBG_ESP, "ESP payload:\n payload %B\n padding %B\n " + "padding length = %hhu, next header = %hhu", &payload, &padding, + pad_length, this->next_header); + return TRUE; + +failed: + reader->destroy(reader); + chunk_free(&plaintext); + return FALSE; +} + +METHOD(esp_packet_t, decrypt, status_t, + private_esp_packet_t *this, esp_context_t *esp_context) +{ + bio_reader_t *reader; + u_int32_t spi, seq; + chunk_t data, iv, icv, ciphertext, plaintext; + crypter_t *crypter; + signer_t *signer; + + DESTROY_IF(this->payload); + this->payload = NULL; + + data = this->packet->get_data(this->packet); + crypter = esp_context->get_crypter(esp_context); + signer = esp_context->get_signer(esp_context); + + reader = bio_reader_create(data); + if (!reader->read_uint32(reader, &spi) || + !reader->read_uint32(reader, &seq) || + !reader->read_data(reader, crypter->get_iv_size(crypter), &iv) || + !reader->read_data_end(reader, signer->get_block_size(signer), &icv) || + reader->remaining(reader) % crypter->get_block_size(crypter)) + { + DBG1(DBG_ESP, "ESP decryption failed: invalid length"); + return PARSE_ERROR; + } + ciphertext = reader->peek(reader); + reader->destroy(reader); + + if (!esp_context->verify_seqno(esp_context, seq)) + { + DBG1(DBG_ESP, "ESP sequence number verification failed:\n " + "src %H, dst %H, SPI %.8x [seq %u]", + get_source(this), get_destination(this), spi, seq); + return VERIFY_ERROR; + } + DBG3(DBG_ESP, "ESP decryption:\n SPI %.8x [seq %u]\n IV %B\n " + "encrypted %B\n ICV %B", spi, seq, &iv, &ciphertext, &icv); + + if (!signer->get_signature(signer, chunk_create(data.ptr, 8), NULL) || + !signer->get_signature(signer, iv, NULL) || + !signer->verify_signature(signer, ciphertext, icv)) + { + DBG1(DBG_ESP, "ICV verification failed!"); + return FAILED; + } + esp_context->set_authenticated_seqno(esp_context, seq); + + if (!crypter->decrypt(crypter, ciphertext, iv, &plaintext)) + { + DBG1(DBG_ESP, "ESP decryption failed"); + return FAILED; + } + + if (!remove_padding(this, plaintext)) + { + return PARSE_ERROR; + } + return SUCCESS; +} + +/** + * Generate the padding as specified in RFC4303 + */ +static void generate_padding(chunk_t padding) +{ + size_t i; + + for (i = 0; i < padding.len; ++i) + { + padding.ptr[i] = (u_int8_t)(i + 1); + } +} + +METHOD(esp_packet_t, encrypt, status_t, + private_esp_packet_t *this, esp_context_t *esp_context, u_int32_t spi) +{ + chunk_t iv, icv, padding, payload, ciphertext, auth_data; + bio_writer_t *writer; + u_int32_t next_seqno; + size_t blocksize, plainlen; + crypter_t *crypter; + signer_t *signer; + rng_t *rng; + + this->packet->set_data(this->packet, chunk_empty); + + if (!esp_context->next_seqno(esp_context, &next_seqno)) + { + DBG1(DBG_ESP, "ESP encapsulation failed: sequence numbers cycled"); + return FAILED; + } + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_ESP, "ESP encryption failed: could not find RNG"); + return NOT_FOUND; + } + crypter = esp_context->get_crypter(esp_context); + signer = esp_context->get_signer(esp_context); + + blocksize = crypter->get_block_size(crypter); + iv.len = crypter->get_iv_size(crypter); + icv.len = signer->get_block_size(signer); + + /* plaintext = payload, padding, pad_length, next_header */ + payload = this->payload ? this->payload->get_encoding(this->payload) + : chunk_empty; + plainlen = payload.len + 2; + padding.len = blocksize - (plainlen % blocksize); + plainlen += padding.len; + + /* len = spi, seq, IV, plaintext, ICV */ + writer = bio_writer_create(2 * sizeof(u_int32_t) + iv.len + plainlen + + icv.len); + writer->write_uint32(writer, ntohl(spi)); + writer->write_uint32(writer, next_seqno); + + iv = writer->skip(writer, iv.len); + if (!rng->get_bytes(rng, iv.len, iv.ptr)) + { + DBG1(DBG_ESP, "ESP encryption failed: could not generate IV"); + writer->destroy(writer); + rng->destroy(rng); + return FAILED; + } + rng->destroy(rng); + + /* plain-/ciphertext will start here */ + ciphertext = writer->get_buf(writer); + ciphertext.ptr += ciphertext.len; + ciphertext.len = plainlen; + + writer->write_data(writer, payload); + + padding = writer->skip(writer, padding.len); + generate_padding(padding); + + writer->write_uint8(writer, padding.len); + writer->write_uint8(writer, this->next_header); + + DBG3(DBG_ESP, "ESP before encryption:\n payload = %B\n padding = %B\n " + "padding length = %hhu, next header = %hhu", &payload, &padding, + (u_int8_t)padding.len, this->next_header); + + /* encrypt the content inline */ + if (!crypter->encrypt(crypter, ciphertext, iv, NULL)) + { + DBG1(DBG_ESP, "ESP encryption failed"); + writer->destroy(writer); + return FAILED; + } + + /* calculate signature */ + auth_data = writer->get_buf(writer); + icv = writer->skip(writer, icv.len); + if (!signer->get_signature(signer, auth_data, icv.ptr)) + { + DBG1(DBG_ESP, "ESP encryption failed: signature generation failed"); + writer->destroy(writer); + return FAILED; + } + + DBG3(DBG_ESP, "ESP packet:\n SPI %.8x [seq %u]\n IV %B\n " + "encrypted %B\n ICV %B", ntohl(spi), next_seqno, &iv, + &ciphertext, &icv); + + this->packet->set_data(this->packet, writer->extract_buf(writer)); + writer->destroy(writer); + return SUCCESS; +} + +METHOD(esp_packet_t, get_next_header, u_int8_t, + private_esp_packet_t *this) +{ + return this->next_header; +} + +METHOD(esp_packet_t, get_payload, ip_packet_t*, + private_esp_packet_t *this) +{ + return this->payload; +} + +METHOD(esp_packet_t, extract_payload, ip_packet_t*, + private_esp_packet_t *this) +{ + ip_packet_t *payload; + + payload = this->payload; + this->payload = NULL; + return payload; +} + +METHOD2(esp_packet_t, packet_t, destroy, void, + private_esp_packet_t *this) +{ + DESTROY_IF(this->payload); + this->packet->destroy(this->packet); + free(this); +} + +static private_esp_packet_t *esp_packet_create_internal(packet_t *packet) +{ + private_esp_packet_t *this; + + INIT(this, + .public = { + .packet = { + .set_source = _set_source, + .get_source = _get_source, + .set_destination = _set_destination, + .get_destination = _get_destination, + .get_data = _get_data, + .set_data = _set_data, + .skip_bytes = _skip_bytes, + .clone = _clone, + .destroy = _destroy, + }, + .get_source = _get_source, + .get_destination = _get_destination, + .get_next_header = _get_next_header, + .parse_header = _parse_header, + .decrypt = _decrypt, + .encrypt = _encrypt, + .get_payload = _get_payload, + .extract_payload = _extract_payload, + .destroy = _destroy, + }, + .packet = packet, + .next_header = IPPROTO_NONE, + ); + return this; +} + +/** + * Described in header. + */ +esp_packet_t *esp_packet_create_from_packet(packet_t *packet) +{ + private_esp_packet_t *this; + + this = esp_packet_create_internal(packet); + + return &this->public; +} + +/** + * Described in header. + */ +esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, + ip_packet_t *payload) +{ + private_esp_packet_t *this; + packet_t *packet; + + packet = packet_create_from_data(src, dst, chunk_empty); + this = esp_packet_create_internal(packet); + this->payload = payload; + if (payload) + { + this->next_header = payload->get_version(payload) == 4 ? IPPROTO_IPIP + : IPPROTO_IPV6; + } + else + { + this->next_header = IPPROTO_NONE; + } + return &this->public; +} diff --git a/src/libipsec/esp_packet.h b/src/libipsec/esp_packet.h new file mode 100644 index 000000000..a1d1602c1 --- /dev/null +++ b/src/libipsec/esp_packet.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 esp_packet esp_packet + * @{ @ingroup libipsec + */ + +#ifndef ESP_PACKET_H_ +#define ESP_PACKET_H_ + +#include "ip_packet.h" +#include "esp_context.h" + +#include <library.h> +#include <utils/host.h> +#include <utils/packet.h> + +typedef struct esp_packet_t esp_packet_t; + +/** + * ESP packet + */ +struct esp_packet_t { + + /** + * Implements packet_t interface to access the raw ESP packet + */ + packet_t packet; + + /** + * Get the source address of this packet + * + * @return source host + */ + host_t *(*get_source)(esp_packet_t *this); + + /** + * Get the destination address of this packet + * + * @return destination host + */ + host_t *(*get_destination)(esp_packet_t *this); + + /** + * Parse the packet header before decryption. Tries to read the SPI + * from the packet to find a corresponding SA. + * + * @param spi parsed SPI, in network byte order + * @return TRUE when successful, FALSE otherwise (e.g. when the + * length of the packet is invalid) + */ + bool (*parse_header)(esp_packet_t *this, u_int32_t *spi); + + /** + * Authenticate and decrypt the packet. Also verifies the sequence number + * using the supplied ESP context and updates the anti-replay window. + * + * @param esp_context ESP context of corresponding inbound IPsec SA + * @return - SUCCESS if successfully authenticated, + * decrypted and parsed + * - PARSE_ERROR if the length of the packet or the + * padding is invalid + * - VERIFY_ERROR if the sequence number + * verification failed + * - FAILED if the ICV (MAC) check or the actual + * decryption failed + */ + status_t (*decrypt)(esp_packet_t *this, esp_context_t *esp_context); + + /** + * Encapsulate and encrypt the packet. The sequence number will be generated + * using the supplied ESP context. + * + * @param esp_context ESP context of corresponding outbound IPsec SA + * @param spi SPI value to use, in network byte order + * @return - SUCCESS if encrypted + * - FAILED if sequence number cycled or any of the + * cryptographic functions failed + * - NOT_FOUND if no suitable RNG could be found + */ + status_t (*encrypt)(esp_packet_t *this, esp_context_t *esp_context, + u_int32_t spi); + + /** + * Get the next header field of a packet. + * + * @note Packet has to be in the decrypted state. + * + * @return next header field + */ + u_int8_t (*get_next_header)(esp_packet_t *this); + + /** + * Get the plaintext payload of this packet. + * + * @return plaintext payload (internal data), + * NULL if not decrypted + */ + ip_packet_t *(*get_payload)(esp_packet_t *this); + + /** + * Extract the plaintext payload from this packet. + * + * @return plaintext payload (has to be destroyed), + * NULL if not decrypted + */ + ip_packet_t *(*extract_payload)(esp_packet_t *this); + + /** + * Destroy an esp_packet_t + */ + void (*destroy)(esp_packet_t *this); + +}; + +/** + * Create an ESP packet out of data from the wire. + * + * @param packet the packet data as received, gets owned + * @return esp_packet_t instance + */ +esp_packet_t *esp_packet_create_from_packet(packet_t *packet); + +/** + * Create an ESP packet from a plaintext payload + * + * @param src source address + * @param dst destination address + * @param payload plaintext payload, gets owned + * @return esp_packet_t instance + */ +esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, + ip_packet_t *payload); + +#endif /** ESP_PACKET_H_ @}*/ + diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c new file mode 100644 index 000000000..4593ba5c8 --- /dev/null +++ b/src/libipsec/ip_packet.c @@ -0,0 +1,192 @@ +/* + * 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 "ip_packet.h" + +#include <library.h> +#include <debug.h> + +#include <netinet/in.h> +#include <netinet/ip.h> +#ifdef HAVE_NETINET_IP6_H +#include <netinet/ip6.h> +#endif + +typedef struct private_ip_packet_t private_ip_packet_t; + +/** + * Private additions to ip_packet_t. + */ +struct private_ip_packet_t { + + /** + * Public members + */ + ip_packet_t public; + + /** + * Source address + */ + host_t *src; + + /** + * Destination address + */ + host_t *dst; + + /** + * IP packet + */ + chunk_t packet; + + /** + * IP version + */ + u_int8_t version; + + /** + * Protocol|Next Header field + */ + u_int8_t next_header; + +}; + +METHOD(ip_packet_t, get_version, u_int8_t, + private_ip_packet_t *this) +{ + return this->version; +} + +METHOD(ip_packet_t, get_source, host_t*, + private_ip_packet_t *this) +{ + return this->src; +} + +METHOD(ip_packet_t, get_destination, host_t*, + private_ip_packet_t *this) +{ + return this->dst; +} + +METHOD(ip_packet_t, get_encoding, chunk_t, + private_ip_packet_t *this) +{ + return this->packet; +} + +METHOD(ip_packet_t, get_next_header, u_int8_t, + private_ip_packet_t *this) +{ + return this->next_header; +} + +METHOD(ip_packet_t, clone, ip_packet_t*, + private_ip_packet_t *this) +{ + return ip_packet_create(this->packet); +} + +METHOD(ip_packet_t, destroy, void, + private_ip_packet_t *this) +{ + this->src->destroy(this->src); + this->dst->destroy(this->dst); + chunk_free(&this->packet); + free(this); +} + +/** + * Described in header. + */ +ip_packet_t *ip_packet_create(chunk_t packet) +{ + private_ip_packet_t *this; + u_int8_t version, next_header; + host_t *src, *dst; + + if (packet.len < 1) + { + DBG1(DBG_ESP, "IP packet too short"); + goto failed; + } + + version = (packet.ptr[0] & 0xf0) >> 4; + + switch (version) + { + case 4: + { + struct iphdr *ip; + + if (packet.len < sizeof(struct iphdr)) + { + DBG1(DBG_ESP, "IPv4 packet too short"); + goto failed; + } + ip = (struct iphdr*)packet.ptr; + src = host_create_from_chunk(AF_INET, + chunk_from_thing(ip->saddr), 0); + dst = host_create_from_chunk(AF_INET, + chunk_from_thing(ip->daddr), 0); + next_header = ip->protocol; + break; + } +#ifdef HAVE_NETINET_IP6_H + case 6: + { + struct ip6_hdr *ip; + + if (packet.len < sizeof(struct ip6_hdr)) + { + DBG1(DBG_ESP, "IPv6 packet too short"); + goto failed; + } + ip = (struct ip6_hdr*)packet.ptr; + src = host_create_from_chunk(AF_INET6, + chunk_from_thing(ip->ip6_src), 0); + dst = host_create_from_chunk(AF_INET6, + chunk_from_thing(ip->ip6_dst), 0); + next_header = ip->ip6_nxt; + } +#endif /* HAVE_NETINET_IP6_H */ + default: + DBG1(DBG_ESP, "unsupported IP version"); + goto failed; + } + + INIT(this, + .public = { + .get_version = _get_version, + .get_source = _get_source, + .get_destination = _get_destination, + .get_next_header = _get_next_header, + .get_encoding = _get_encoding, + .clone = _clone, + .destroy = _destroy, + }, + .src = src, + .dst = dst, + .packet = packet, + .version = version, + .next_header = next_header, + ); + return &this->public; + +failed: + chunk_free(&packet); + return NULL; +} diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h new file mode 100644 index 000000000..b4fc298ff --- /dev/null +++ b/src/libipsec/ip_packet.h @@ -0,0 +1,96 @@ +/* + * 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 ip_packet ip_packet + * @{ @ingroup libipsec + */ + +#ifndef IP_PACKET_H_ +#define IP_PACKET_H_ + +#include <library.h> +#include <utils/host.h> +#include <utils/packet.h> + +typedef struct ip_packet_t ip_packet_t; + +/** + * IP packet + */ +struct ip_packet_t { + + /** + * IP version of this packet + * + * @return ip version + */ + u_int8_t (*get_version)(ip_packet_t *this); + + /** + * Get the source address of this packet + * + * @return source host + */ + host_t *(*get_source)(ip_packet_t *this); + + /** + * Get the destination address of this packet + * + * @return destination host + */ + host_t *(*get_destination)(ip_packet_t *this); + + /** + * Get the protocol (IPv4) or next header (IPv6) field of this packet. + * + * @return protocol|next header field + */ + u_int8_t (*get_next_header)(ip_packet_t *this); + + /** + * Get the complete IP packet (including the header) + * + * @return IP packet (internal data) + */ + chunk_t (*get_encoding)(ip_packet_t *this); + + /** + * Clone the IP packet + * + * @return clone of the packet + */ + ip_packet_t *(*clone)(ip_packet_t *this); + + /** + * Destroy an ip_packet_t + */ + void (*destroy)(ip_packet_t *this); + +}; + +/** + * Create an IP packet out of data from the wire (or decapsulated from another + * packet). + * + * @note The raw IP packet gets either owned by the new object, or destroyed, + * if the data is invalid. + * + * @param packet the IP packet (including header), gets owned + * @return ip_packet_t instance, or NULL if invalid + */ +ip_packet_t *ip_packet_create(chunk_t packet); + +#endif /** IP_PACKET_H_ @}*/ diff --git a/src/libipsec/ipsec.c b/src/libipsec/ipsec.c index add3b463a..50d9163ea 100644 --- a/src/libipsec/ipsec.c +++ b/src/libipsec/ipsec.c @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager * Copyright (C) 2012 Tobias Brunner * Hochschule fuer Technik Rapperswil * @@ -41,6 +43,10 @@ ipsec_t *ipsec; void libipsec_deinit() { private_ipsec_t *this = (private_ipsec_t*)ipsec; + DESTROY_IF(this->public.processor); + DESTROY_IF(this->public.events); + DESTROY_IF(this->public.policies); + DESTROY_IF(this->public.sas); free(this); ipsec = NULL; } @@ -52,10 +58,7 @@ bool libipsec_init() { private_ipsec_t *this; - INIT(this, - .public = { - }, - ); + INIT(this); ipsec = &this->public; if (lib->integrity && @@ -64,6 +67,11 @@ bool libipsec_init() DBG1(DBG_LIB, "integrity check of libipsec failed"); return FALSE; } + + this->public.sas = ipsec_sa_mgr_create(); + this->public.policies = ipsec_policy_mgr_create(); + this->public.events = ipsec_event_relay_create(); + this->public.processor = ipsec_processor_create(); return TRUE; } diff --git a/src/libipsec/ipsec.h b/src/libipsec/ipsec.h index 80bef5426..7ee49432a 100644 --- a/src/libipsec/ipsec.h +++ b/src/libipsec/ipsec.h @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager * Copyright (C) 2012 Tobias Brunner * Hochschule fuer Technik Rapperswil * @@ -23,15 +25,40 @@ #ifndef IPSEC_H_ #define IPSEC_H_ -typedef struct ipsec_t ipsec_t; +#include "ipsec_sa_mgr.h" +#include "ipsec_policy_mgr.h" +#include "ipsec_event_relay.h" +#include "ipsec_processor.h" #include <library.h> +typedef struct ipsec_t ipsec_t; + /** * User space IPsec implementation. */ struct ipsec_t { + /** + * IPsec SA manager instance + */ + ipsec_sa_mgr_t *sas; + + /** + * IPsec policy manager instance + */ + ipsec_policy_mgr_t *policies; + + /** + * Event relay instance + */ + ipsec_event_relay_t *events; + + /** + * IPsec processor instance + */ + ipsec_processor_t *processor; + }; /** diff --git a/src/libipsec/ipsec_event_listener.h b/src/libipsec/ipsec_event_listener.h new file mode 100644 index 000000000..c5c39b0f1 --- /dev/null +++ b/src/libipsec/ipsec_event_listener.h @@ -0,0 +1,48 @@ +/* + * 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 ipsec_event_listener ipsec_event_listener + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_EVENT_LISTENER_H_ +#define IPSEC_EVENT_LISTENER_H_ + +typedef struct ipsec_event_listener_t ipsec_event_listener_t; + +#include <library.h> + +/** + * Listener interface for IPsec events + * + * All methods are optional. + */ +struct ipsec_event_listener_t { + + /** + * Called when the lifetime of an IPsec SA expired + * + * @param reqid reqid of the expired SA + * @param protocol protocol of the expired SA + * @param spi spi of the expired SA + * @param hard TRUE if this is a hard expire, FALSE otherwise + */ + void (*expire)(u_int32_t reqid, u_int8_t protocol, u_int32_t spi, + bool hard); + +}; + +#endif /** IPSEC_EVENT_LISTENER_H_ @}*/ diff --git a/src/libipsec/ipsec_event_relay.c b/src/libipsec/ipsec_event_relay.c new file mode 100644 index 000000000..34222258c --- /dev/null +++ b/src/libipsec/ipsec_event_relay.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "ipsec_event_relay.h" + +#include <library.h> +#include <debug.h> +#include <threading/rwlock.h> +#include <utils/linked_list.h> +#include <utils/blocking_queue.h> +#include <processing/jobs/callback_job.h> + +typedef struct private_ipsec_event_relay_t private_ipsec_event_relay_t; + +/** + * Private additions to ipsec_event_relay_t. + */ +struct private_ipsec_event_relay_t { + + /** + * Public members + */ + ipsec_event_relay_t public; + + /** + * Registered listeners + */ + linked_list_t *listeners; + + /** + * Lock to safely access the list of listeners + */ + rwlock_t *lock; + + /** + * Blocking queue for events + */ + blocking_queue_t *queue; +}; + +/** + * Helper struct used to manage events in a queue + */ +typedef struct { + + /** + * Type of the event + */ + enum { + IPSEC_EVENT_EXPIRE, + } type; + + /** + * Reqid of the SA, if any + */ + u_int32_t reqid; + + /** + * SPI of the SA, if any + */ + u_int32_t spi; + + /** + * Additional data for specific event types + */ + union { + + struct { + /** Protocol of the SA */ + u_int8_t protocol; + /** TRUE in case of a hard expire */ + bool hard; + } expire; + + } data; + +} ipsec_event_t; + +/** + * Dequeue events and relay them to listeners + */ +static job_requeue_t handle_events(private_ipsec_event_relay_t *this) +{ + enumerator_t *enumerator; + ipsec_event_listener_t *current; + ipsec_event_t *event; + + event = this->queue->dequeue(this->queue); + + this->lock->read_lock(this->lock); + enumerator = this->listeners->create_enumerator(this->listeners); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + switch (event->type) + { + case IPSEC_EVENT_EXPIRE: + if (current->expire) + { + current->expire(event->reqid, event->data.expire.protocol, + event->spi, event->data.expire.hard); + } + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return JOB_REQUEUE_DIRECT; +} + +METHOD(ipsec_event_relay_t, expire, void, + private_ipsec_event_relay_t *this, u_int32_t reqid, u_int8_t protocol, + u_int32_t spi, bool hard) +{ + ipsec_event_t *event; + + INIT(event, + .type = IPSEC_EVENT_EXPIRE, + .reqid = reqid, + .spi = spi, + .data = { + .expire = { + .protocol = protocol, + .hard = hard, + }, + }, + ); + this->queue->enqueue(this->queue, event); +} + +METHOD(ipsec_event_relay_t, register_listener, void, + private_ipsec_event_relay_t *this, ipsec_event_listener_t *listener) +{ + this->lock->write_lock(this->lock); + this->listeners->insert_last(this->listeners, listener); + this->lock->unlock(this->lock); +} + +METHOD(ipsec_event_relay_t, unregister_listener, void, + private_ipsec_event_relay_t *this, ipsec_event_listener_t *listener) +{ + this->lock->write_lock(this->lock); + this->listeners->remove(this->listeners, listener, NULL); + this->lock->unlock(this->lock); +} + +METHOD(ipsec_event_relay_t, destroy, void, + private_ipsec_event_relay_t *this) +{ + this->queue->destroy_function(this->queue, free); + this->listeners->destroy(this->listeners); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +ipsec_event_relay_t *ipsec_event_relay_create() +{ + private_ipsec_event_relay_t *this; + + INIT(this, + .public = { + .expire = _expire, + .register_listener = _register_listener, + .unregister_listener = _unregister_listener, + .destroy = _destroy, + }, + .listeners = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .queue = blocking_queue_create(), + ); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_events, this, + NULL, (callback_job_cancel_t)return_false)); + + return &this->public; +} diff --git a/src/libipsec/ipsec_event_relay.h b/src/libipsec/ipsec_event_relay.h new file mode 100644 index 000000000..c6935d546 --- /dev/null +++ b/src/libipsec/ipsec_event_relay.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 ipsec_event_relay ipsec_event_relay + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_EVENT_RELAY_H_ +#define IPSEC_EVENT_RELAY_H_ + +#include "ipsec_event_listener.h" + +#include <library.h> + +typedef struct ipsec_event_relay_t ipsec_event_relay_t; + +/** + * Event relay manager. + * + * Used to notify upper layers about changes + */ +struct ipsec_event_relay_t { + + /** + * Raise an expire event. + * + * @param reqid reqid of the expired IPsec SA + * @param protocol protocol (e.g ESP) of the expired SA + * @param spi SPI of the expired SA + * @param hard TRUE for a hard expire, FALSE otherwise + */ + void (*expire)(ipsec_event_relay_t *this, u_int32_t reqid, + u_int8_t protocol, u_int32_t spi, bool hard); + + /** + * Register a listener to events raised by this manager + * + * @param listener the listener to register + */ + void (*register_listener)(ipsec_event_relay_t *this, + ipsec_event_listener_t *listener); + + /** + * Unregister a listener + * + * @param listener the listener to unregister + */ + void (*unregister_listener)(ipsec_event_relay_t *this, + ipsec_event_listener_t *listener); + + /** + * Destroy an ipsec_event_relay_t + */ + void (*destroy)(ipsec_event_relay_t *this); + +}; + +/** + * Create an ipsec_event_relay_t instance + * + * @return IPsec event relay instance + */ +ipsec_event_relay_t *ipsec_event_relay_create(); + +#endif /** IPSEC_EVENT_RELAY_H_ @}*/ diff --git a/src/libipsec/ipsec_policy.c b/src/libipsec/ipsec_policy.c new file mode 100644 index 000000000..af8ea9f9d --- /dev/null +++ b/src/libipsec/ipsec_policy.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "ipsec_policy.h" + +#include <debug.h> + +typedef struct private_ipsec_policy_t private_ipsec_policy_t; + +/** + * Private additions to ipsec_policy_t. + */ +struct private_ipsec_policy_t { + + /** + * Public members + */ + ipsec_policy_t public; + + /** + * SA source address + */ + host_t *src; + + /** + * SA destination address + */ + host_t *dst; + + /** + * Source traffic selector + */ + traffic_selector_t *src_ts; + + /** + * Destination traffic selector + */ + traffic_selector_t *dst_ts; + + /** + * If any of the two TS has a protocol selector we cache it here + */ + u_int8_t protocol; + + /** + * Traffic direction + */ + policy_dir_t direction; + + /** + * Policy type + */ + policy_type_t type; + + /** + * SA configuration + */ + ipsec_sa_cfg_t sa; + + /** + * Mark + */ + mark_t mark; + + /** + * Policy priority + */ + policy_priority_t priority; + + /** + * Reference counter + */ + refcount_t refcount; + +}; + +METHOD(ipsec_policy_t, match, bool, + private_ipsec_policy_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t priority) +{ + return (this->direction == direction && + this->priority == priority && + this->sa.reqid == reqid && + memeq(&this->mark, &mark, sizeof(mark_t)) && + this->src_ts->equals(this->src_ts, src_ts) && + this->dst_ts->equals(this->dst_ts, dst_ts)); +} + +METHOD(ipsec_policy_t, match_packet, bool, + private_ipsec_policy_t *this, ip_packet_t *packet) +{ + u_int8_t proto = packet->get_next_header(packet); + host_t *src = packet->get_source(packet), + *dst = packet->get_destination(packet); + + return (!this->protocol || this->protocol == proto) && + this->src_ts->includes(this->src_ts, src) && + this->dst_ts->includes(this->dst_ts, dst); +} + +METHOD(ipsec_policy_t, get_source_ts, traffic_selector_t*, + private_ipsec_policy_t *this) +{ + return this->src_ts; +} + +METHOD(ipsec_policy_t, get_destination_ts, traffic_selector_t*, + private_ipsec_policy_t *this) +{ + return this->dst_ts; +} + +METHOD(ipsec_policy_t, get_reqid, u_int32_t, + private_ipsec_policy_t *this) +{ + return this->sa.reqid; +} + +METHOD(ipsec_policy_t, get_direction, policy_dir_t, + private_ipsec_policy_t *this) +{ + return this->direction; +} + +METHOD(ipsec_policy_t, get_priority, policy_priority_t, + private_ipsec_policy_t *this) +{ + return this->priority; +} + +METHOD(ipsec_policy_t, get_type, policy_type_t, + private_ipsec_policy_t *this) +{ + return this->type; +} + +METHOD(ipsec_policy_t, get_ref, ipsec_policy_t*, + private_ipsec_policy_t *this) +{ + ref_get(&this->refcount); + return &this->public; +} + +METHOD(ipsec_policy_t, destroy, void, + private_ipsec_policy_t *this) +{ + if (ref_put(&this->refcount)) + { + this->src->destroy(this->src); + this->dst->destroy(this->dst); + this->src_ts->destroy(this->src_ts); + this->dst_ts->destroy(this->dst_ts); + free(this); + } +} + +/** + * Described in header. + */ +ipsec_policy_t *ipsec_policy_create(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) +{ + private_ipsec_policy_t *this; + + INIT(this, + .public = { + .match = _match, + .match_packet = _match_packet, + .get_source_ts = _get_source_ts, + .get_destination_ts = _get_destination_ts, + .get_direction = _get_direction, + .get_priority = _get_priority, + .get_reqid = _get_reqid, + .get_type = _get_type, + .get_ref = _get_ref, + .destroy = _destroy, + }, + .src = src->clone(src), + .dst = dst->clone(dst), + .src_ts = src_ts->clone(src_ts), + .dst_ts = dst_ts->clone(dst_ts), + .protocol = max(src_ts->get_protocol(src_ts), + dst_ts->get_protocol(dst_ts)), + .direction = direction, + .type = type, + .sa = *sa, + .mark = mark, + .priority = priority, + .refcount = 1, + ); + + return &this->public; +} diff --git a/src/libipsec/ipsec_policy.h b/src/libipsec/ipsec_policy.h new file mode 100644 index 000000000..67ad0b0ed --- /dev/null +++ b/src/libipsec/ipsec_policy.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 ipsec_policy ipsec_policy + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_POLICY_H +#define IPSEC_POLICY_H + +#include "ip_packet.h" + +#include <library.h> +#include <utils/host.h> +#include <ipsec/ipsec_types.h> +#include <selectors/traffic_selector.h> + +typedef struct ipsec_policy_t ipsec_policy_t; + +/** + * IPsec Policy + */ +struct ipsec_policy_t { + + /** + * Get the source traffic selector of this policy + * + * @return the source traffic selector + */ + traffic_selector_t *(*get_source_ts)(ipsec_policy_t *this); + + /** + * Get the destination traffic selector of this policy + * + * @return the destination traffic selector + */ + traffic_selector_t *(*get_destination_ts)(ipsec_policy_t *this); + + /** + * Get the direction of this policy + * + * @return direction + */ + policy_dir_t (*get_direction)(ipsec_policy_t *this); + + /** + * Get the priority of this policy + * + * @return priority + */ + policy_priority_t (*get_priority)(ipsec_policy_t *this); + + /** + * Get the type of this policy (e.g. IPsec) + * + * @return the policy type + */ + policy_type_t (*get_type)(ipsec_policy_t *this); + + /** + * Get the reqid associated to this policy + * + * @return the reqid + */ + u_int32_t (*get_reqid)(ipsec_policy_t *this); + + /** + * Get another reference to this policy + * + * @return additional reference to the policy + */ + ipsec_policy_t *(*get_ref)(ipsec_policy_t *this); + + /** + * Check if this policy matches all given parameters + * + * @param src_ts source traffic selector + * @param dst_ts destination traffic selector + * @param direction traffic direction + * @param reqid reqid of the policy + * @param mark mark for this policy + * @param prioirty policy priority + * @return TRUE if policy matches all parameters + */ + bool (*match)(ipsec_policy_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); + + /** + * Check if this policy matches the given IP packet + * + * @param packet IP packet + * @return TRUE if policy matches the packet + */ + bool (*match_packet)(ipsec_policy_t *this, ip_packet_t *packet); + + /** + * Destroy an ipsec_policy_t + */ + void (*destroy)(ipsec_policy_t *this); + +}; + +/** + * Create an ipsec_policy_t instance + * + * @param src source address of SA + * @param dst dest address of SA + * @param src_ts traffic selector to match traffic source + * @param dst_ts traffic selector to match traffic dest + * @param direction direction of traffic, POLICY_(IN|OUT|FWD) + * @param type type of policy, POLICY_(IPSEC|PASS|DROP) + * @param sa details about the SA(s) tied to this policy + * @param mark mark for this policy + * @param priority priority of this policy + * @return ipsec policy instance + */ +ipsec_policy_t *ipsec_policy_create(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); + +#endif /** IPSEC_POLICY_H @}*/ diff --git a/src/libipsec/ipsec_policy_mgr.c b/src/libipsec/ipsec_policy_mgr.c new file mode 100644 index 000000000..41ba792c3 --- /dev/null +++ b/src/libipsec/ipsec_policy_mgr.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "ipsec_policy_mgr.h" + +#include <debug.h> +#include <threading/rwlock.h> +#include <utils/linked_list.h> + +/** Base priority for installed policies */ +#define PRIO_BASE 512 + +typedef struct private_ipsec_policy_mgr_t private_ipsec_policy_mgr_t; + +/** + * Private additions to ipsec_policy_mgr_t. + */ +struct private_ipsec_policy_mgr_t { + + /** + * Public members of ipsec_policy_mgr_t. + */ + ipsec_policy_mgr_t public; + + /** + * Installed policies (ipsec_policy_entry_t*) + */ + linked_list_t *policies; + + /** + * Lock to safely access the list of policies + */ + rwlock_t *lock; + +}; + +/** + * Helper struct to store policies in a list sorted by the same pseudo-priority + * used by the NETLINK kernel interface. + */ +typedef struct { + + /** + * Priority used to sort policies + */ + u_int32_t priority; + + /** + * The policy + */ + ipsec_policy_t *policy; + +} ipsec_policy_entry_t; + +/** + * Calculate the pseudo-priority to sort policies. This is the same algorithm + * used by the NETLINK kernel interface (i.e. high priority -> low value). + */ +static u_int32_t calculate_priority(policy_priority_t policy_priority, + traffic_selector_t *src, + traffic_selector_t *dst) +{ + u_int32_t priority = PRIO_BASE; + u_int16_t port; + u_int8_t mask, proto; + host_t *net; + + switch (policy_priority) + { + case POLICY_PRIORITY_FALLBACK: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_ROUTED: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_DEFAULT: + break; + } + /* calculate priority based on selector size, small size = high prio */ + src->to_subnet(src, &net, &mask); + priority -= mask; + proto = src->get_protocol(src); + port = net->get_port(net); + net->destroy(net); + + dst->to_subnet(dst, &net, &mask); + priority -= mask; + proto = max(proto, dst->get_protocol(dst)); + port = max(port, net->get_port(net)); + net->destroy(net); + + priority <<= 2; /* make some room for the two flags */ + priority += port ? 0 : 2; + priority += proto ? 0 : 1; + return priority; +} + +/** + * Create a policy entry + */ +static ipsec_policy_entry_t *policy_entry_create(ipsec_policy_t *policy) +{ + ipsec_policy_entry_t *this; + + INIT(this, + .policy = policy, + .priority = calculate_priority(policy->get_priority(policy), + policy->get_source_ts(policy), + policy->get_destination_ts(policy)), + ); + return this; +} + +/** + * Destroy a policy entry + */ +static void policy_entry_destroy(ipsec_policy_entry_t *this) +{ + this->policy->destroy(this->policy); + free(this); +} + +METHOD(ipsec_policy_mgr_t, add_policy, status_t, + private_ipsec_policy_mgr_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) +{ + enumerator_t *enumerator; + ipsec_policy_entry_t *entry, *current; + ipsec_policy_t *policy; + + if (type != POLICY_IPSEC || direction == POLICY_FWD) + { /* we ignore these policies as we currently have no use for them */ + return SUCCESS; + } + + DBG2(DBG_ESP, "adding policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + + policy = ipsec_policy_create(src, dst, src_ts, dst_ts, direction, type, sa, + mark, priority); + entry = policy_entry_create(policy); + + this->lock->write_lock(this->lock); + enumerator = this->policies->create_enumerator(this->policies); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (current->priority >= entry->priority) + { + break; + } + } + this->policies->insert_before(this->policies, enumerator, entry); + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return SUCCESS; +} + +METHOD(ipsec_policy_mgr_t, del_policy, status_t, + private_ipsec_policy_mgr_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 policy_priority) +{ + enumerator_t *enumerator; + ipsec_policy_entry_t *current, *found = NULL; + u_int32_t priority; + + if (direction == POLICY_FWD) + { /* we ignore these policies as we currently have no use for them */ + return SUCCESS; + } + DBG2(DBG_ESP, "deleting policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + + priority = calculate_priority(policy_priority, src_ts, dst_ts); + + this->lock->write_lock(this->lock); + enumerator = this->policies->create_enumerator(this->policies); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (current->priority == priority && + current->policy->match(current->policy, src_ts, dst_ts, direction, + reqid, mark, policy_priority)) + { + this->policies->remove_at(this->policies, enumerator); + found = current; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + if (found) + { + policy_entry_destroy(found); + return SUCCESS; + } + return FAILED; +} + +METHOD(ipsec_policy_mgr_t, flush_policies, status_t, + private_ipsec_policy_mgr_t *this) +{ + ipsec_policy_entry_t *entry; + + DBG2(DBG_ESP, "flushing policies"); + + this->lock->write_lock(this->lock); + while (this->policies->remove_last(this->policies, + (void**)&entry) == SUCCESS) + { + policy_entry_destroy(entry); + } + this->lock->unlock(this->lock); + return SUCCESS; +} + +METHOD(ipsec_policy_mgr_t, find_by_packet, ipsec_policy_t*, + private_ipsec_policy_mgr_t *this, ip_packet_t *packet, bool inbound) +{ + enumerator_t *enumerator; + ipsec_policy_entry_t *current; + ipsec_policy_t *found = NULL; + + this->lock->read_lock(this->lock); + enumerator = this->policies->create_enumerator(this->policies); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + ipsec_policy_t *policy = current->policy; + + if ((inbound == (policy->get_direction(policy) == POLICY_IN)) && + policy->match_packet(policy, packet)) + { + found = policy->get_ref(policy); + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return found; +} + +METHOD(ipsec_policy_mgr_t, destroy, void, + private_ipsec_policy_mgr_t *this) +{ + flush_policies(this); + this->policies->destroy(this->policies); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +ipsec_policy_mgr_t *ipsec_policy_mgr_create() +{ + private_ipsec_policy_mgr_t *this; + + INIT(this, + .public = { + .add_policy = _add_policy, + .del_policy = _del_policy, + .flush_policies = _flush_policies, + .find_by_packet = _find_by_packet, + .destroy = _destroy, + }, + .policies = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/libipsec/ipsec_policy_mgr.h b/src/libipsec/ipsec_policy_mgr.h new file mode 100644 index 000000000..d3ee1074f --- /dev/null +++ b/src/libipsec/ipsec_policy_mgr.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 ipsec_policy_mgr ipsec_policy_mgr + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_POLICY_MGR_H_ +#define IPSEC_POLICY_MGR_H_ + +#include "ipsec_policy.h" +#include "ip_packet.h" + +#include <library.h> +#include <utils/host.h> +#include <utils/linked_list.h> +#include <ipsec/ipsec_types.h> +#include <selectors/traffic_selector.h> + +typedef struct ipsec_policy_mgr_t ipsec_policy_mgr_t; + +/** + * IPsec policy manager + * + * The first methods are modeled after those in kernel_ipsec_t. + * + * @note Only policies of type POLICY_IPSEC are currently used, also policies + * with direction POLICY_FWD are ignored. Any packets that do not match an + * installed policy will be dropped. + */ +struct ipsec_policy_mgr_t { + + /** + * Add a policy + * + * A policy is always associated to an SA. Traffic which matches a + * policy is handled by the SA with the same reqid. + * + * @param src source address of SA + * @param dst dest address of SA + * @param src_ts traffic selector to match traffic source + * @param dst_ts traffic selector to match traffic dest + * @param direction direction of traffic, POLICY_(IN|OUT|FWD) + * @param type type of policy, POLICY_(IPSEC|PASS|DROP) + * @param sa details about the SA(s) tied to this policy + * @param mark mark for this policy + * @param priority priority of this policy + * @return SUCCESS if operation completed + */ + status_t (*add_policy)(ipsec_policy_mgr_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); + + /** + * Remove a policy + * + * @param src_ts traffic selector to match traffic source + * @param dst_ts traffic selector to match traffic dest + * @param direction direction of traffic, POLICY_(IN|OUT|FWD) + * @param reqid unique ID of the associated SA + * @param mark optional mark + * @param priority priority of the policy + * @return SUCCESS if operation completed + */ + status_t (*del_policy)(ipsec_policy_mgr_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); + + /** + * Flush all policies + * + * @return SUCCESS if operation completed + */ + status_t (*flush_policies)(ipsec_policy_mgr_t *this); + + /** + * Find the policy that matches the given IP packet best + * + * @param packet IP packet to match + * @param inbound TRUE for an inbound packet + * @return reference to the policy, or NULL if none found + */ + ipsec_policy_t *(*find_by_packet)(ipsec_policy_mgr_t *this, + ip_packet_t *packet, bool inbound); + + /** + * Destroy an ipsec_policy_mgr_t + */ + void (*destroy)(ipsec_policy_mgr_t *this); + +}; + +/** + * Create an ipsec_policy_mgr instance + * + * @return ipsec_policy_mgr + */ +ipsec_policy_mgr_t *ipsec_policy_mgr_create(); + +#endif /** IPSEC_POLICY_MGR_H_ @}*/ diff --git a/src/libipsec/ipsec_processor.c b/src/libipsec/ipsec_processor.c new file mode 100644 index 000000000..a91d9e074 --- /dev/null +++ b/src/libipsec/ipsec_processor.c @@ -0,0 +1,324 @@ +/* + * 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 "ipsec.h" +#include "ipsec_processor.h" + +#include <debug.h> +#include <library.h> +#include <threading/rwlock.h> +#include <utils/blocking_queue.h> +#include <processing/jobs/callback_job.h> + +typedef struct private_ipsec_processor_t private_ipsec_processor_t; + +/** + * Private additions to ipsec_processor_t. + */ +struct private_ipsec_processor_t { + + /** + * Public members + */ + ipsec_processor_t public; + + /** + * Queue for inbound packets (esp_packet_t*) + */ + blocking_queue_t *inbound_queue; + + /** + * Queue for outbound packets (ip_packet_t*) + */ + blocking_queue_t *outbound_queue; + + /** + * Registered inbound callback + */ + struct { + ipsec_inbound_cb_t cb; + void *data; + } inbound; + + /** + * Registered outbound callback + */ + struct { + ipsec_outbound_cb_t cb; + void *data; + } outbound; + + /** + * Lock used to synchronize access to the callbacks + */ + rwlock_t *lock; +}; + +/** + * Deliver an inbound IP packet to the registered listener + */ +static void deliver_inbound(private_ipsec_processor_t *this, + esp_packet_t *packet) +{ + this->lock->read_lock(this->lock); + if (this->inbound.cb) + { + this->inbound.cb(this->inbound.data, packet->extract_payload(packet)); + } + else + { + DBG2(DBG_ESP, "no inbound callback registered, dropping packet"); + } + packet->destroy(packet); + this->lock->unlock(this->lock); +} + +/** + * Processes inbound packets + */ +static job_requeue_t process_inbound(private_ipsec_processor_t *this) +{ + esp_packet_t *packet; + ipsec_sa_t *sa; + u_int8_t next_header; + u_int32_t spi; + + packet = (esp_packet_t*)this->inbound_queue->dequeue(this->inbound_queue); + + if (!packet->parse_header(packet, &spi)) + { + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + + sa = ipsec->sas->checkout_by_spi(ipsec->sas, spi, + packet->get_destination(packet)); + if (!sa) + { + DBG2(DBG_ESP, "inbound ESP packet does not belong to an installed SA"); + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + + if (!sa->is_inbound(sa)) + { + DBG1(DBG_ESP, "error: IPsec SA is not inbound"); + packet->destroy(packet); + ipsec->sas->checkin(ipsec->sas, sa); + return JOB_REQUEUE_DIRECT; + } + + if (packet->decrypt(packet, sa->get_esp_context(sa)) != SUCCESS) + { + ipsec->sas->checkin(ipsec->sas, sa); + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + ipsec->sas->checkin(ipsec->sas, sa); + + next_header = packet->get_next_header(packet); + switch (next_header) + { + case IPPROTO_IPIP: + case IPPROTO_IPV6: + { + ipsec_policy_t *policy; + ip_packet_t *ip_packet; + + ip_packet = packet->get_payload(packet); + policy = ipsec->policies->find_by_packet(ipsec->policies, + ip_packet, TRUE); + if (policy) + { /* TODO-IPSEC: update policy/sa stats? */ + deliver_inbound(this, packet); + policy->destroy(policy); + break; + } + DBG1(DBG_ESP, "discarding inbound IP packet due to policy"); + /* no matching policy found, fall-through */ + } + case IPPROTO_NONE: + /* discard dummy packets */ + /* fall-through */ + default: + packet->destroy(packet); + break; + } + return JOB_REQUEUE_DIRECT; +} + +/** + * Send an ESP packet using the registered outbound callback + */ +static void send_outbound(private_ipsec_processor_t *this, + esp_packet_t *packet) +{ + this->lock->read_lock(this->lock); + if (this->outbound.cb) + { + this->outbound.cb(this->outbound.data, packet); + } + else + { + DBG2(DBG_ESP, "no outbound callback registered, dropping packet"); + packet->destroy(packet); + } + this->lock->unlock(this->lock); +} + +/** + * Processes outbound packets + */ +static job_requeue_t process_outbound(private_ipsec_processor_t *this) +{ + ipsec_policy_t *policy; + esp_packet_t *esp_packet; + ip_packet_t *packet; + ipsec_sa_t *sa; + host_t *src, *dst; + + packet = (ip_packet_t*)this->outbound_queue->dequeue(this->outbound_queue); + + policy = ipsec->policies->find_by_packet(ipsec->policies, packet, FALSE); + if (!policy) + { + DBG1(DBG_ESP, "no matching outbound IPsec policy for %H == %H", + packet->get_source(packet), packet->get_destination(packet)); + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + + sa = ipsec->sas->checkout_by_reqid(ipsec->sas, policy->get_reqid(policy), + FALSE); + if (!sa) + { /* TODO-IPSEC: send an acquire to uppper layer */ + DBG1(DBG_ESP, "could not find an outbound IPsec SA for reqid {%u}, " + "dropping packet", policy->get_reqid(policy)); + packet->destroy(packet); + policy->destroy(policy); + return JOB_REQUEUE_DIRECT; + } + src = sa->get_source(sa); + dst = sa->get_destination(sa); + esp_packet = esp_packet_create_from_payload(src->clone(src), + dst->clone(dst), packet); + if (esp_packet->encrypt(esp_packet, sa->get_esp_context(sa), + sa->get_spi(sa)) != SUCCESS) + { + ipsec->sas->checkin(ipsec->sas, sa); + esp_packet->destroy(esp_packet); + policy->destroy(policy); + return JOB_REQUEUE_DIRECT; + } + /* TODO-IPSEC: update policy/sa counters? */ + ipsec->sas->checkin(ipsec->sas, sa); + policy->destroy(policy); + send_outbound(this, esp_packet); + return JOB_REQUEUE_DIRECT; +} + +METHOD(ipsec_processor_t, queue_inbound, void, + private_ipsec_processor_t *this, esp_packet_t *packet) +{ + this->inbound_queue->enqueue(this->inbound_queue, packet); +} + +METHOD(ipsec_processor_t, queue_outbound, void, + private_ipsec_processor_t *this, ip_packet_t *packet) +{ + this->outbound_queue->enqueue(this->outbound_queue, packet); +} + +METHOD(ipsec_processor_t, register_inbound, void, + private_ipsec_processor_t *this, ipsec_inbound_cb_t cb, void *data) +{ + this->lock->write_lock(this->lock); + this->inbound.cb = cb; + this->inbound.data = data; + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, unregister_inbound, void, + private_ipsec_processor_t *this, ipsec_inbound_cb_t cb) +{ + this->lock->write_lock(this->lock); + if (this->inbound.cb == cb) + { + this->inbound.cb = NULL; + } + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, register_outbound, void, + private_ipsec_processor_t *this, ipsec_outbound_cb_t cb, void *data) +{ + this->lock->write_lock(this->lock); + this->outbound.cb = cb; + this->outbound.data = data; + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, unregister_outbound, void, + private_ipsec_processor_t *this, ipsec_outbound_cb_t cb) +{ + this->lock->write_lock(this->lock); + if (this->outbound.cb == cb) + { + this->outbound.cb = NULL; + } + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, destroy, void, + private_ipsec_processor_t *this) +{ + this->inbound_queue->destroy_offset(this->inbound_queue, + offsetof(esp_packet_t, destroy)); + this->outbound_queue->destroy_offset(this->outbound_queue, + offsetof(ip_packet_t, destroy)); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +ipsec_processor_t *ipsec_processor_create() +{ + private_ipsec_processor_t *this; + + INIT(this, + .public = { + .queue_inbound = _queue_inbound, + .queue_outbound = _queue_outbound, + .register_inbound = _register_inbound, + .unregister_inbound = _unregister_inbound, + .register_outbound = _register_outbound, + .unregister_outbound = _unregister_outbound, + .destroy = _destroy, + }, + .inbound_queue = blocking_queue_create(), + .outbound_queue = blocking_queue_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)process_inbound, this, + NULL, (callback_job_cancel_t)return_false)); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)process_outbound, this, + NULL, (callback_job_cancel_t)return_false)); + return &this->public; +} diff --git a/src/libipsec/ipsec_processor.h b/src/libipsec/ipsec_processor.h new file mode 100644 index 000000000..0a409828b --- /dev/null +++ b/src/libipsec/ipsec_processor.h @@ -0,0 +1,115 @@ +/* + * 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 ipsec_processor ipsec_processor + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_PROCESSOR_H_ +#define IPSEC_PROCESSOR_H_ + +#include "ip_packet.h" +#include "esp_packet.h" + +typedef struct ipsec_processor_t ipsec_processor_t; + +/** + * Callback called to deliver an inbound plaintext packet. + * + * @param data data supplied during registration of the callback + * @param packet plaintext IP packet to deliver + */ +typedef void (*ipsec_inbound_cb_t)(void *data, ip_packet_t *packet); + +/** + * Callback called to send an ESP packet. + * + * @note The ESP packet currently comes without IP header (and without UDP + * header in case of UDP encapsulation) + * + * @param data data supplied during registration of the callback + * @param packet ESP packet to send + */ +typedef void (*ipsec_outbound_cb_t)(void *data, esp_packet_t *packet); + +/** + * IPsec processor + */ +struct ipsec_processor_t { + + /** + * Queue an inbound ESP packet for processing. + * + * @param packet the ESP packet to process + */ + void (*queue_inbound)(ipsec_processor_t *this, esp_packet_t *packet); + + /** + * Queue an outbound plaintext IP packet for processing. + * + * @param packet the plaintext IP packet + */ + void (*queue_outbound)(ipsec_processor_t *this, ip_packet_t *packet); + + /** + * Register the callback used to deliver inbound plaintext packets. + * + * @param cb the inbound callback function + * @param data optional data provided to callback + */ + void (*register_inbound)(ipsec_processor_t *this, ipsec_inbound_cb_t cb, + void *data); + + /** + * Unregister a previously registered inbound callback. + * + * @param cb previously registered callback function + */ + void (*unregister_inbound)(ipsec_processor_t *this, + ipsec_inbound_cb_t cb); + + /** + * Register the callback used to send outbound ESP packets. + * + * @param cb the outbound callback function + * @param data optional data provided to callback + */ + void (*register_outbound)(ipsec_processor_t *this, ipsec_outbound_cb_t cb, + void *data); + + /** + * Unregister a previously registered outbound callback. + * + * @param cb previously registered callback function + */ + void (*unregister_outbound)(ipsec_processor_t *this, + ipsec_outbound_cb_t cb); + + /** + * Destroy an ipsec_processor_t. + */ + void (*destroy)(ipsec_processor_t *this); + +}; + +/** + * Create an ipsec_processor_t instance + * + * @return IPsec processor instance + */ +ipsec_processor_t *ipsec_processor_create(); + +#endif /** IPSEC_PROCESSOR_H_ @}*/ diff --git a/src/libipsec/ipsec_sa.c b/src/libipsec/ipsec_sa.c new file mode 100644 index 000000000..cccd16404 --- /dev/null +++ b/src/libipsec/ipsec_sa.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "ipsec_sa.h" + +#include <library.h> +#include <debug.h> + +typedef struct private_ipsec_sa_t private_ipsec_sa_t; + +/** + * Private additions to ipsec_sa_t. + */ +struct private_ipsec_sa_t { + + /** + * Public members + */ + ipsec_sa_t public; + + /** + * SPI of this SA + */ + u_int32_t spi; + + /** + * Source address + */ + host_t *src; + + /** + * Destination address + */ + host_t *dst; + + /** + * Protocol + */ + u_int8_t protocol; + + /** + * Reqid of this SA + */ + u_int32_t reqid; + + /** + * Lifetime configuration + */ + lifetime_cfg_t lifetime; + + /** + * IPsec mode + */ + ipsec_mode_t mode; + + /** + * TRUE if extended sequence numbers are used + */ + bool esn; + + /** + * TRUE if this is an inbound SA + */ + bool inbound; + + /** + * ESP context + */ + esp_context_t *esp_context; +}; + +METHOD(ipsec_sa_t, get_source, host_t*, + private_ipsec_sa_t *this) +{ + return this->src; +} + +METHOD(ipsec_sa_t, get_destination, host_t*, + private_ipsec_sa_t *this) +{ + return this->dst; +} + +METHOD(ipsec_sa_t, get_spi, u_int32_t, + private_ipsec_sa_t *this) +{ + return this->spi; +} + +METHOD(ipsec_sa_t, get_reqid, u_int32_t, + private_ipsec_sa_t *this) +{ + return this->reqid; +} + +METHOD(ipsec_sa_t, get_protocol, u_int8_t, + private_ipsec_sa_t *this) +{ + return this->protocol; +} + +METHOD(ipsec_sa_t, get_lifetime, lifetime_cfg_t*, + private_ipsec_sa_t *this) +{ + return &this->lifetime; +} + +METHOD(ipsec_sa_t, is_inbound, bool, + private_ipsec_sa_t *this) +{ + return this->inbound; +} + +METHOD(ipsec_sa_t, get_esp_context, esp_context_t*, + private_ipsec_sa_t *this) +{ + return this->esp_context; +} + +METHOD(ipsec_sa_t, match_by_spi_dst, bool, + private_ipsec_sa_t *this, u_int32_t spi, host_t *dst) +{ + return this->spi == spi && this->dst->ip_equals(this->dst, dst); +} + +METHOD(ipsec_sa_t, match_by_spi_src_dst, bool, + private_ipsec_sa_t *this, u_int32_t spi, host_t *src, host_t *dst) +{ + return this->spi == spi && this->src->ip_equals(this->src, src) && + this->dst->ip_equals(this->dst, dst); +} + +METHOD(ipsec_sa_t, match_by_reqid, bool, + private_ipsec_sa_t *this, u_int32_t reqid, bool inbound) +{ + return this->reqid == reqid && this->inbound == inbound; +} + +METHOD(ipsec_sa_t, destroy, void, + private_ipsec_sa_t *this) +{ + this->src->destroy(this->src); + this->dst->destroy(this->dst); + DESTROY_IF(this->esp_context); + free(this); +} + +/** + * Described in header. + */ +ipsec_sa_t *ipsec_sa_create(u_int32_t spi, host_t *src, host_t *dst, + 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 encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts) +{ + private_ipsec_sa_t *this; + + if (protocol != IPPROTO_ESP) + { + DBG1(DBG_ESP, " IPsec SA: protocol not supported"); + return NULL; + } + if (!encap) + { + DBG1(DBG_ESP, " IPsec SA: only UDP encapsulation is supported"); + return NULL; + } + if (esn) + { + DBG1(DBG_ESP, " IPsec SA: ESN not supported"); + return NULL; + } + if (ipcomp != IPCOMP_NONE) + { + DBG1(DBG_ESP, " IPsec SA: compression not supported"); + return NULL; + } + if (mode != MODE_TUNNEL) + { + DBG1(DBG_ESP, " IPsec SA: unsupported mode"); + return NULL; + } + + INIT(this, + .public = { + .destroy = _destroy, + .get_source = _get_source, + .get_destination = _get_destination, + .get_spi = _get_spi, + .get_reqid = _get_reqid, + .get_protocol = _get_protocol, + .get_lifetime = _get_lifetime, + .is_inbound = _is_inbound, + .match_by_spi_dst = _match_by_spi_dst, + .match_by_spi_src_dst = _match_by_spi_src_dst, + .match_by_reqid = _match_by_reqid, + .get_esp_context = _get_esp_context, + }, + .spi = spi, + .src = src->clone(src), + .dst = dst->clone(dst), + .lifetime = *lifetime, + .protocol = protocol, + .reqid = reqid, + .mode = mode, + .esn = esn, + .inbound = inbound, + ); + + this->esp_context = esp_context_create(enc_alg, enc_key, int_alg, int_key, + inbound); + if (!this->esp_context) + { + destroy(this); + return NULL; + } + return &this->public; +} diff --git a/src/libipsec/ipsec_sa.h b/src/libipsec/ipsec_sa.h new file mode 100644 index 000000000..5fd03b6e4 --- /dev/null +++ b/src/libipsec/ipsec_sa.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 ipsec_sa ipsec_sa + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_SA_H_ +#define IPSEC_SA_H_ + +#include "esp_context.h" + +#include <library.h> +#include <utils/host.h> +#include <selectors/traffic_selector.h> +#include <ipsec/ipsec_types.h> + +typedef struct ipsec_sa_t ipsec_sa_t; + +/** + * IPsec Security Association (SA) + */ +struct ipsec_sa_t { + + /** + * Get the source address for this SA + * + * @return source address of this SA + */ + host_t *(*get_source)(ipsec_sa_t *this); + + /** + * Get the destination address for this SA + * + * @return destination address of this SA + */ + host_t *(*get_destination)(ipsec_sa_t *this); + + /** + * Get the SPI for this SA + * + * @return SPI of this SA + */ + u_int32_t (*get_spi)(ipsec_sa_t *this); + + /** + * Get the reqid of this SA + * + * @return reqid of this SA + */ + u_int32_t (*get_reqid)(ipsec_sa_t *this); + + /** + * Get the protocol (e.g. IPPROTO_ESP) of this SA + * + * @return protocol of this SA + */ + u_int8_t (*get_protocol)(ipsec_sa_t *this); + + /** + * Returns whether this SA is inbound or outbound + * + * @return TRUE if inbound, FALSE if outbound + */ + bool (*is_inbound)(ipsec_sa_t *this); + + /** + * Get the lifetime information for this SA + * Note that this information is always relative to the time when the + * SA was installed (i.e. it is not adjusted over time) + * + * @return lifetime of this SA + */ + lifetime_cfg_t *(*get_lifetime)(ipsec_sa_t *this); + + /** + * Get the ESP context for this SA + * + * @return ESP context of this SA + */ + esp_context_t *(*get_esp_context)(ipsec_sa_t *this); + + /** + * Check if this SA matches all given parameters + * + * @param spi SPI + * @param dst destination address + * @return TRUE if this SA matches all parameters, FALSE otherwise + */ + bool (*match_by_spi_dst)(ipsec_sa_t *this, u_int32_t spi, host_t *dst); + + /** + * Check if this SA matches all given parameters + * + * @param spi SPI + * @param src source address + * @param dst destination address + * @return TRUE if this SA matches all parameters, FALSE otherwise + */ + bool (*match_by_spi_src_dst)(ipsec_sa_t *this, u_int32_t spi, host_t *src, + host_t *dst); + + /** + * Check if this SA matches all given parameters + * + * @param reqid reqid + * @param inbound TRUE for inbound SA, FALSE for outbound + * @return TRUE if this SA matches all parameters, FALSE otherwise + */ + bool (*match_by_reqid)(ipsec_sa_t *this, u_int32_t reqid, bool inbound); + + /** + * Destroy an ipsec_sa_t + */ + void (*destroy)(ipsec_sa_t *this); + +}; + +/** + * Create an ipsec_sa_t instance + * + * @param spi SPI for this SA + * @param src source address for this SA (gets cloned) + * @param dst destination address for this SA (gets cloned) + * @param protocol protocol for this SA (only ESP is supported) + * @param reqid reqid for this SA + * @param mark mark for this SA (ignored) + * @param tfc Traffic Flow Confidentiality (currently not supported) + * @param lifetime lifetime for this SA + * @param enc_alg encryption algorithm for this SA + * @param enc_key encryption key for this SA + * @param int_alg integrity protection algorithm + * @param int_key integrity protection key + * @param mode mode for this SA (only tunnel mode is supported) + * @param ipcomp IPcomp transform (not supported, use IPCOMP_NONE) + * @param cpi CPI for IPcomp (ignored) + * @param encap enable UDP encapsulation (must be TRUE) + * @param esn Extended Sequence Numbers (currently not supported) + * @param inbound TRUE if this is an inbound SA, FALSE otherwise + * @param src_ts source traffic selector + * @param dst_ts destination traffic selector + * @return the IPsec SA, or NULL if the creation failed + */ +ipsec_sa_t *ipsec_sa_create(u_int32_t spi, host_t *src, host_t *dst, + 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 encap, bool esn, bool inbound, + traffic_selector_t *src_ts, + traffic_selector_t *dst_ts); + +#endif /** IPSEC_SA_H_ @}*/ diff --git a/src/libipsec/ipsec_sa_mgr.c b/src/libipsec/ipsec_sa_mgr.c new file mode 100644 index 000000000..e42c77aa5 --- /dev/null +++ b/src/libipsec/ipsec_sa_mgr.c @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "ipsec.h" +#include "ipsec_sa_mgr.h" + +#include <debug.h> +#include <library.h> +#include <processing/jobs/callback_job.h> +#include <threading/condvar.h> +#include <threading/mutex.h> +#include <utils/hashtable.h> +#include <utils/linked_list.h> + +typedef struct private_ipsec_sa_mgr_t private_ipsec_sa_mgr_t; + +/** + * Private additions to ipsec_sa_mgr_t. + */ +struct private_ipsec_sa_mgr_t { + + /** + * Public members of ipsec_sa_mgr_t. + */ + ipsec_sa_mgr_t public; + + /** + * Installed SAs + */ + linked_list_t *sas; + + /** + * SPIs allocated using get_spi() + */ + hashtable_t *allocated_spis; + + /** + * Mutex used to synchronize access to the SA manager + */ + mutex_t *mutex; + + /** + * RNG used to generate SPIs + */ + rng_t *rng; +}; + +/** + * Struct to keep track of locked IPsec SAs + */ +typedef struct { + + /** + * IPsec SA + */ + ipsec_sa_t *sa; + + /** + * Set if this SA is currently in use by a thread + */ + bool locked; + + /** + * Condvar used by threads to wait for this entry + */ + condvar_t *condvar; + + /** + * Number of threads waiting for this entry + */ + u_int waiting_threads; + + /** + * Set if this entry is awaiting deletion + */ + bool awaits_deletion; + +} ipsec_sa_entry_t; + +/** + * Helper struct for expiration events + */ +typedef struct { + + /** + * IPsec SA manager + */ + private_ipsec_sa_mgr_t *manager; + + /** + * Entry that expired + */ + ipsec_sa_entry_t *entry; + + /** + * 0 if this is a hard expire, otherwise the offset in s (soft->hard) + */ + u_int32_t hard_offset; + +} ipsec_sa_expired_t; + +/* + * Used for the hash table of allocated SPIs + */ +static bool spi_equals(u_int32_t *spi, u_int32_t *other_spi) +{ + return *spi == *other_spi; +} + +static u_int spi_hash(u_int32_t *spi) +{ + return chunk_hash(chunk_from_thing(*spi)); +} + +/** + * Create an SA entry + */ +static ipsec_sa_entry_t *create_entry(ipsec_sa_t *sa) +{ + ipsec_sa_entry_t *this; + + INIT(this, + .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), + .sa = sa, + ); + return this; +} + +/** + * Destroy an SA entry + */ +static void destroy_entry(ipsec_sa_entry_t *entry) +{ + entry->condvar->destroy(entry->condvar); + entry->sa->destroy(entry->sa); + free(entry); +} + +/** + * Makes sure an entry is safe to remove + * Must be called with this->mutex held. + * + * @return TRUE if entry can be removed, FALSE if entry is already +* being removed by another thread + */ +static bool wait_remove_entry(private_ipsec_sa_mgr_t *this, + ipsec_sa_entry_t *entry) +{ + if (entry->awaits_deletion) + { + /* this will be deleted by another thread already */ + return FALSE; + } + entry->awaits_deletion = TRUE; + while (entry->locked) + { + entry->condvar->wait(entry->condvar, this->mutex); + } + while (entry->waiting_threads > 0) + { + entry->condvar->broadcast(entry->condvar); + entry->condvar->wait(entry->condvar, this->mutex); + } + return TRUE; +} + +/** + * Waits until an is available and then locks it. + * Must only be called with this->mutex held + */ +static bool wait_for_entry(private_ipsec_sa_mgr_t *this, + ipsec_sa_entry_t *entry) +{ + while (entry->locked && !entry->awaits_deletion) + { + entry->waiting_threads++; + entry->condvar->wait(entry->condvar, this->mutex); + entry->waiting_threads--; + } + if (entry->awaits_deletion) + { + /* others may still be waiting, */ + entry->condvar->signal(entry->condvar); + return FALSE; + } + entry->locked = TRUE; + return TRUE; +} + +/** + * Flushes all entries + * Must be called with this->mutex held. + */ +static void flush_entries(private_ipsec_sa_mgr_t *this) +{ + ipsec_sa_entry_t *current; + enumerator_t *enumerator; + + DBG2(DBG_ESP, "flushing SAD"); + + enumerator = this->sas->create_enumerator(this->sas); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (wait_remove_entry(this, current)) + { + this->sas->remove_at(this->sas, enumerator); + destroy_entry(current); + } + } + enumerator->destroy(enumerator); +} + +/* + * Different match functions to find SAs in the linked list + */ +static bool match_entry_by_ptr(ipsec_sa_entry_t *item, ipsec_sa_entry_t *entry) +{ + return item == entry; +} + +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) +{ + 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, + host_t *src, host_t *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) +{ + 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, + host_t *dst) +{ + return item->sa->match_by_spi_dst(item->sa, spi, dst); +} + +/** + * Remove an entry + */ +static bool remove_entry(private_ipsec_sa_mgr_t *this, ipsec_sa_entry_t *entry) +{ + ipsec_sa_entry_t *current; + enumerator_t *enumerator; + bool removed = FALSE; + + enumerator = this->sas->create_enumerator(this->sas); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (current == entry) + { + if (wait_remove_entry(this, current)) + { + this->sas->remove_at(this->sas, enumerator); + removed = TRUE; + } + break; + } + } + enumerator->destroy(enumerator); + return removed; +} + +/** + * Callback for expiration events + */ +static job_requeue_t sa_expired(ipsec_sa_expired_t *expired) +{ + private_ipsec_sa_mgr_t *this = expired->manager; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_ptr, + NULL, expired->entry) == SUCCESS) + { + u_int32_t hard_offset = expired->hard_offset; + ipsec_sa_t *sa = expired->entry->sa; + + ipsec->events->expire(ipsec->events, sa->get_reqid(sa), + sa->get_protocol(sa), sa->get_spi(sa), + hard_offset == 0); + if (hard_offset) + { /* soft limit reached, schedule hard expire */ + expired->hard_offset = 0; + this->mutex->unlock(this->mutex); + return JOB_RESCHEDULE(hard_offset); + } + /* hard limit reached */ + if (remove_entry(this, expired->entry)) + { + destroy_entry(expired->entry); + } + } + this->mutex->unlock(this->mutex); + return JOB_REQUEUE_NONE; +} + +/** + * Schedule a job to handle IPsec SA expiration + */ +static void schedule_expiration(private_ipsec_sa_mgr_t *this, + ipsec_sa_entry_t *entry) +{ + lifetime_cfg_t *lifetime = entry->sa->get_lifetime(entry->sa); + ipsec_sa_expired_t *expired; + callback_job_t *job; + u_int32_t timeout; + + INIT(expired, + .manager = this, + .entry = entry, + ); + + /* schedule a rekey first, a hard timeout will be scheduled then, if any */ + expired->hard_offset = lifetime->time.life - lifetime->time.rekey; + timeout = lifetime->time.rekey; + + if (lifetime->time.life <= lifetime->time.rekey || + lifetime->time.rekey == 0) + { /* no rekey, schedule hard timeout */ + expired->hard_offset = 0; + timeout = lifetime->time.life; + } + + job = callback_job_create((callback_job_cb_t)sa_expired, expired, + (callback_job_cleanup_t)free, NULL); + lib->scheduler->schedule_job(lib->scheduler, (job_t*)job, timeout); +} + +/** + * Remove all allocated SPIs + */ +static void flush_allocated_spis(private_ipsec_sa_mgr_t *this) +{ + enumerator_t *enumerator; + u_int32_t *current; + + DBG2(DBG_ESP, "flushing allocated SPIs"); + enumerator = this->allocated_spis->create_enumerator(this->allocated_spis); + while (enumerator->enumerate(enumerator, NULL, (void**)¤t)) + { + this->allocated_spis->remove_at(this->allocated_spis, enumerator); + DBG2(DBG_ESP, " removed allocated SPI %.8x", ntohl(*current)); + free(current); + } + enumerator->destroy(enumerator); +} + +/** + * Pre-allocate an SPI for an inbound SA + */ +static bool allocate_spi(private_ipsec_sa_mgr_t *this, u_int32_t spi) +{ + u_int32_t *spi_alloc; + + 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) + { + return FALSE; + } + spi_alloc = malloc_thing(u_int32_t); + *spi_alloc = spi; + this->allocated_spis->put(this->allocated_spis, spi_alloc, spi_alloc); + return TRUE; +} + +METHOD(ipsec_sa_mgr_t, get_spi, status_t, + private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int8_t protocol, + u_int32_t reqid, u_int32_t *spi) +{ + u_int32_t spi_new; + + DBG2(DBG_ESP, "allocating SPI for reqid {%u}", reqid); + + this->mutex->lock(this->mutex); + if (!this->rng) + { + this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!this->rng) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_ESP, "failed to create RNG for SPI generation"); + return FAILED; + } + } + + do + { + if (!this->rng->get_bytes(this->rng, sizeof(spi_new), + (u_int8_t*)&spi_new)) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_ESP, "failed to allocate SPI for reqid {%u}", reqid); + return FAILED; + } + /* make sure the SPI is valid (not in range 0-255) */ + spi_new |= 0x00000100; + spi_new = htonl(spi_new); + } + while (!allocate_spi(this, spi_new)); + this->mutex->unlock(this->mutex); + + *spi = spi_new; + + DBG2(DBG_ESP, "allocated SPI %.8x for reqid {%u}", ntohl(*spi), reqid); + return SUCCESS; +} + +METHOD(ipsec_sa_mgr_t, add_sa, status_t, + private_ipsec_sa_mgr_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 encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts) +{ + ipsec_sa_entry_t *entry; + ipsec_sa_t *sa_new; + + DBG2(DBG_ESP, "adding SAD entry with SPI %.8x and reqid {%u}", + ntohl(spi), reqid); + DBG2(DBG_ESP, " using encryption algorithm %N with key size %d", + encryption_algorithm_names, enc_alg, enc_key.len * 8); + DBG2(DBG_ESP, " using integrity algorithm %N with key size %d", + integrity_algorithm_names, int_alg, int_key.len * 8); + + sa_new = ipsec_sa_create(spi, src, dst, protocol, reqid, mark, tfc, + lifetime, enc_alg, enc_key, int_alg, int_key, mode, + ipcomp, cpi, encap, esn, inbound, src_ts, dst_ts); + if (!sa_new) + { + DBG1(DBG_ESP, "failed to create SAD entry"); + return FAILED; + } + + this->mutex->lock(this->mutex); + + if (inbound) + { /* remove any pre-allocated SPIs */ + u_int32_t *spi_alloc; + + spi_alloc = this->allocated_spis->remove(this->allocated_spis, &spi); + free(spi_alloc); + } + + if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst, + NULL, spi, src, dst) == SUCCESS) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_ESP, "failed to install SAD entry: already installed"); + sa_new->destroy(sa_new); + return FAILED; + } + + entry = create_entry(sa_new); + schedule_expiration(this, entry); + this->sas->insert_last(this->sas, entry); + + this->mutex->unlock(this->mutex); + 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) +{ + ipsec_sa_entry_t *current, *found = NULL; + enumerator_t *enumerator; + + this->mutex->lock(this->mutex); + 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 (wait_remove_entry(this, current)) + { + this->sas->remove_at(this->sas, enumerator); + found = current; + } + break; + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); + + if (found) + { + DBG2(DBG_ESP, "deleted %sbound SAD entry with SPI %.8x", + found->sa->is_inbound(found->sa) ? "in" : "out", ntohl(spi)); + destroy_entry(found); + return SUCCESS; + } + return FAILED; +} + +METHOD(ipsec_sa_mgr_t, checkout_by_reqid, ipsec_sa_t*, + private_ipsec_sa_mgr_t *this, u_int32_t reqid, bool inbound) +{ + ipsec_sa_entry_t *entry; + ipsec_sa_t *sa = NULL; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_reqid_inbound, + (void**)&entry, reqid, inbound) == SUCCESS && + wait_for_entry(this, entry)) + { + sa = entry->sa; + } + this->mutex->unlock(this->mutex); + return sa; +} + +METHOD(ipsec_sa_mgr_t, checkout_by_spi, ipsec_sa_t*, + private_ipsec_sa_mgr_t *this, u_int32_t spi, host_t *dst) +{ + ipsec_sa_entry_t *entry; + ipsec_sa_t *sa = NULL; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_dst, + (void**)&entry, spi, dst) == SUCCESS && + wait_for_entry(this, entry)) + { + sa = entry->sa; + } + this->mutex->unlock(this->mutex); + return sa; +} + +METHOD(ipsec_sa_mgr_t, checkin, void, + private_ipsec_sa_mgr_t *this, ipsec_sa_t *sa) +{ + ipsec_sa_entry_t *entry; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_sa_ptr, + (void**)&entry, sa) == SUCCESS) + { + if (entry->locked) + { + entry->locked = FALSE; + entry->condvar->signal(entry->condvar); + } + } + this->mutex->unlock(this->mutex); +} + +METHOD(ipsec_sa_mgr_t, flush_sas, status_t, + private_ipsec_sa_mgr_t *this) +{ + this->mutex->lock(this->mutex); + flush_entries(this); + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(ipsec_sa_mgr_t, destroy, void, + private_ipsec_sa_mgr_t *this) +{ + this->mutex->lock(this->mutex); + flush_entries(this); + flush_allocated_spis(this); + this->mutex->unlock(this->mutex); + + this->allocated_spis->destroy(this->allocated_spis); + this->sas->destroy(this->sas); + + this->mutex->destroy(this->mutex); + DESTROY_IF(this->rng); + free(this); +} + +/** + * Described in header. + */ +ipsec_sa_mgr_t *ipsec_sa_mgr_create() +{ + private_ipsec_sa_mgr_t *this; + + INIT(this, + .public = { + .get_spi = _get_spi, + .add_sa = _add_sa, + .del_sa = _del_sa, + .checkout_by_spi = _checkout_by_spi, + .checkout_by_reqid = _checkout_by_reqid, + .checkin = _checkin, + .flush_sas = _flush_sas, + .destroy = _destroy, + }, + .sas = linked_list_create(), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .allocated_spis = hashtable_create((hashtable_hash_t)spi_hash, + (hashtable_equals_t)spi_equals, 16), + ); + + return &this->public; +} diff --git a/src/libipsec/ipsec_sa_mgr.h b/src/libipsec/ipsec_sa_mgr.h new file mode 100644 index 000000000..303b36f0e --- /dev/null +++ b/src/libipsec/ipsec_sa_mgr.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 ipsec_sa_mgr ipsec_sa_mgr + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_SA_MGR_H_ +#define IPSEC_SA_MGR_H_ + +#include "ipsec_sa.h" + +#include <library.h> +#include <ipsec/ipsec_types.h> +#include <selectors/traffic_selector.h> +#include <utils/host.h> + +typedef struct ipsec_sa_mgr_t ipsec_sa_mgr_t; + +/** + * IPsec SA manager + * + * The first methods are modeled after those in kernel_ipsec_t. + */ +struct ipsec_sa_mgr_t { + + /** + * Allocate an SPI for an inbound IPsec SA + * + * @param src source address of the SA + * @param dst destination address of the SA + * @param protocol protocol of the SA (only ESP supported) + * @param reqid reqid for the SA + * @param spi the allocated SPI + * @return SUCCESS of operation successful + */ + status_t (*get_spi)(ipsec_sa_mgr_t *this, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t reqid, u_int32_t *spi); + + /** + * Add a new SA + * + * @param src source address for this SA (gets cloned) + * @param dst destination address for this SA (gets cloned) + * @param spi SPI for this SA + * @param protocol protocol for this SA (only ESP is supported) + * @param reqid reqid for this SA + * @param mark mark for this SA (ignored) + * @param tfc Traffic Flow Confidentiality (not yet supported) + * @param lifetime lifetime for this SA + * @param enc_alg encryption algorithm for this SA + * @param enc_key encryption key for this SA + * @param int_alg integrity protection algorithm + * @param int_key integrity protection key + * @param mode mode for this SA (only tunnel mode is supported) + * @param ipcomp IPcomp transform (not supported, use IPCOMP_NONE) + * @param cpi CPI for IPcomp (ignored) + * @param encap enable UDP encapsulation (must be TRUE) + * @param esn Extended Sequence Numbers (currently not supported) + * @param inbound TRUE if this is an inbound SA, FALSE otherwise + * @param src_ts source traffic selector + * @param dst_ts destination traffic selector + * @return SUCCESS if operation completed + */ + status_t (*add_sa)(ipsec_sa_mgr_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 encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts); + + /** + * Delete a previously added SA + * + * @param spi SPI of the SA + * @param src source address of the SA + * @param dst destination address of the SA + * @param protocol protocol of the SA + * @param cpi CPI for IPcomp + * @param mark optional mark + * @return SUCCESS if operation completed + */ + status_t (*del_sa)(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); + + /** + * Flush all SAs + * + * @return SUCCESS if operation completed + */ + status_t (*flush_sas)(ipsec_sa_mgr_t *this); + + /** + * Checkout an installed IPsec SA by SPI and destination address + * Can be used to find the correct SA for an inbound packet. + * + * The matching SA is locked until it is checked in using checkin(). + * If the matching SA is already checked out, this call blocks until the + * SA is checked in. + * + * Since other threads may be waiting for the checked out SA, it should be + * checked in as soon as possible after use. + * + * @param spi SPI (e.g. of an inbound packet) + * @param dst destination address (e.g. of an inbound packet) + * @return the matching IPsec SA, or NULL if none is found + */ + ipsec_sa_t *(*checkout_by_spi)(ipsec_sa_mgr_t *this, u_int32_t spi, + host_t *dst); + + /** + * Checkout an installed IPsec SA by its reqid and inbound/outbound flag. + * Can be used to find the correct SA for an outbound packet. + * + * The matching SA is locked until it is checked in using checkin(). + * If the matching SA is already checked out, this call blocks until the + * SA is checked in. + * + * Since other threads may be waiting for a checked out SA, it should be + * checked in as soon as possible after use. + * + * @param reqid reqid of the SA + * @param inbound TRUE for an inbound SA, FALSE for an outbound SA + * @return the matching IPsec SA, or NULL if none is found + */ + ipsec_sa_t *(*checkout_by_reqid)(ipsec_sa_mgr_t *this, u_int32_t reqid, + bool inbound); + + /** + * Checkin an SA after use. + * + * @param sa checked out SA + */ + void (*checkin)(ipsec_sa_mgr_t *this, ipsec_sa_t *sa); + + /** + * Destroy an ipsec_sa_mgr_t + */ + void (*destroy)(ipsec_sa_mgr_t *this); + +}; + +/** + * Create an ipsec_sa_mgr instance + * + * @return IPsec SA manager instance + */ +ipsec_sa_mgr_t *ipsec_sa_mgr_create(); + +#endif /** IPSEC_SA_MGR_H_ @}*/ diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 8cba58816..3b2d7eaaa 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -20,13 +20,14 @@ credentials/sets/auth_cfg_wrapper.c credentials/sets/ocsp_response_wrapper.c \ credentials/sets/cert_cache.c credentials/sets/mem_cred.c \ credentials/sets/callback_cred.c credentials/auth_cfg.c database/database.c \ database/database_factory.c fetcher/fetcher.c fetcher/fetcher_manager.c eap/eap.c \ +ipsec/ipsec_types.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ -utils.c utils/host.c utils/identification.c utils/lexparser.c \ -utils/linked_list.c utils/hashtable.c utils/enumerator.c utils/optionsfrom.c \ -utils/capabilities.c utils/backtrace.c +utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ +utils/linked_list.c utils/blocking_queue.c utils/hashtable.c utils/enumerator.c \ +utils/optionsfrom.c utils/capabilities.c utils/backtrace.c utils/tun_device.c # adding the plugin source files diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index dc849d6f2..a50d7fb90 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -18,13 +18,14 @@ credentials/sets/auth_cfg_wrapper.c credentials/sets/ocsp_response_wrapper.c \ credentials/sets/cert_cache.c credentials/sets/mem_cred.c \ credentials/sets/callback_cred.c credentials/auth_cfg.c database/database.c \ database/database_factory.c fetcher/fetcher.c fetcher/fetcher_manager.c eap/eap.c \ +ipsec/ipsec_types.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ -utils.c utils/host.c utils/identification.c utils/lexparser.c \ -utils/linked_list.c utils/hashtable.c utils/enumerator.c utils/optionsfrom.c \ -utils/capabilities.c utils/backtrace.c +utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ +utils/linked_list.c utils/blocking_queue.c utils/hashtable.c utils/enumerator.c \ +utils/optionsfrom.c utils/capabilities.c utils/backtrace.c utils/tun_device.c if USE_DEV_HEADERS strongswan_includedir = ${dev_headers} @@ -51,14 +52,16 @@ credentials/sets/ocsp_response_wrapper.h credentials/sets/cert_cache.h \ credentials/sets/mem_cred.h credentials/sets/callback_cred.h \ credentials/auth_cfg.h credentials/credential_set.h credentials/cert_validator.h \ database/database.h database/database_factory.h fetcher/fetcher.h \ -fetcher/fetcher_manager.h eap/eap.h pen/pen.h plugins/plugin_loader.h \ -plugins/plugin.h plugins/plugin_feature.h processing/jobs/job.h \ -processing/jobs/callback_job.h processing/processor.h processing/scheduler.h \ -selectors/traffic_selector.h threading/thread.h threading/thread_value.h \ +fetcher/fetcher_manager.h eap/eap.h pen/pen.h ipsec/ipsec_types.h \ +plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h +processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h +processing/scheduler.h selectors/traffic_selector.h \ +threading/thread.h threading/thread_value.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ threading/rwlock.h threading/lock_profiler.h utils.h utils/host.h \ -utils/identification.h utils/lexparser.h utils/linked_list.h utils/hashtable.h \ -utils/enumerator.h utils/optionsfrom.h utils/capabilities.h utils/backtrace.h +utils/packet.h utils/identification.h utils/lexparser.h utils/linked_list.h \ +utils/blocking_queue.h utils/hashtable.h utils/enumerator.h utils/optionsfrom.h \ +utils/capabilities.h utils/backtrace.h utils/tun_device.h endif library.lo : $(top_builddir)/config.status diff --git a/src/libstrongswan/bio/bio_reader.c b/src/libstrongswan/bio/bio_reader.c index fce0d1aef..3a62bb541 100644 --- a/src/libstrongswan/bio/bio_reader.c +++ b/src/libstrongswan/bio/bio_reader.c @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -47,8 +50,38 @@ METHOD(bio_reader_t, peek, chunk_t, return this->buf; } -METHOD(bio_reader_t, read_uint8, bool, - private_bio_reader_t *this, u_int8_t *res) +/** + * A version of chunk_skip() that supports skipping from the end (i.e. simply + * reducing the size) + */ +static inline chunk_t chunk_skip_end(chunk_t chunk, size_t bytes, bool from_end) +{ + if (chunk.len > bytes) + { + if (!from_end) + { + chunk.ptr += bytes; + } + chunk.len -= bytes; + return chunk; + } + return chunk_empty; +} + +/** + * Returns a pointer to the data to read, optionally from the end + */ +static inline u_char *get_ptr_end(private_bio_reader_t *this, u_int32_t len, + bool from_end) +{ + return from_end ? this->buf.ptr + (this->buf.len - len) : this->buf.ptr; +} + +/** + * Read an u_int8_t from the buffer, optionally from the end of the buffer + */ +static bool read_uint8_internal(private_bio_reader_t *this, u_int8_t *res, + bool from_end) { if (this->buf.len < 1) { @@ -56,13 +89,16 @@ METHOD(bio_reader_t, read_uint8, bool, this->buf.len); return FALSE; } - *res = this->buf.ptr[0]; - this->buf = chunk_skip(this->buf, 1); + *res = *get_ptr_end(this, 1, from_end); + this->buf = chunk_skip_end(this->buf, 1, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint16, bool, - private_bio_reader_t *this, u_int16_t *res) +/** + * Read an u_int16_t from the buffer, optionally from the end + */ +static bool read_uint16_internal(private_bio_reader_t *this, u_int16_t *res, + bool from_end) { if (this->buf.len < 2) { @@ -70,13 +106,16 @@ METHOD(bio_reader_t, read_uint16, bool, this->buf.len); return FALSE; } - *res = untoh16(this->buf.ptr); - this->buf = chunk_skip(this->buf, 2); + *res = untoh16(get_ptr_end(this, 2, from_end)); + this->buf = chunk_skip_end(this->buf, 2, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint24, bool, - private_bio_reader_t *this, u_int32_t *res) +/** + * Read an u_int32_t (only 24-bit) from the buffer, optionally from the end + */ +static bool read_uint24_internal(private_bio_reader_t *this, u_int32_t *res, + bool from_end) { if (this->buf.len < 3) { @@ -84,13 +123,16 @@ METHOD(bio_reader_t, read_uint24, bool, this->buf.len); return FALSE; } - *res = untoh32(this->buf.ptr) >> 8; - this->buf = chunk_skip(this->buf, 3); + *res = untoh32(get_ptr_end(this, 3, from_end)) >> 8; + this->buf = chunk_skip_end(this->buf, 3, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint32, bool, - private_bio_reader_t *this, u_int32_t *res) +/** + * Read an u_int32_t from the buffer, optionally from the end + */ +static bool read_uint32_internal(private_bio_reader_t *this, u_int32_t *res, + bool from_end) { if (this->buf.len < 4) { @@ -98,13 +140,16 @@ METHOD(bio_reader_t, read_uint32, bool, this->buf.len); return FALSE; } - *res = untoh32(this->buf.ptr); - this->buf = chunk_skip(this->buf, 4); + *res = untoh32(get_ptr_end(this, 4, from_end)); + this->buf = chunk_skip_end(this->buf, 4, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint64, bool, - private_bio_reader_t *this, u_int64_t *res) +/** + * Read an u_int64_t from the buffer, optionally from the end + */ +static bool read_uint64_internal(private_bio_reader_t *this, u_int64_t *res, + bool from_end) { if (this->buf.len < 8) { @@ -112,13 +157,16 @@ METHOD(bio_reader_t, read_uint64, bool, this->buf.len); return FALSE; } - *res = untoh64(this->buf.ptr); - this->buf = chunk_skip(this->buf, 8); + *res = untoh64(get_ptr_end(this, 8, from_end)); + this->buf = chunk_skip_end(this->buf, 8, from_end); return TRUE; } -METHOD(bio_reader_t, read_data, bool, - private_bio_reader_t *this, u_int32_t len, chunk_t *res) +/** + * Read a chunk of data from the buffer, optionally from the end + */ +static bool read_data_internal(private_bio_reader_t *this, u_int32_t len, + chunk_t *res, bool from_end) { if (this->buf.len < len) { @@ -126,11 +174,83 @@ METHOD(bio_reader_t, read_data, bool, this->buf.len, len); return FALSE; } - *res = chunk_create(this->buf.ptr, len); - this->buf = chunk_skip(this->buf, len); + *res = chunk_create(get_ptr_end(this, len, from_end), len); + this->buf = chunk_skip_end(this->buf, len, from_end); return TRUE; } +METHOD(bio_reader_t, read_uint8, bool, + private_bio_reader_t *this, u_int8_t *res) +{ + return read_uint8_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint16, bool, + private_bio_reader_t *this, u_int16_t *res) +{ + return read_uint16_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint24, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint24_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint32, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint32_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint64, bool, + private_bio_reader_t *this, u_int64_t *res) +{ + return read_uint64_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_data, bool, + private_bio_reader_t *this, u_int32_t len, chunk_t *res) +{ + return read_data_internal(this, len, res, FALSE); +} + +METHOD(bio_reader_t, read_uint8_end, bool, + private_bio_reader_t *this, u_int8_t *res) +{ + return read_uint8_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint16_end, bool, + private_bio_reader_t *this, u_int16_t *res) +{ + return read_uint16_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint24_end, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint24_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint32_end, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint32_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint64_end, bool, + private_bio_reader_t *this, u_int64_t *res) +{ + return read_uint64_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_data_end, bool, + private_bio_reader_t *this, u_int32_t len, chunk_t *res) +{ + return read_data_internal(this, len, res, TRUE); +} + METHOD(bio_reader_t, read_data8, bool, private_bio_reader_t *this, chunk_t *res) { @@ -202,6 +322,12 @@ bio_reader_t *bio_reader_create(chunk_t data) .read_uint32 = _read_uint32, .read_uint64 = _read_uint64, .read_data = _read_data, + .read_uint8_end = _read_uint8_end, + .read_uint16_end = _read_uint16_end, + .read_uint24_end = _read_uint24_end, + .read_uint32_end = _read_uint32_end, + .read_uint64_end = _read_uint64_end, + .read_data_end = _read_data_end, .read_data8 = _read_data8, .read_data16 = _read_data16, .read_data24 = _read_data24, diff --git a/src/libstrongswan/bio/bio_reader.h b/src/libstrongswan/bio/bio_reader.h index 85434a784..3162f3eda 100644 --- a/src/libstrongswan/bio/bio_reader.h +++ b/src/libstrongswan/bio/bio_reader.h @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -27,6 +30,8 @@ typedef struct bio_reader_t bio_reader_t; /** * Buffered input parser. + * + * @note Integers are returned in host byte order. */ struct bio_reader_t { @@ -94,6 +99,55 @@ struct bio_reader_t { bool (*read_data)(bio_reader_t *this, u_int32_t len, chunk_t *res); /** + * Read a 8-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint8_end)(bio_reader_t *this, u_int8_t *res); + + /** + * Read a 16-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint16_end)(bio_reader_t *this, u_int16_t *res); + + /** + * Read a 24-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint24_end)(bio_reader_t *this, u_int32_t *res); + + /** + * Read a 32-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint32_end)(bio_reader_t *this, u_int32_t *res); + + /** + * Read a 64-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint64_end)(bio_reader_t *this, u_int64_t *res); + + /** + * Read a chunk of len bytes from the end of the buffer, reduce remaining. + * + * @param len number of bytes to read + * @param res ponter to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data_end)(bio_reader_t *this, u_int32_t len, chunk_t *res); + + /** * Read a chunk of bytes with a 8-bit length header, advance. * * @param res pointer to result, not cloned diff --git a/src/libstrongswan/bio/bio_writer.c b/src/libstrongswan/bio/bio_writer.c index bf373d6ac..8576843ee 100644 --- a/src/libstrongswan/bio/bio_writer.c +++ b/src/libstrongswan/bio/bio_writer.c @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -199,12 +202,35 @@ METHOD(bio_writer_t, wrap32, void, this->used += 4; } +METHOD(bio_writer_t, skip, chunk_t, + private_bio_writer_t *this, size_t len) +{ + chunk_t skipped; + + while (this->used + len > this->buf.len) + { + increase(this); + } + skipped = chunk_create(this->buf.ptr + this->used, len); + this->used += len; + return skipped; +} + METHOD(bio_writer_t, get_buf, chunk_t, private_bio_writer_t *this) { return chunk_create(this->buf.ptr, this->used); } +METHOD(bio_writer_t, extract_buf, chunk_t, + private_bio_writer_t *this) +{ + chunk_t buf = get_buf(this); + this->buf = chunk_empty; + this->used = 0; + return buf; +} + METHOD(bio_writer_t, destroy, void, private_bio_writer_t *this) { @@ -235,7 +261,9 @@ bio_writer_t *bio_writer_create(u_int32_t bufsize) .wrap16 = _wrap16, .wrap24 = _wrap24, .wrap32 = _wrap32, + .skip = _skip, .get_buf = _get_buf, + .extract_buf = _extract_buf, .destroy = _destroy, }, .increase = bufsize ? max(bufsize, 4) : 32, diff --git a/src/libstrongswan/bio/bio_writer.h b/src/libstrongswan/bio/bio_writer.h index 0b50f7882..57a5c3d38 100644 --- a/src/libstrongswan/bio/bio_writer.h +++ b/src/libstrongswan/bio/bio_writer.h @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -27,6 +30,8 @@ typedef struct bio_writer_t bio_writer_t; /** * Buffered output generator. + * + * @note Integers are converted to network byte order before writing. */ struct bio_writer_t { @@ -121,6 +126,15 @@ struct bio_writer_t { void (*wrap32)(bio_writer_t *this); /** + * Skips len bytes in the buffer before the next data is written, returns + * a chunk covering the skipped bytes. + * + * @param len number of bytes to skip + * @return chunk pointing to skipped bytes in the internal buffer + */ + chunk_t (*skip)(bio_writer_t *this, size_t len); + + /** * Get the encoded data buffer. * * @return chunk to internal buffer @@ -128,6 +142,14 @@ struct bio_writer_t { chunk_t (*get_buf)(bio_writer_t *this); /** + * Return the encoded data buffer and detach it from the writer (resets + * the internal buffer). + * + * @return chunk to internal buffer (has to be freed) + */ + chunk_t (*extract_buf)(bio_writer_t *this); + + /** * Destroy a bio_writer_t. */ void (*destroy)(bio_writer_t *this); @@ -136,6 +158,9 @@ struct bio_writer_t { /** * Create a bio_writer instance. * + * The size of the internal buffer is increased automatically by bufsize (or a + * default if not given) if the initial size does not suffice. + * * @param bufsize initially allocated buffer size */ bio_writer_t *bio_writer_create(u_int32_t bufsize); diff --git a/src/libstrongswan/ipsec/ipsec_types.c b/src/libstrongswan/ipsec/ipsec_types.c new file mode 100644 index 000000000..e4e927313 --- /dev/null +++ b/src/libstrongswan/ipsec/ipsec_types.c @@ -0,0 +1,38 @@ +/* + * 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 "ipsec_types.h" + +ENUM(ipsec_mode_names, MODE_TRANSPORT, MODE_DROP, + "TRANSPORT", + "TUNNEL", + "BEET", + "PASS", + "DROP" +); + +ENUM(policy_dir_names, POLICY_IN, POLICY_FWD, + "in", + "out", + "fwd" +); + +ENUM(ipcomp_transform_names, IPCOMP_NONE, IPCOMP_LZJH, + "IPCOMP_NONE", + "IPCOMP_OUI", + "IPCOMP_DEFLATE", + "IPCOMP_LZS", + "IPCOMP_LZJH" +); diff --git a/src/libstrongswan/ipsec/ipsec_types.h b/src/libstrongswan/ipsec/ipsec_types.h new file mode 100644 index 000000000..32e55bc50 --- /dev/null +++ b/src/libstrongswan/ipsec/ipsec_types.h @@ -0,0 +1,172 @@ +/* + * 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 ipsec_types ipsec_types + * @{ @ingroup ipsec + */ + +#ifndef IPSEC_TYPES_H_ +#define IPSEC_TYPES_H_ + +typedef enum ipsec_mode_t ipsec_mode_t; +typedef enum policy_dir_t policy_dir_t; +typedef enum policy_type_t policy_type_t; +typedef enum policy_priority_t policy_priority_t; +typedef enum ipcomp_transform_t ipcomp_transform_t; +typedef struct ipsec_sa_cfg_t ipsec_sa_cfg_t; +typedef struct lifetime_cfg_t lifetime_cfg_t; +typedef struct mark_t mark_t; + +#include <library.h> + +/** + * Mode of an IPsec SA. + */ +enum ipsec_mode_t { + /** not using any encapsulation */ + MODE_NONE = 0, + /** transport mode, no inner address */ + MODE_TRANSPORT = 1, + /** tunnel mode, inner and outer addresses */ + MODE_TUNNEL, + /** BEET mode, tunnel mode but fixed, bound inner addresses */ + MODE_BEET, + /** passthrough policy for traffic without an IPsec SA */ + MODE_PASS, + /** drop policy discarding traffic */ + MODE_DROP +}; + +/** + * enum names for ipsec_mode_t. + */ +extern enum_name_t *ipsec_mode_names; + +/** + * Direction of a policy. These are equal to those + * defined in xfrm.h, but we want to stay implementation + * neutral here. + */ +enum policy_dir_t { + /** Policy for inbound traffic */ + POLICY_IN = 0, + /** Policy for outbound traffic */ + POLICY_OUT = 1, + /** Policy for forwarded traffic */ + POLICY_FWD = 2, +}; + +/** + * enum names for policy_dir_t. + */ +extern enum_name_t *policy_dir_names; + +/** + * Type of a policy. + */ +enum policy_type_t { + /** Normal IPsec policy */ + POLICY_IPSEC = 1, + /** Passthrough policy (traffic is ignored by IPsec) */ + POLICY_PASS, + /** Drop policy (traffic is discarded) */ + POLICY_DROP, +}; + +/** + * High-level priority of a policy. + */ +enum policy_priority_t { + /** Default priority */ + POLICY_PRIORITY_DEFAULT, + /** Priority for trap policies */ + POLICY_PRIORITY_ROUTED, + /** Priority for fallback drop policies */ + POLICY_PRIORITY_FALLBACK, +}; + +/** + * IPComp transform IDs, as in RFC 4306 + */ +enum ipcomp_transform_t { + IPCOMP_NONE = 0, + IPCOMP_OUI = 1, + IPCOMP_DEFLATE = 2, + IPCOMP_LZS = 3, + IPCOMP_LZJH = 4, +}; + +/** + * enum strings for ipcomp_transform_t. + */ +extern enum_name_t *ipcomp_transform_names; + +/** + * This struct contains details about IPsec SA(s) tied to a policy. + */ +struct ipsec_sa_cfg_t { + /** mode of SA (tunnel, transport) */ + ipsec_mode_t mode; + /** unique ID */ + u_int32_t reqid; + /** details about ESP/AH */ + struct { + /** TRUE if this protocol is used */ + bool use; + /** SPI for ESP/AH */ + u_int32_t spi; + } esp, ah; + /** details about IPComp */ + struct { + /** the IPComp transform used */ + u_int16_t transform; + /** CPI for IPComp */ + u_int16_t cpi; + } ipcomp; +}; + +/** + * A lifetime_cfg_t defines the lifetime limits of an SA. + * + * Set any of these values to 0 to ignore. + */ +struct lifetime_cfg_t { + struct { + /** Limit before the SA gets invalid. */ + u_int64_t life; + /** Limit before the SA gets rekeyed. */ + u_int64_t rekey; + /** The range of a random value subtracted from rekey. */ + u_int64_t jitter; + } time, bytes, packets; +}; + +/** + * A mark_t defines an optional mark in an IPsec SA. + */ +struct mark_t { + /** Mark value */ + u_int32_t value; + /** Mark mask */ + u_int32_t mask; +}; + +/** + * Special mark value that uses the reqid of the CHILD_SA as mark + */ +#define MARK_REQID (0xFFFFFFFF) + +#endif /** IPSEC_TYPES_H_ @}*/ diff --git a/src/libstrongswan/library.h b/src/libstrongswan/library.h index d357ddf5a..634128fe9 100644 --- a/src/libstrongswan/library.h +++ b/src/libstrongswan/library.h @@ -43,6 +43,9 @@ * @defgroup fetcher fetcher * @ingroup libstrongswan * + * @defgroup ipsec ipsec + * @ingroup libstrongswan + * * @defgroup plugins plugins * @ingroup libstrongswan * diff --git a/src/libstrongswan/printf_hook.c b/src/libstrongswan/printf_hook.c index 2ae804380..8bd513c05 100644 --- a/src/libstrongswan/printf_hook.c +++ b/src/libstrongswan/printf_hook.c @@ -93,6 +93,7 @@ static int custom_print(FILE *stream, const struct printf_info *info, }; spec.hash = info->alt; + spec.plus = info->showsign; spec.minus = info->left; spec.width = info->width; @@ -164,6 +165,7 @@ static int custom_fmt_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *fmt_spec) } spec.hash = fmt_spec->fmt_hash; + spec.plus = fmt_spec->fmt_plus; spec.minus = fmt_spec->fmt_minus; spec.width = fmt_spec->fmt_field_width; diff --git a/src/libstrongswan/printf_hook.h b/src/libstrongswan/printf_hook.h index 6beb4fef1..b162e6d97 100644 --- a/src/libstrongswan/printf_hook.h +++ b/src/libstrongswan/printf_hook.h @@ -194,6 +194,11 @@ struct printf_hook_spec_t { int minus; /** + * TRUE if a '+' was used in the format specifier + */ + int plus; + + /** * The width as given in the format specifier. */ int width; diff --git a/src/libstrongswan/threading/thread.c b/src/libstrongswan/threading/thread.c index 49a1b8430..9ef514ebc 100644 --- a/src/libstrongswan/threading/thread.c +++ b/src/libstrongswan/threading/thread.c @@ -114,7 +114,7 @@ typedef struct { /** * Next thread ID. */ -static u_int next_id = 1; +static u_int next_id; /** * Mutex to safely access the next thread ID. @@ -452,6 +452,7 @@ void threads_init() dummy1 = thread_value_create(NULL); + next_id = 1; main_thread->id = 0; main_thread->thread_id = pthread_self(); current_thread = thread_value_create(NULL); @@ -482,4 +483,3 @@ void threads_deinit() current_thread->destroy(current_thread); id_mutex->destroy(id_mutex); } - diff --git a/src/libstrongswan/utils/blocking_queue.c b/src/libstrongswan/utils/blocking_queue.c new file mode 100644 index 000000000..c70184198 --- /dev/null +++ b/src/libstrongswan/utils/blocking_queue.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 "blocking_queue.h" + +#include <threading/mutex.h> +#include <threading/thread.h> +#include <threading/condvar.h> +#include <utils/linked_list.h> + +typedef struct private_blocking_queue_t private_blocking_queue_t; + +/** + * Private data of a blocking_queue_t object. + */ +struct private_blocking_queue_t { + + /** + * Public part + */ + blocking_queue_t public; + + /** + * Linked list containing all items in the queue + */ + linked_list_t *list; + + /** + * Mutex used to synchronize access to the queue + */ + mutex_t *mutex; + + /** + * Condvar used to wait for items + */ + condvar_t *condvar; + +}; + +METHOD(blocking_queue_t, enqueue, void, + private_blocking_queue_t *this, void *item) +{ + this->mutex->lock(this->mutex); + this->list->insert_first(this->list, item); + this->condvar->signal(this->condvar); + this->mutex->unlock(this->mutex); +} + +METHOD(blocking_queue_t, dequeue, void*, + private_blocking_queue_t *this) +{ + bool oldstate; + void *item; + + + this->mutex->lock(this->mutex); + thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex); + /* ensure that a canceled thread does not dequeue any items */ + thread_cancellation_point(); + while (this->list->remove_last(this->list, &item) != SUCCESS) + { + oldstate = thread_cancelability(TRUE); + this->condvar->wait(this->condvar, this->mutex); + thread_cancelability(oldstate); + } + thread_cleanup_pop(TRUE); + return item; +} + +METHOD(blocking_queue_t, destroy, void, + private_blocking_queue_t *this) +{ + this->list->destroy(this->list); + this->condvar->destroy(this->condvar); + this->mutex->destroy(this->mutex); + free(this); +} + +METHOD(blocking_queue_t, destroy_offset, void, + private_blocking_queue_t *this, size_t offset) +{ + this->list->invoke_offset(this->list, offset); + destroy(this); +} + +METHOD(blocking_queue_t, destroy_function, void, + private_blocking_queue_t *this, void (*fn)(void*)) +{ + this->list->invoke_function(this->list, (linked_list_invoke_t)fn); + destroy(this); +} + +/* + * Described in header. + */ +blocking_queue_t *blocking_queue_create() +{ + private_blocking_queue_t *this; + + INIT(this, + .public = { + .enqueue = _enqueue, + .dequeue = _dequeue, + .destroy = _destroy, + .destroy_offset = _destroy_offset, + .destroy_function = _destroy_function, + }, + .list = linked_list_create(), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + return &this->public; +} + diff --git a/src/libstrongswan/utils/blocking_queue.h b/src/libstrongswan/utils/blocking_queue.h new file mode 100644 index 000000000..cf2712cf4 --- /dev/null +++ b/src/libstrongswan/utils/blocking_queue.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 blocking_queue blocking_queue + * @{ @ingroup utils + */ + +#ifndef BLOCKING_QUEUE_H_ +#define BLOCKING_QUEUE_H_ + +typedef struct blocking_queue_t blocking_queue_t; + +#include <library.h> + +/** + * Class implementing a synchronized blocking queue based on linked_list_t + */ +struct blocking_queue_t { + + /** + * Inserts a new item at the tail of the queue + * + * @param item item to insert in queue + */ + void (*enqueue)(blocking_queue_t *this, void *item); + + /** + * Removes the first item in the queue and returns its value. + * If the queue is empty, this call blocks until a new item is inserted. + * + * @note This is a thread cancellation point + * + * @return removed item + */ + void *(*dequeue)(blocking_queue_t *this); + + /** + * Destroys a blocking_queue_t object. + * + * @note No thread must wait in dequeue() when this function is called + */ + void (*destroy)(blocking_queue_t *this); + + /** + * Destroys a queue and its objects using the given destructor. + * + * If a queue and the contained objects should be destroyed, use + * destroy_offset. The supplied offset specifies the destructor to + * call on each object. The offset may be calculated using the offsetof + * macro, e.g.: queue->destroy_offset(queue, offsetof(object_t, destroy)); + * + * @note No thread must wait in dequeue() when this function is called + * + * @param offset offset of the objects destructor + */ + void (*destroy_offset)(blocking_queue_t *this, size_t offset); + + /** + * Destroys a queue and its objects using a cleanup function. + * + * If a queue and its contents should get destroyed using a specific + * cleanup function, use destroy_function. This is useful when the + * list contains malloc()-ed blocks which should get freed, + * e.g.: queue->destroy_function(queue, free); + * + * @note No thread must wait in dequeue() when this function is called + * + * @param function function to call on each object + */ + void (*destroy_function)(blocking_queue_t *this, void (*)(void*)); + +}; + +/** + * Creates an empty queue object. + * + * @return blocking_queue_t object. + */ +blocking_queue_t *blocking_queue_create(); + +#endif /** BLOCKING_QUEUE_H_ @}*/ + diff --git a/src/libstrongswan/utils/host.c b/src/libstrongswan/utils/host.c index 0f40a0dd4..3a16138a4 100644 --- a/src/libstrongswan/utils/host.c +++ b/src/libstrongswan/utils/host.c @@ -110,7 +110,7 @@ int host_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, { snprintf(buffer, sizeof(buffer), "(null)"); } - else if (is_anyaddr(this)) + else if (is_anyaddr(this) && !spec->plus) { snprintf(buffer, sizeof(buffer), "%%any%s", this->address.sa_family == AF_INET6 ? "6" : ""); diff --git a/src/libstrongswan/utils/host.h b/src/libstrongswan/utils/host.h index 444878524..a8b010544 100644 --- a/src/libstrongswan/utils/host.h +++ b/src/libstrongswan/utils/host.h @@ -155,7 +155,7 @@ struct host_t { * * @param string string of an address, such as "152.96.193.130" * @param port port number - * @return host_t, NULL if string not an address. + * @return host_t, NULL if string not an address. */ host_t *host_create_from_string(char *string, u_int16_t port); @@ -165,7 +165,7 @@ host_t *host_create_from_string(char *string, u_int16_t port); * @param string hostname to resolve * @param family family to prefer, 0 for first match * @param port port number - * @return host_t, NULL lookup failed + * @return host_t, NULL lookup failed */ host_t *host_create_from_dns(char *string, int family, u_int16_t port); @@ -174,10 +174,10 @@ host_t *host_create_from_dns(char *string, int family, u_int16_t port); * * If family is AF_UNSPEC, it is guessed using address.len. * - * @param family Address family, such as AF_INET or AF_INET6 + * @param family Address family, such as AF_INET or AF_INET6 * @param address address as chunk_t in network order * @param port port number - * @return host_t, NULL if family not supported/chunk invalid + * @return host_t, NULL if family not supported/chunk invalid */ host_t *host_create_from_chunk(int family, chunk_t address, u_int16_t port); @@ -185,7 +185,7 @@ host_t *host_create_from_chunk(int family, chunk_t address, u_int16_t port); * Constructor to create a host_t object from a sockaddr struct * * @param sockaddr sockaddr struct which contains family, address and port - * @return host_t, NULL if family not supported + * @return host_t, NULL if family not supported */ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr); @@ -202,7 +202,7 @@ host_t *host_create_from_subnet(char *string, int *bits); * Create a host without an address, a "any" host. * * @param family family of the any host - * @return host_t, NULL if family not supported + * @return host_t, NULL if family not supported */ host_t *host_create_any(int family); @@ -212,6 +212,7 @@ host_t *host_create_any(int family); * Arguments are: * host_t *host * Use #-modifier to include port number + * Use +-modifier to force numeric representation (instead of e.g. %any) */ int host_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, const void *const *args); diff --git a/src/libcharon/network/packet.c b/src/libstrongswan/utils/packet.c index c817e00fb..a2c329d60 100644 --- a/src/libcharon/network/packet.c +++ b/src/libstrongswan/utils/packet.c @@ -110,25 +110,26 @@ METHOD(packet_t, clone_, packet_t*, packet_t *other; other = packet_create(); - if (this->destination != NULL) + if (this->destination) { - other->set_destination(other, this->destination->clone(this->destination)); + other->set_destination(other, + this->destination->clone(this->destination)); } - if (this->source != NULL) + if (this->source) { other->set_source(other, this->source->clone(this->source)); } - if (this->data.ptr != NULL) + if (this->data.ptr) { other->set_data(other, chunk_clone(this->adjusted_data)); } return other; } -/* - * Documented in header +/** + * Described in header. */ -packet_t *packet_create(void) +packet_t *packet_create_from_data(host_t *src, host_t *dst, chunk_t data) { private_packet_t *this; @@ -144,8 +145,19 @@ packet_t *packet_create(void) .clone = _clone_, .destroy = _destroy, }, + .source = src, + .destination = dst, + .adjusted_data = data, + .data = data, ); return &this->public; } +/* + * Described in header. + */ +packet_t *packet_create() +{ + return packet_create_from_data(NULL, NULL, chunk_empty); +} diff --git a/src/libcharon/network/packet.h b/src/libstrongswan/utils/packet.h index c53364104..5c4440115 100644 --- a/src/libcharon/network/packet.h +++ b/src/libstrongswan/utils/packet.h @@ -17,7 +17,7 @@ /** * @defgroup packet packet - * @{ @ingroup network + * @{ @ingroup utils */ #ifndef PACKET_H_ @@ -29,97 +29,93 @@ typedef struct packet_t packet_t; #include <utils/host.h> /** - * Abstraction of an UDP-Packet, contains data, sender and receiver. + * Abstraction of an IP/UDP-Packet, contains data, sender and receiver. */ struct packet_t { /** * Set the source address. * - * Set host_t is now owned by packet_t, it will destroy - * it if necessary. - * - * @param source address to set as source + * @param source address to set as source (gets owned) */ - void (*set_source) (packet_t *packet, host_t *source); + void (*set_source)(packet_t *packet, host_t *source); /** * Set the destination address. * - * Set host_t is now owned by packet_t, it will destroy - * it if necessary. - * - * @param source address to set as destination + * @param source address to set as destination (gets owned) */ - void (*set_destination) (packet_t *packet, host_t *destination); + void (*set_destination)(packet_t *packet, host_t *destination); /** * Get the source address. * - * Set host_t is still owned by packet_t, clone it - * if needed. - * - * @return source address + * @return source address (internal data) */ - host_t *(*get_source) (packet_t *packet); + host_t *(*get_source)(packet_t *packet); /** * Get the destination address. * - * Set host_t is still owned by packet_t, clone it - * if needed. - * - * @return destination address + * @return destination address (internal data) */ - host_t *(*get_destination) (packet_t *packet); + host_t *(*get_destination)(packet_t *packet); /** * Get the data from the packet. * - * The data pointed by the chunk is still owned - * by the packet. Clone it if needed. - * - * @return chunk containing the data + * @return chunk containing the data (internal data) */ - chunk_t (*get_data) (packet_t *packet); + chunk_t (*get_data)(packet_t *packet); /** * Set the data in the packet. * - * Supplied chunk data is now owned by the - * packet. It will free it. - * - * @param data chunk with data to set + * @param data chunk with data to set (gets owned) */ - void (*set_data) (packet_t *packet, chunk_t data); + void (*set_data)(packet_t *packet, chunk_t data); /** * Increase the offset where the actual packet data starts. * + * The total offset applies to future calls of get_data() and clone(). + * * @note The offset is reset to 0 when set_data() is called. * * @param bytes the number of additional bytes to skip */ - void (*skip_bytes) (packet_t *packet, size_t bytes); + void (*skip_bytes)(packet_t *packet, size_t bytes); /** * Clones a packet_t object. * + * @note Data is cloned without skipped bytes. + * * @param clone clone of the packet */ - packet_t* (*clone) (packet_t *packet); + packet_t* (*clone)(packet_t *packet); /** * Destroy the packet, freeing contained data. */ - void (*destroy) (packet_t *packet); + void (*destroy)(packet_t *packet); }; /** - * create an empty packet + * Create an empty packet + * + * @return packet_t object + */ +packet_t *packet_create(); + +/** + * Create a packet from the supplied data * + * @param src source address (gets owned) + * @param dst destination address (gets owned) + * @param data packet data (gets owned) * @return packet_t object */ -packet_t *packet_create(void); +packet_t *packet_create_from_data(host_t *src, host_t *dst, chunk_t data); #endif /** PACKET_H_ @}*/ diff --git a/src/libstrongswan/utils/tun_device.c b/src/libstrongswan/utils/tun_device.c new file mode 100644 index 000000000..889fe6247 --- /dev/null +++ b/src/libstrongswan/utils/tun_device.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> +#include <linux/if.h> +#include <linux/if_tun.h> + +#include "tun_device.h" + +#include <library.h> +#include <debug.h> +#include <threading/thread.h> + +#define TUN_DEFAULT_MTU 1500 + +typedef struct private_tun_device_t private_tun_device_t; + +struct private_tun_device_t { + + /** + * Public interface + */ + tun_device_t public; + + /** + * The TUN device's file descriptor + */ + int tunfd; + + /** + * Name of the TUN device + */ + char if_name[IFNAMSIZ]; + + /** + * Socket used for ioctl() to set interface addr, ... + */ + int sock; + + /** + * The current MTU + */ + int mtu; +}; + +/** + * Set the sockaddr_t from the given netmask + */ +static void set_netmask(struct ifreq *ifr, int family, u_int8_t netmask) +{ + int len, bytes, bits; + char *target; + + switch (family) + { + case AF_INET: + { + struct sockaddr_in *addr = (struct sockaddr_in*)&ifr->ifr_addr; + addr->sin_family = AF_INET; + target = (char*)&addr->sin_addr; + len = 4; + break; + } + case AF_INET6: + { + struct sockaddr_in6 *addr = (struct sockaddr_in6*)&ifr->ifr_addr; + addr->sin6_family = AF_INET6; + target = (char*)&addr->sin6_addr; + len = 16; + break; + } + default: + return; + } + + bytes = (netmask + 7) / 8; + bits = (bytes * 8) - netmask; + + memset(target, 0xff, bytes); + memset(target + bytes, 0x00, len - bytes); + target[bytes - 1] = bits ? (u_int8_t)(0xff << bits) : 0xff; +} + +METHOD(tun_device_t, set_address, bool, + private_tun_device_t *this, host_t *addr, u_int8_t netmask) +{ + struct ifreq ifr; + int family; + + family = addr->get_family(addr); + if ((netmask > 32 && family == AF_INET) || netmask > 128) + { + DBG1(DBG_LIB, "failed to set address on %s: invalid netmask", + this->if_name); + return FALSE; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + memcpy(&ifr.ifr_addr, addr->get_sockaddr(addr), sizeof(sockaddr_t)); + + if (ioctl(this->sock, SIOCSIFADDR, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to set address on %s: %s", + this->if_name, strerror(errno)); + return FALSE; + } + + set_netmask(&ifr, family, netmask); + + if (ioctl(this->sock, SIOCSIFNETMASK, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to set netmask on %s: %s", + this->if_name, strerror(errno)); + return FALSE; + } + return TRUE; +} + +METHOD(tun_device_t, up, bool, + private_tun_device_t *this) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + + if (ioctl(this->sock, SIOCGIFFLAGS, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to get interface flags for %s: %s", this->if_name, + strerror(errno)); + return FALSE; + } + + ifr.ifr_flags |= IFF_RUNNING | IFF_UP; + + if (ioctl(this->sock, SIOCSIFFLAGS, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to set interface flags on %s: %s", this->if_name, + strerror(errno)); + return FALSE; + } + return TRUE; +} + +METHOD(tun_device_t, set_mtu, bool, + private_tun_device_t *this, int mtu) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + ifr.ifr_mtu = mtu; + + if (ioctl(this->sock, SIOCSIFMTU, &ifr) < 0) + { + return FALSE; + } + this->mtu = mtu; + return TRUE; +} + +METHOD(tun_device_t, get_mtu, int, + private_tun_device_t *this) +{ + struct ifreq ifr; + + if (this->mtu > 0) + { + return this->mtu; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + this->mtu = TUN_DEFAULT_MTU; + + if (ioctl(this->sock, SIOCGIFMTU, &ifr) == 0) + { + this->mtu = ifr.ifr_mtu; + } + return this->mtu; +} + +METHOD(tun_device_t, get_name, char*, + private_tun_device_t *this) +{ + return this->if_name; +} + +METHOD(tun_device_t, write_packet, bool, + private_tun_device_t *this, chunk_t packet) +{ + ssize_t s; + + s = write(this->tunfd, packet.ptr, packet.len); + if (s < 0) + { + DBG1(DBG_LIB, "failed to write packet to TUN device %s: %s", + this->if_name, strerror(errno)); + return FALSE; + } + else if (s != packet.len) + { + return FALSE; + } + return TRUE; +} + +METHOD(tun_device_t, read_packet, bool, + private_tun_device_t *this, chunk_t *packet) +{ + ssize_t len; + fd_set set; + bool old; + + FD_ZERO(&set); + FD_SET(this->tunfd, &set); + + old = thread_cancelability(TRUE); + len = select(this->tunfd + 1, &set, NULL, NULL, NULL); + thread_cancelability(old); + + if (len < 0) + { + DBG1(DBG_LIB, "select on TUN device %s failed: %s", this->if_name, + strerror(errno)); + return FALSE; + } + /* FIXME: this is quite expensive for lots of small packets, copy from + * local buffer instead? */ + *packet = chunk_alloc(get_mtu(this)); + len = read(this->tunfd, packet->ptr, packet->len); + if (len < 0) + { + DBG1(DBG_LIB, "reading from TUN device %s failed: %s", this->if_name, + strerror(errno)); + chunk_free(packet); + return FALSE; + } + packet->len = len; + return TRUE; +} + +METHOD(tun_device_t, destroy, void, + private_tun_device_t *this) +{ + if (this->tunfd > 0) + { + close(this->tunfd); + } + if (this->sock > 0) + { + close(this->sock); + } + free(this); +} + +/** + * Allocate a TUN device + */ +static int tun_alloc(char dev[IFNAMSIZ]) +{ + struct ifreq ifr; + int fd; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) + { + DBG1(DBG_LIB, "failed to open /dev/net/tun: %s", strerror(errno)); + return fd; + } + + memset(&ifr, 0, sizeof(ifr)); + + /* TUN device, no packet info */ + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + + if (ioctl(fd, TUNSETIFF, (void*)&ifr) < 0) + { + DBG1(DBG_LIB, "failed to configure TUN device: %s", strerror(errno)); + close(fd); + return -1; + } + strncpy(dev, ifr.ifr_name, IFNAMSIZ); + return fd; +} + +/* + * Described in header + */ +tun_device_t *tun_device_create(const char *name_tmpl) +{ + private_tun_device_t *this; + + INIT(this, + .public = { + .read_packet = _read_packet, + .write_packet = _write_packet, + .get_mtu = _get_mtu, + .set_mtu = _set_mtu, + .get_name = _get_name, + .set_address = _set_address, + .up = _up, + .destroy = _destroy, + }, + .tunfd = -1, + .sock = -1, + ); + + strncpy(this->if_name, name_tmpl ?: "tun%d", IFNAMSIZ); + this->if_name[IFNAMSIZ-1] = '\0'; + + this->tunfd = tun_alloc(this->if_name); + if (this->tunfd < 0) + { + destroy(this); + return NULL; + } + DBG1(DBG_LIB, "created TUN device: %s", this->if_name); + + this->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (this->sock < 0) + { + DBG1(DBG_LIB, "failed to open socket to configure TUN device"); + destroy(this); + return NULL; + } + return &this->public; +} diff --git a/src/libstrongswan/utils/tun_device.h b/src/libstrongswan/utils/tun_device.h new file mode 100644 index 000000000..71af0386b --- /dev/null +++ b/src/libstrongswan/utils/tun_device.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 tun_device tun_device + * @{ @ingroup utils + */ + +#ifndef TUN_DEVICE_H_ +#define TUN_DEVICE_H_ + +#include <library.h> +#include <utils/host.h> + +typedef struct tun_device_t tun_device_t; + +/** + * Class to create TUN devices + * + * Creating such a device requires the CAP_NET_ADMIN capability. + * + * @note The implementation is currently very Linux specific + */ +struct tun_device_t { + + /** + * Read a packet from the TUN device + * + * @note This call blocks until a packet is available. It is a thread + * cancellation point. + * + * @param packet the packet read from the device + * @return TRUE if successful + */ + bool (*read_packet)(tun_device_t *this, chunk_t *packet); + + /** + * Write a packet to the TUN device + * + * @param packet the packet to write to the TUN device + * @return TRUE if successful + */ + bool (*write_packet)(tun_device_t *this, chunk_t packet); + + /** + * Set the IP address of the device + * + * @param addr the desired interface address + * @param netmask the netmask to use + * @return TRUE if operation successful + */ + bool (*set_address)(tun_device_t *this, host_t *addr, u_int8_t netmask); + + /** + * Bring the TUN device up + * + * @return TRUE if operation successful + */ + bool (*up)(tun_device_t *this); + + /** + * Set the MTU for this TUN device + * + * @param mtu new MTU + * @return TRUE if operation successful + */ + bool (*set_mtu)(tun_device_t *this, int mtu); + + /** + * Get the current MTU for this TUN device + * + * @return current MTU + */ + int (*get_mtu)(tun_device_t *this); + + /** + * Get the interface name of this device + * + * @return interface name + */ + char *(*get_name)(tun_device_t *this); + + /** + * Destroy a tun_device_t + */ + void (*destroy)(tun_device_t *this); + +}; + +/** + * Create a TUN device using the given name template. + * + * @param name_tmpl name template, defaults to "tun%d" if not given + * @return TUN device + */ +tun_device_t *tun_device_create(const char *name_tmpl); + +#endif /** TUN_DEVICE_H_ @}*/ |