Merge branch 'android-app'

This branch introduces a userland IPsec implementation (libipsec) and an
Android App which targets the VpnService API that is provided by Android 4+.

The implementation is based on the bachelor thesis 'Userland IPsec for
Android 4' by Giuliano Grassi and Ralf Sager.
This commit is contained in:
Tobias Brunner 2012-08-13 12:07:52 +02:00
commit 09ae3d79ca
116 changed files with 12088 additions and 419 deletions

View File

@ -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], [], [],
[

View File

@ -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>
</manifest>

View File

@ -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 -------------------------------------------------------

View File

@ -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);
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -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 JNI_PACKAGE org_strongswan_android
#define ANDROID_DEBUG_LEVEL 1
#define ANDROID_RETRASNMIT_TRIES 3
#define ANDROID_RETRANSMIT_TIMEOUT 3.0
#define ANDROID_RETRANSMIT_BASE 1.4
#define JNI_METHOD_PP(pack, klass, name, ret, ...) \
ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__)
typedef struct private_charonservice_t private_charonservice_t;
#define JNI_METHOD_P(pack, klass, name, ret, ...) \
JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__)
/**
* private data of charonservice
*/
struct private_charonservice_t {
#define JNI_METHOD(klass, name, ret, ...) \
JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__)
/**
* public interface
*/
charonservice_t public;
/**
* android_attr instance
*/
android_attr_t *attr;
/**
* android_creds instance
*/
android_creds_t *creds;
/**
* 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);
@ -67,11 +118,277 @@ 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,8 +414,7 @@ JNI_METHOD(CharonVpnService, initializeCharon, void)
return;
}
if (!libcharon_init("charon") ||
!charon->initialize(charon, PLUGINS))
if (!libcharon_init("charon"))
{
libcharon_deinit();
libipsec_deinit();
@ -107,18 +423,69 @@ JNI_METHOD(CharonVpnService, initializeCharon, void)
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);
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -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;
};

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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" />
</LinearLayout>
<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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
</resources>
<!-- 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>

View File

@ -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>

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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;
}
});
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
});
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
};
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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());
}
});
}
}

View File

@ -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 \

View File

@ -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 \

View File

@ -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());

View File

@ -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>
/**

View File

@ -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

View File

@ -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.

View File

@ -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)
{

View File

@ -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.

View File

@ -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>

View File

@ -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;
}

View File

@ -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.

View File

@ -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.

View File

@ -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
*/

View File

@ -24,158 +24,13 @@
#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.
*

View File

@ -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)

View File

@ -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

300
src/libipsec/esp_context.c Normal file
View File

@ -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;
}

110
src/libipsec/esp_context.h Normal file
View File

@ -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_ @}*/

468
src/libipsec/esp_packet.c Normal file
View File

@ -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;
}

151
src/libipsec/esp_packet.h Normal file
View File

@ -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_ @}*/

192
src/libipsec/ip_packet.c Normal file
View File

@ -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;
}

96
src/libipsec/ip_packet.h Normal file
View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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;
};
/**

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

212
src/libipsec/ipsec_policy.c Normal file
View File

@ -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;
}

140
src/libipsec/ipsec_policy.h Normal file
View File

@ -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 @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

234
src/libipsec/ipsec_sa.c Normal file
View File

@ -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;
}

169
src/libipsec/ipsec_sa.h Normal file
View File

@ -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_ @}*/

626
src/libipsec/ipsec_sa_mgr.c Normal file
View File

@ -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;
}

167
src/libipsec/ipsec_sa_mgr.h Normal file
View File

@ -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_ @}*/

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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 {
@ -93,6 +98,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.
*

Some files were not shown because too many files have changed in this diff Show More