aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.in1
-rw-r--r--src/frontends/android/AndroidManifest.xml56
-rw-r--r--src/frontends/android/jni/libandroidbridge/Android.mk9
-rw-r--r--src/frontends/android/jni/libandroidbridge/android_jni.c107
-rw-r--r--src/frontends/android/jni/libandroidbridge/android_jni.h103
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_attr.c120
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_attr.h52
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_creds.c176
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_creds.h67
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_service.c533
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_service.h61
-rw-r--r--src/frontends/android/jni/libandroidbridge/charonservice.c397
-rw-r--r--src/frontends/android/jni/libandroidbridge/charonservice.h104
-rw-r--r--src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c193
-rw-r--r--src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h48
-rw-r--r--src/frontends/android/jni/libandroidbridge/kernel/android_net.c64
-rw-r--r--src/frontends/android/jni/libandroidbridge/kernel/android_net.h49
-rw-r--r--src/frontends/android/jni/libandroidbridge/vpnservice_builder.c274
-rw-r--r--src/frontends/android/jni/libandroidbridge/vpnservice_builder.h95
-rw-r--r--src/frontends/android/res/drawable-hdpi/ic_launcher.pngbin4147 -> 4953 bytes
-rw-r--r--src/frontends/android/res/drawable-ldpi/ic_launcher.pngbin1723 -> 0 bytes
-rw-r--r--src/frontends/android/res/drawable-mdpi/ic_launcher.pngbin2574 -> 3149 bytes
-rw-r--r--src/frontends/android/res/drawable-xhdpi/ic_launcher.pngbin0 -> 6786 bytes
-rw-r--r--src/frontends/android/res/drawable/vpn_state_background.xml21
-rw-r--r--src/frontends/android/res/layout/log_activity.xml26
-rw-r--r--src/frontends/android/res/layout/log_fragment.xml41
-rw-r--r--src/frontends/android/res/layout/login_dialog.xml51
-rw-r--r--src/frontends/android/res/layout/main.xml36
-rw-r--r--src/frontends/android/res/layout/profile_detail_view.xml109
-rw-r--r--src/frontends/android/res/layout/profile_list_fragment.xml38
-rw-r--r--src/frontends/android/res/layout/profile_list_item.xml49
-rw-r--r--src/frontends/android/res/layout/trusted_certificates_item.xml29
-rw-r--r--src/frontends/android/res/layout/vpn_state_fragment.xml91
-rw-r--r--src/frontends/android/res/menu/log.xml23
-rw-r--r--src/frontends/android/res/menu/main.xml28
-rw-r--r--src/frontends/android/res/menu/profile_edit.xml28
-rw-r--r--src/frontends/android/res/menu/profile_list.xml22
-rw-r--r--src/frontends/android/res/menu/profile_list_context.xml24
-rw-r--r--src/frontends/android/res/values-de/strings.xml83
-rw-r--r--src/frontends/android/res/values/colors.xml24
-rw-r--r--src/frontends/android/res/values/strings.xml82
-rw-r--r--src/frontends/android/res/values/styles.xml21
-rw-r--r--src/frontends/android/src/org/strongswan/android/CharonVpnService.java57
-rw-r--r--src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java149
-rw-r--r--src/frontends/android/src/org/strongswan/android/data/VpnProfile.java113
-rw-r--r--src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java231
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java595
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java211
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java264
-rw-r--r--src/frontends/android/src/org/strongswan/android/strongSwanActivity.java40
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/LogActivity.java86
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/LogFragment.java227
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java78
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/MainActivity.java201
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java375
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java264
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java371
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java149
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java88
-rw-r--r--src/libcharon/Android.mk2
-rw-r--r--src/libcharon/Makefile.am2
-rw-r--r--src/libcharon/daemon.c43
-rw-r--r--src/libcharon/encoding/message.h2
-rw-r--r--src/libcharon/network/receiver.c2
-rw-r--r--src/libcharon/network/receiver.h2
-rw-r--r--src/libcharon/network/sender.c3
-rw-r--r--src/libcharon/network/sender.h2
-rw-r--r--src/libcharon/network/socket.h2
-rw-r--r--src/libcharon/sa/ike_sa.c2
-rw-r--r--src/libcharon/sa/ike_sa.h1
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_mobike.h2
-rw-r--r--src/libhydra/kernel/kernel_ipsec.c22
-rw-r--r--src/libhydra/kernel/kernel_ipsec.h147
-rw-r--r--src/libipsec/Android.mk15
-rw-r--r--src/libipsec/Makefile.am15
-rw-r--r--src/libipsec/esp_context.c300
-rw-r--r--src/libipsec/esp_context.h110
-rw-r--r--src/libipsec/esp_packet.c468
-rw-r--r--src/libipsec/esp_packet.h151
-rw-r--r--src/libipsec/ip_packet.c192
-rw-r--r--src/libipsec/ip_packet.h96
-rw-r--r--src/libipsec/ipsec.c16
-rw-r--r--src/libipsec/ipsec.h29
-rw-r--r--src/libipsec/ipsec_event_listener.h48
-rw-r--r--src/libipsec/ipsec_event_relay.c193
-rw-r--r--src/libipsec/ipsec_event_relay.h79
-rw-r--r--src/libipsec/ipsec_policy.c212
-rw-r--r--src/libipsec/ipsec_policy.h140
-rw-r--r--src/libipsec/ipsec_policy_mgr.c286
-rw-r--r--src/libipsec/ipsec_policy_mgr.h119
-rw-r--r--src/libipsec/ipsec_processor.c324
-rw-r--r--src/libipsec/ipsec_processor.h115
-rw-r--r--src/libipsec/ipsec_sa.c234
-rw-r--r--src/libipsec/ipsec_sa.h169
-rw-r--r--src/libipsec/ipsec_sa_mgr.c626
-rw-r--r--src/libipsec/ipsec_sa_mgr.h167
-rw-r--r--src/libstrongswan/Android.mk7
-rw-r--r--src/libstrongswan/Makefile.am21
-rw-r--r--src/libstrongswan/bio/bio_reader.c174
-rw-r--r--src/libstrongswan/bio/bio_reader.h54
-rw-r--r--src/libstrongswan/bio/bio_writer.c28
-rw-r--r--src/libstrongswan/bio/bio_writer.h25
-rw-r--r--src/libstrongswan/ipsec/ipsec_types.c38
-rw-r--r--src/libstrongswan/ipsec/ipsec_types.h172
-rw-r--r--src/libstrongswan/library.h3
-rw-r--r--src/libstrongswan/printf_hook.c2
-rw-r--r--src/libstrongswan/printf_hook.h5
-rw-r--r--src/libstrongswan/threading/thread.c4
-rw-r--r--src/libstrongswan/utils/blocking_queue.c129
-rw-r--r--src/libstrongswan/utils/blocking_queue.h97
-rw-r--r--src/libstrongswan/utils/host.c2
-rw-r--r--src/libstrongswan/utils/host.h13
-rw-r--r--src/libstrongswan/utils/packet.c (renamed from src/libcharon/network/packet.c)26
-rw-r--r--src/libstrongswan/utils/packet.h (renamed from src/libcharon/network/packet.h)70
-rw-r--r--src/libstrongswan/utils/tun_device.c353
-rw-r--r--src/libstrongswan/utils/tun_device.h112
116 files changed, 12088 insertions, 419 deletions
diff --git a/configure.in b/configure.in
index 918bb04b6..3b9b5c244 100644
--- a/configure.in
+++ b/configure.in
@@ -432,6 +432,7 @@ AC_CHECK_FUNCS(prctl mallinfo getpass closefrom getpwnam_r getgrnam_r)
AC_CHECK_HEADERS(sys/sockio.h glob.h)
AC_CHECK_HEADERS(net/pfkeyv2.h netipsec/ipsec.h netinet6/ipsec.h linux/udp.h)
+AC_CHECK_HEADERS(netinet/ip6.h)
AC_CHECK_MEMBERS([struct sockaddr.sa_len], [], [],
[
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**)&current) == 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
index 8074c4c57..7cd1df4ee 100644
--- a/src/frontends/android/res/drawable-hdpi/ic_launcher.png
+++ b/src/frontends/android/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/src/frontends/android/res/drawable-ldpi/ic_launcher.png b/src/frontends/android/res/drawable-ldpi/ic_launcher.png
deleted file mode 100644
index 1095584ec..000000000
--- a/src/frontends/android/res/drawable-ldpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/src/frontends/android/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/res/drawable-mdpi/ic_launcher.png
index a07c69fa5..200ee9677 100644
--- a/src/frontends/android/res/drawable-mdpi/ic_launcher.png
+++ b/src/frontends/android/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/src/frontends/android/res/drawable-xhdpi/ic_launcher.png b/src/frontends/android/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..2eb6db1b6
--- /dev/null
+++ b/src/frontends/android/res/drawable-xhdpi/ic_launcher.png
Binary files differ
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&#8230;</string>
+ <string name="state_connected">Verbunden</string>
+ <string name="state_disconnecting">Trennen&#8230;</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&#8230;</string>
+ <string name="state_connected">Connected</string>
+ <string name="state_disconnecting">Disconnecting&#8230;</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**)&current))
+ {
+ 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**)&current))
+ {
+ 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**)&current))
+ {
+ 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**)&current))
+ {
+ 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**)&current))
+ {
+ 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**)&current))
+ {
+ 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**)&current))
+ {
+ 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**)&current))
+ {
+ 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_ @}*/