diff options
author | Tobias Brunner <tobias@strongswan.org> | 2015-07-28 13:29:03 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2015-07-28 13:55:23 +0200 |
commit | f14feed01421384b610f896916a28bd4f73f895a (patch) | |
tree | 598a92977c36e3af97755049259c8ac6cbece668 /src/frontends/android | |
parent | fbcac07043a6e98ebba8df8bc2c1eb282ae1a2a3 (diff) | |
parent | 1e323dc1b772a42470939ab53ad295b3bc786e30 (diff) | |
download | strongswan-f14feed01421384b610f896916a28bd4f73f895a.tar.bz2 strongswan-f14feed01421384b610f896916a28bd4f73f895a.tar.xz |
Merge branch 'android-updates'
Fixes the roaming behavior on Android 5+, a linker issue on Android M,
a few bugs, and adds several new advanced options for VPN profile (MTU,
server port, split tunneling).
Also adds methods and a constructor to parse settings_t from a string
instead of a file.
Fixes #782, #847, #865.
Diffstat (limited to 'src/frontends/android')
28 files changed, 945 insertions, 178 deletions
diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 10365985b..5075440e2 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012-2014 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -20,7 +20,7 @@ android:versionCode="26" android:versionName="1.4.5" > - <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" /> + <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="22" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk index 670e83de1..1fb233b48 100644 --- a/src/frontends/android/jni/Android.mk +++ b/src/frontends/android/jni/Android.mk @@ -6,7 +6,7 @@ include $(CLEAR_VARS) strongswan_USE_BYOD := true strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ - pkcs1 pkcs8 pem xcbc hmac socket-default kernel-netlink \ + pkcs1 pkcs8 pem xcbc hmac socket-default \ eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls ifneq ($(strongswan_USE_BYOD),) diff --git a/src/frontends/android/jni/Application.mk b/src/frontends/android/jni/Application.mk index cdfb47400..9fa668354 100644 --- a/src/frontends/android/jni/Application.mk +++ b/src/frontends/android/jni/Application.mk @@ -1,2 +1,3 @@ # select the ABI(s) to build for (see CPU-ARCH-ABIS.html in the NDK docs). APP_ABI := armeabi x86 mips +APP_PLATFORM := android-19 diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c index 7ab9a24bd..a6412bdf7 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.c +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -15,6 +15,8 @@ * for more details. */ +#include <dlfcn.h> + #include "android_jni.h" #include <library.h> @@ -25,6 +27,21 @@ */ static JavaVM *android_jvm; +static struct { + char name[32]; + void *handle; +} libs[] = { + { "libstrongswan.so", NULL }, +#ifdef USE_BYOD + { "libtncif.so", NULL }, + { "libtnccs.so", NULL }, + { "libimcv.so", NULL }, +#endif + { "libhydra.so", NULL }, + { "libcharon.so", NULL }, + { "libipsec.so", NULL }, +}; + jclass *android_charonvpnservice_class; jclass *android_charonvpnservice_builder_class; android_sdk_version_t android_sdk_version; @@ -79,6 +96,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) JNIEnv *env; jclass jversion; jfieldID jsdk_int; + int i; android_jvm = vm; @@ -87,6 +105,15 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) return -1; } + for (i = 0; i < countof(libs); i++) + { + libs[i].handle = dlopen(libs[i].name, RTLD_GLOBAL); + if (!libs[i].handle) + { + return -1; + } + } + androidjni_threadlocal = thread_value_create(attached_thread_cleanup); android_charonvpnservice_class = @@ -109,6 +136,16 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) */ void JNI_OnUnload(JavaVM *vm, void *reserved) { + int i; + androidjni_threadlocal->destroy(androidjni_threadlocal); + + for (i = countof(libs) - 1; i >= 0; i--) + { + if (libs[i].handle) + { + dlclose(libs[i].handle); + } + } } diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 99c0bc2cd..b08670f7e 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -54,6 +54,8 @@ typedef enum { ANDROID_ICE_CREAM_SANDWICH = 14, ANDROID_ICE_CREAM_SANDWICH_MR1 = 15, ANDROID_JELLY_BEAN = 16, + ANDROID_JELLY_BEAN_MR1 = 17, + ANDROID_JELLY_BEAN_MR2 = 18, } android_sdk_version_t; /** diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 896bb0940..7ef3913f7 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 Tobias Brunner + * Copyright (C) 2010-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -32,8 +32,6 @@ typedef struct private_android_service_t private_android_service_t; -#define TUN_DEFAULT_MTU 1400 - /** * private data of Android service */ @@ -55,24 +53,9 @@ struct private_android_service_t { ike_sa_t *ike_sa; /** - * the type of VPN - */ - char *type; - - /** - * gateway + * configuration setttings */ - char *gateway; - - /** - * username - */ - char *username; - - /** - * password - */ - char *password; + settings_t *settings; /** * lock to safely access the TUN device fd @@ -85,6 +68,11 @@ struct private_android_service_t { int tunfd; /** + * MTU of TUN device + */ + int mtu; + + /** * DNS proxy */ android_dns_proxy_t *dns_proxy; @@ -191,7 +179,7 @@ static job_requeue_t handle_plain(private_android_service_t *this) return JOB_REQUEUE_DIRECT; } - raw = chunk_alloc(TUN_DEFAULT_MTU); + raw = chunk_alloc(this->mtu); len = read(tunfd, raw.ptr, raw.len); if (len < 0) { @@ -309,7 +297,7 @@ static bool setup_tun_device(private_android_service_t *this, return FALSE; } if (!add_routes(builder, child_sa) || - !builder->set_mtu(builder, TUN_DEFAULT_MTU)) + !builder->set_mtu(builder, this->mtu)) { return FALSE; } @@ -621,6 +609,7 @@ static void add_auth_cfg_pw(private_android_service_t *this, { identification_t *user; auth_cfg_t *auth; + char *username, *password; auth = auth_cfg_create(); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); @@ -629,12 +618,14 @@ static void add_auth_cfg_pw(private_android_service_t *this, auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TTLS); } - user = identification_create_from_string(this->username); + username = this->settings->get_str(this->settings, "connection.username", + NULL); + password = this->settings->get_str(this->settings, "connection.password", + NULL); + user = identification_create_from_string(username); auth->add(auth, AUTH_RULE_IDENTITY, user); - this->creds->add_username_password(this->creds, this->username, - this->password); - memwipe(this->password, strlen(this->password)); + this->creds->add_username_password(this->creds, username, password); peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); } @@ -644,6 +635,7 @@ static bool add_auth_cfg_cert(private_android_service_t *this, certificate_t *cert; identification_t *id; auth_cfg_t *auth; + char *type; cert = this->creds->load_user_certificate(this->creds); if (!cert) @@ -651,8 +643,9 @@ static bool add_auth_cfg_cert(private_android_service_t *this, return FALSE; } + type = this->settings->get_str(this->settings, "connection.type", NULL); auth = auth_cfg_create(); - if (strpfx("ikev2-eap-tls", this->type)) + if (strpfx("ikev2-eap-tls", type)) { auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS); @@ -687,11 +680,15 @@ static job_requeue_t initiate(private_android_service_t *this) .jitter = 300 /* 5min */ } }; + char *type, *server; + int port; + server = this->settings->get_str(this->settings, "connection.server", NULL); + port = this->settings->get_int(this->settings, "connection.port", + IKEV2_UDP_PORT); ike_cfg = ike_cfg_create(IKEV2, TRUE, TRUE, "0.0.0.0", charon->socket->get_port(charon->socket, FALSE), - this->gateway, IKEV2_UDP_PORT, - FRAGMENTATION_YES, 0); + server, port, FRAGMENTATION_YES, 0); ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE)); @@ -705,10 +702,11 @@ static job_requeue_t initiate(private_android_service_t *this) peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET)); peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET6)); + type = this->settings->get_str(this->settings, "connection.type", NULL); /* local auth config */ - if (streq("ikev2-cert", this->type) || - streq("ikev2-cert-eap", this->type) || - streq("ikev2-eap-tls", this->type)) + if (streq("ikev2-cert", type) || + streq("ikev2-cert-eap", type) || + streq("ikev2-eap-tls", type)) { if (!add_auth_cfg_cert(this, peer_cfg)) { @@ -718,16 +716,16 @@ static job_requeue_t initiate(private_android_service_t *this) return JOB_REQUEUE_NONE; } } - if (streq("ikev2-eap", this->type) || - streq("ikev2-cert-eap", this->type) || - streq("ikev2-byod-eap", this->type)) + if (streq("ikev2-eap", type) || + streq("ikev2-cert-eap", type) || + streq("ikev2-byod-eap", type)) { - add_auth_cfg_pw(this, peer_cfg, strpfx(this->type, "ikev2-byod")); + add_auth_cfg_pw(this, peer_cfg, strpfx(type, "ikev2-byod")); } /* remote auth config */ auth = auth_cfg_create(); - gateway = identification_create_from_string(this->gateway); + gateway = identification_create_from_string(server); auth->add(auth, AUTH_RULE_IDENTITY, gateway); auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); @@ -806,23 +804,15 @@ METHOD(android_service_t, destroy, void, close_tun_device(this); this->dns_proxy->destroy(this->dns_proxy); this->lock->destroy(this->lock); - free(this->type); - free(this->gateway); - free(this->username); - if (this->password) - { - memwipe(this->password, strlen(this->password)); - free(this->password); - } + this->settings->destroy(this->settings); free(this); } /** * See header */ -android_service_t *android_service_create(android_creds_t *creds, char *type, - char *gateway, char *username, - char *password) +android_service_t *android_service_create(android_creds_t *creds, + settings_t *settings) { private_android_service_t *this; @@ -840,15 +830,14 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, }, .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .dns_proxy = android_dns_proxy_create(), - .username = username, - .password = password, - .gateway = gateway, + .settings = settings, .creds = creds, - .type = type, .tunfd = -1, + .mtu = settings->get_int(settings, "global.mtu", ANDROID_DEFAULT_MTU), ); /* only allow queries for the VPN gateway */ - this->dns_proxy->add_hostname(this->dns_proxy, gateway); + this->dns_proxy->add_hostname(this->dns_proxy, + this->settings->get_str(this->settings, "connection.server", NULL)); charon->bus->add_listener(charon->bus, &this->public.listener); diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h index 1bfdcf994..1a5175774 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 Tobias Brunner + * Copyright (C) 2010-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -52,13 +52,9 @@ struct android_service_t { * new IKE SA. * * @param creds Android specific credential set - * @param type VPN type (see VpnType.java) - * @param gateway gateway address - * @param username user name (local identity) - * @param password password (if any) + * @param settings configuration settings (gets adopted) */ -android_service_t *android_service_create(android_creds_t *creds, char *type, - char *gateway, char *username, - char *password); +android_service_t *android_service_create(android_creds_t *creds, + settings_t *settings); #endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 524630024..2655f7361 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -44,7 +44,6 @@ #define ANDROID_RETRASNMIT_TRIES 3 #define ANDROID_RETRANSMIT_TIMEOUT 2.0 #define ANDROID_RETRANSMIT_BASE 1.4 -#define ANDROID_FRAGMENT_SIZE 1400 typedef struct private_charonservice_t private_charonservice_t; @@ -84,11 +83,6 @@ struct private_charonservice_t { network_manager_t *network_manager; /** - * Handle network events - */ - android_net_t *net_handler; - - /** * CharonVpnService reference */ jobject vpn_service; @@ -400,18 +394,27 @@ METHOD(charonservice_t, get_network_manager, network_manager_t*, /** * Initiate a new connection * - * @param gateway gateway address (gets owned) - * @param username username (gets owned) - * @param password password (gets owned) + * @param settings configuration settings (gets owned) */ -static void initiate(char *type, char *gateway, char *username, char *password) +static void initiate(settings_t *settings) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + lib->settings->set_str(lib->settings, + "charon.plugins.tnc-imc.preferred_language", + settings->get_str(settings, "global.language", "en")); + /* this is actually the size of the complete IKE/IP packet, so if the MTU + * for the TUN devices has to be reduced to pass traffic the IKE packets + * will be a bit smaller than necessary as there is no IPsec overhead like + * for the tunneled traffic (but compensating that seems like overkill) */ + lib->settings->set_int(lib->settings, + "charon.fragment_size", + settings->get_int(settings, "global.mtu", + ANDROID_DEFAULT_MTU)); + this->creds->clear(this->creds); DESTROY_IF(this->service); - this->service = android_service_create(this->creds, type, gateway, - username, password); + this->service = android_service_create(this->creds, settings); } /** @@ -423,14 +426,12 @@ static bool charonservice_register(plugin_t *plugin, plugin_feature_t *feature, private_charonservice_t *this = (private_charonservice_t*)charonservice; if (reg) { - this->net_handler = android_net_create(); lib->credmgr->add_set(lib->credmgr, &this->creds->set); charon->attributes->add_handler(charon->attributes, &this->attr->handler); } else { - this->net_handler->destroy(this->net_handler); lib->credmgr->remove_set(lib->credmgr, &this->creds->set); charon->attributes->remove_handler(charon->attributes, &this->attr->handler); @@ -466,8 +467,8 @@ static void set_options(char *logfile) "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); lib->settings->set_double(lib->settings, "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); - lib->settings->set_int(lib->settings, - "charon.fragment_size", ANDROID_FRAGMENT_SIZE); + lib->settings->set_bool(lib->settings, + "charon.initiator_only", TRUE); lib->settings->set_bool(lib->settings, "charon.close_ike_on_child_failure", TRUE); /* setting the source address breaks the VpnService.protect() function which @@ -483,19 +484,6 @@ static void set_options(char *logfile) * so lets disable IPv6 for now to avoid issues with dual-stack gateways */ lib->settings->set_bool(lib->settings, "charon.plugins.socket-default.use_ipv6", FALSE); - /* don't install virtual IPs via kernel-netlink */ - lib->settings->set_bool(lib->settings, - "charon.install_virtual_ip", FALSE); - /* kernel-netlink should not trigger roam events, we use Android's - * ConnectivityManager for that, much less noise */ - lib->settings->set_bool(lib->settings, - "charon.plugins.kernel-netlink.roam_events", FALSE); - /* ignore tun devices (it's mostly tun0 but it may already be taken, ignore - * some others too), also ignore lo as a default route points to it when - * no connectivity is available */ - lib->settings->set_str(lib->settings, - "charon.interfaces_ignore", "lo, tun0, tun1, tun2, tun3, " - "tun4"); #ifdef USE_BYOD lib->settings->set_str(lib->settings, @@ -519,6 +507,8 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, static plugin_feature_t features[] = { PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-net"), PLUGIN_CALLBACK(charonservice_register, NULL), PLUGIN_PROVIDE(CUSTOM, "android-backend"), PLUGIN_DEPENDS(CUSTOM, "libcharon"), @@ -705,14 +695,12 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void) * Initiate SA */ JNI_METHOD(CharonVpnService, initiate, void, - jstring jtype, jstring jgateway, jstring jusername, jstring jpassword) + jstring jconfig) { - char *type, *gateway, *username, *password; - - type = androidjni_convert_jstring(env, jtype); - gateway = androidjni_convert_jstring(env, jgateway); - username = androidjni_convert_jstring(env, jusername); - password = androidjni_convert_jstring(env, jpassword); + settings_t *settings; + char *config; - initiate(type, gateway, username, password); + config = androidjni_convert_jstring(env, jconfig); + settings = settings_create_string(config); + initiate(settings); } diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 0c71d876d..8cb68e099 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -45,6 +45,11 @@ typedef enum android_imc_state_t android_imc_state_t; typedef struct charonservice_t charonservice_t; /** + * Default value for the MTU of TUN device and the size of IKE fragments + */ +#define ANDROID_DEFAULT_MTU 1400 + +/** * VPN status codes. As defined in CharonVpnService.java */ enum android_vpn_state_t { diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c index 653e2738b..2ce1bdfac 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -11,9 +11,14 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <errno.h> #include "android_net.h" +#include "../android_jni.h" #include "../charonservice.h" #include <hydra.h> #include <processing/jobs/callback_job.h> @@ -21,6 +26,7 @@ /** delay before firing roam events (ms) */ #define ROAM_DELAY 100 +#define ROAM_DELAY_RECHECK 1000 typedef struct private_android_net_t private_android_net_t; @@ -29,7 +35,7 @@ struct private_android_net_t { /** * Public kernel interface */ - android_net_t public; + kernel_net_t public; /** * Reference to NetworkManager object @@ -37,14 +43,29 @@ struct private_android_net_t { network_manager_t *network_manager; /** - * earliest time of the next roam event + * Earliest time of the next roam event */ timeval_t next_roam; /** - * mutex to check and update roam event time + * Mutex to check and update roam event time, and other private members */ mutex_t *mutex; + + /** + * List of virtual IPs + */ + linked_list_t *vips; + + /** + * Socket used to determine source address + */ + int socket_v4; + + /** + * Whether the device is currently connected + */ + bool connected; }; /** @@ -69,6 +90,7 @@ static void connectivity_cb(private_android_net_t *this, time_monotonic(&now); this->mutex->lock(this->mutex); + this->connected = !disconnected; if (!timercmp(&now, &this->next_roam, >)) { this->mutex->unlock(this->mutex); @@ -83,32 +105,210 @@ static void connectivity_cb(private_android_net_t *this, lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY); } -METHOD(android_net_t, destroy, void, +METHOD(kernel_net_t, get_source_addr, host_t*, + private_android_net_t *this, host_t *dest, host_t *src) +{ + union { + struct sockaddr sockaddr; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } addr; + socklen_t addrlen; + timeval_t now; + job_t *job; + + addrlen = *dest->get_sockaddr_len(dest); + addr.sockaddr.sa_family = AF_UNSPEC; + if (connect(this->socket_v4, &addr.sockaddr, addrlen) < 0) + { + DBG1(DBG_KNL, "failed to disconnect socket: %s", strerror(errno)); + return NULL; + } + if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) + { /* this seems to help avoiding the VIP, unless there is no connectivity + * at all */ + charonservice->bypass_socket(charonservice, -1, 0); + } + if (connect(this->socket_v4, dest->get_sockaddr(dest), addrlen) < 0) + { + /* don't report an error if we are not connected (ENETUNREACH) */ + if (errno != ENETUNREACH) + { + DBG1(DBG_KNL, "failed to connect socket: %s", strerror(errno)); + } + else + { + time_monotonic(&now); + this->mutex->lock(this->mutex); + if (this->connected && timercmp(&now, &this->next_roam, >)) + { /* we were not able to find a source address but reportedly are + * connected, trigger a recheck in case an IP address appears + * delayed but the callback is not triggered again */ + timeval_add_ms(&now, ROAM_DELAY_RECHECK); + this->next_roam = now; + this->mutex->unlock(this->mutex); + job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, + NULL, NULL, NULL); + lib->scheduler->schedule_job_ms(lib->scheduler, job, + ROAM_DELAY_RECHECK); + } + else + { + this->mutex->unlock(this->mutex); + } + } + return NULL; + } + if (getsockname(this->socket_v4, &addr.sockaddr, &addrlen) < 0) + { + DBG1(DBG_KNL, "failed to determine src address: %s", strerror(errno)); + return NULL; + } + return host_create_from_sockaddr((sockaddr_t*)&addr); +} + +METHOD(kernel_net_t, get_source_addr_old, host_t*, + private_android_net_t *this, host_t *dest, host_t *src) +{ + host_t *host; + + /* on older Android versions we might get the virtual IP back because + * the protect() implementation there and connect() don't properly work + * together, on newer releases (using fwmarks) that's not a problem */ + host = get_source_addr(this, dest, src); + if (host) + { + this->mutex->lock(this->mutex); + if (this->vips->find_first(this->vips, (void*)host->ip_equals, + NULL, host) == SUCCESS) + { + host->destroy(host); + host = NULL; + } + this->mutex->unlock(this->mutex); + } + return host; +} + +METHOD(kernel_net_t, get_nexthop, host_t*, + private_android_net_t *this, host_t *dest, int prefix, host_t *src) +{ + return NULL; +} + +METHOD(kernel_net_t, get_interface, bool, + private_android_net_t *this, host_t *host, char **name) +{ + if (name) + { /* the actual name does not matter in our case */ + *name = strdup("strongswan"); + } + return TRUE; +} + +METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, + private_android_net_t *this, kernel_address_type_t which) +{ + /* return virtual IPs if requested, nothing otherwise */ + if (which & ADDR_TYPE_VIRTUAL) + { + this->mutex->lock(this->mutex); + return enumerator_create_cleaner( + this->vips->create_enumerator(this->vips), + (void*)this->mutex->unlock, this->mutex); + } + return enumerator_create_empty(); +} + +METHOD(kernel_net_t, add_ip, status_t, + private_android_net_t *this, host_t *virtual_ip, int prefix, char *iface) +{ + this->mutex->lock(this->mutex); + this->vips->insert_last(this->vips, virtual_ip->clone(virtual_ip)); + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_net_t, del_ip, status_t, + private_android_net_t *this, host_t *virtual_ip, int prefix, bool wait) +{ + host_t *vip; + + this->mutex->lock(this->mutex); + if (this->vips->find_first(this->vips, (void*)virtual_ip->ip_equals, + (void**)&vip, virtual_ip) == SUCCESS) + { + this->vips->remove(this->vips, vip, NULL); + vip->destroy(vip); + } + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_net_t, add_route, status_t, + private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, del_route, status_t, + private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, destroy, void, private_android_net_t *this) { this->network_manager->remove_connectivity_cb(this->network_manager, (void*)connectivity_cb); this->mutex->destroy(this->mutex); + this->vips->destroy(this->vips); + close(this->socket_v4); free(this); } -/* - * Described in header. - */ -android_net_t *android_net_create() +kernel_net_t *kernel_android_net_create() { private_android_net_t *this; INIT(this, .public = { + .get_source_addr = _get_source_addr, + .get_nexthop = _get_nexthop, + .get_interface = _get_interface, + .create_address_enumerator = _create_address_enumerator, + .add_ip = _add_ip, + .del_ip = _del_ip, + .add_route = _add_route, + .del_route = _del_route, .destroy = _destroy, }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .vips = linked_list_create(), .network_manager = charonservice->get_network_manager(charonservice), ); timerclear(&this->next_roam); - this->network_manager->add_connectivity_cb(this->network_manager, - (void*)connectivity_cb, this); + if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) + { + this->public.get_source_addr = _get_source_addr_old; + } + + this->socket_v4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (this->socket_v4 < 0) + { + DBG1(DBG_KNL, "failed to create socket to lookup src addresses: %s", + strerror(errno)); + } + charonservice->bypass_socket(charonservice, this->socket_v4, AF_INET); + + this->mutex->lock(this->mutex); + this->network_manager->add_connectivity_cb( + this->network_manager, (void*)connectivity_cb, this); + this->connected = this->network_manager->is_connected(this->network_manager); + this->mutex->unlock(this->mutex); return &this->public; -}; +} diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h index ade83f32a..761fa21bc 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -22,25 +22,14 @@ #define ANDROID_NET_H_ #include <library.h> - -typedef struct android_net_t android_net_t; - -/** - * Handle connectivity events from NetworkManager - */ -struct android_net_t { - - /** - * Destroy an android_net_t instance. - */ - void (*destroy)(android_net_t *this); -}; +#include <kernel/kernel_net.h> /** - * Create an android_net_t instance. + * Create an Android-specific kernel_net_t instance. * - * @return android_net_t instance + * @return kernel_net_t instance */ -android_net_t *android_net_create(); +kernel_net_t *kernel_android_net_create(); + #endif /** ANDROID_NET_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c index f8e560b56..372b25c55 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -90,8 +90,8 @@ METHOD(network_manager_t, add_connectivity_cb, void, this->connectivity_cb.cb = cb; this->connectivity_cb.data = data; } - androidjni_detach_thread(); } + androidjni_detach_thread(); } this->mutex->unlock(this->mutex); } @@ -132,6 +132,28 @@ METHOD(network_manager_t, remove_connectivity_cb, void, this->mutex->unlock(this->mutex); } +METHOD(network_manager_t, is_connected, bool, + private_network_manager_t *this) +{ + JNIEnv *env; + jmethodID method_id; + bool connected = FALSE; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "isConnected", "()Z"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + connected = (*env)->CallBooleanMethod(env, this->obj, method_id); + connected = !androidjni_exception_occurred(env) && connected; + } + androidjni_detach_thread(); + return connected; +} + METHOD(network_manager_t, destroy, void, private_network_manager_t *this) { @@ -174,6 +196,7 @@ network_manager_t *network_manager_create(jobject context) .public = { .add_connectivity_cb = _add_connectivity_cb, .remove_connectivity_cb = _remove_connectivity_cb, + .is_connected = _is_connected, .destroy = _destroy, }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h index abca239ea..9a6a715b7 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h +++ b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -66,6 +66,13 @@ struct network_manager_t { connectivity_cb_t cb); /** + * Check whether we currently have connectivity + * + * @return TRUE if currently connected + */ + bool (*is_connected)(network_manager_t *this); + + /** * Destroy a network_manager_t instance */ void (*destroy)(network_manager_t *this); diff --git a/src/frontends/android/project.properties b/src/frontends/android/project.properties index a5578ba09..bbe203c84 100644 --- a/src/frontends/android/project.properties +++ b/src/frontends/android/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-19 +target=android-21 diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml index 91cd345ff..57d5606ff 100644 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -139,6 +139,65 @@ android:id="@+id/select_certificate" layout="@layout/two_line_button" /> + <CheckBox + android:id="@+id/show_advanced" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_show_advanced_label" /> + + <LinearLayout + android:id="@+id/advanced_settings" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_mtu_label" /> + + <EditText + android:id="@+id/mtu" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="number|textNoSuggestions" + android:hint="@string/profile_use_default_hint" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_port_label" /> + + <EditText + android:id="@+id/port" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="number|textNoSuggestions" + android:hint="@string/profile_use_default_hint" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_split_tunneling_label" /> + + <CheckBox + android:id="@+id/split_tunneling_v4" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_split_tunnelingv4_title" /> + + <CheckBox + android:id="@+id/split_tunneling_v6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_split_tunnelingv6_title" /> + + </LinearLayout> </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 491fe8a8a..6cd5ba50a 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012-2013 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -63,11 +63,19 @@ <string name="profile_ca_auto_label">Automatisch wählen</string> <string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string> <string name="profile_ca_select_certificate">Wählen Sie ein bestimmtes CA-Zertifikat</string> + <string name="profile_show_advanced_label">Erweiterte Einstellungen anzeigen</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server Port:</string> + <string name="profile_use_default_hint">(Standardwert verwenden)</string> + <string name="profile_split_tunneling_label">Split-Tunneling:</string> + <string name="profile_split_tunnelingv4_title">Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist</string> + <string name="profile_split_tunnelingv6_title">Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist</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> + <string name="alert_text_out_of_range">Bitte geben Sie eine Nummer von %1$d - %2$d ein</string> <string name="tnc_notice_title">EAP-TNC kann Ihre Privatsphäre beeinträchtigen</string> <string name="tnc_notice_subtitle">Gerätedaten werden an den Gateway-Betreiber gesendet</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) erlaubt Gateway-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.</p><p>Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.</p><b>Solche Daten werden nur übermittelt nachdem die Identität des Gateways geprüft wurde.</b></string> diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index d0cfa48f1..fb2aba003 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -63,11 +63,19 @@ <string name="profile_ca_auto_label">Wybierz automatycznie</string> <string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string> <string name="profile_ca_select_certificate">Wybierz określony certyfikat CA</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Wprowadź adres bramki</string> <string name="alert_text_no_input_username">Wprowadź swoją nazwę użytkownika</string> <string name="alert_text_nocertfound_title">Nie wybrano żadnego certyfikatu CA</string> <string name="alert_text_nocertfound">Wybierz lub uaktywnij jeden <i>Wybierz automatycznie</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml index eb69183db..eabfc084b 100644 --- a/src/frontends/android/res/values-ru/strings.xml +++ b/src/frontends/android/res/values-ru/strings.xml @@ -60,11 +60,19 @@ <string name="profile_ca_auto_label">Выбрать автоматически</string> <string name="profile_ca_select_certificate_label">Выбрать сертификат CA</string> <string name="profile_ca_select_certificate">Выбрать CA сертификат</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Пожалуйста введите адрес шлюза</string> <string name="alert_text_no_input_username">Пожалуйста введите имя пользователя</string> <string name="alert_text_nocertfound_title">Не выбран сертификат CA</string> <string name="alert_text_nocertfound">Пожалуйста выберите один <i>Выбрать автоматически</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml index e23b9b9b2..d7c238370 100644 --- a/src/frontends/android/res/values-ua/strings.xml +++ b/src/frontends/android/res/values-ua/strings.xml @@ -61,11 +61,19 @@ <string name="profile_ca_auto_label">Вибрати автоматично</string> <string name="profile_ca_select_certificate_label">Вибрати сертифікат CA</string> <string name="profile_ca_select_certificate">Вибрати спеціальний сертифікат CA</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Введіть адресу шлюза тут</string> <string name="alert_text_no_input_username">Введіть ім\'я користувача тут</string> <string name="alert_text_nocertfound_title">Не вибрано сертифікат CA</string> <string name="alert_text_nocertfound">Будь ласка виберіть один <i>Вибрати автоматично</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 933a80aff..5c8ebab57 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012-2013 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -63,11 +63,19 @@ <string name="profile_ca_auto_label">Select automatically</string> <string name="profile_ca_select_certificate_label">Select CA certificate</string> <string name="profile_ca_select_certificate">Select a specific CA certificate</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</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> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java index 8323826d2..5c64ad0e5 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -17,9 +17,15 @@ package org.strongswan.android.data; + public class VpnProfile implements Cloneable { + /* While storing this as EnumSet would be nicer this simplifies storing it in a database */ + public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1; + public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2; + private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate; + private Integer mMTU, mPort, mSplitTunneling; private VpnType mVpnType; private long mId = -1; @@ -103,6 +109,36 @@ public class VpnProfile implements Cloneable this.mUserCertificate = alias; } + public Integer getMTU() + { + return mMTU; + } + + public void setMTU(Integer mtu) + { + this.mMTU = mtu; + } + + public Integer getPort() + { + return mPort; + } + + public void setPort(Integer port) + { + this.mPort = port; + } + + public Integer getSplitTunneling() + { + return mSplitTunneling; + } + + public void setSplitTunneling(Integer splitTunneling) + { + this.mSplitTunneling = splitTunneling; + } + @Override public String toString() { diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java index 6fd68d0c8..45e9b8650 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -40,6 +40,9 @@ public class VpnProfileDataSource public static final String KEY_PASSWORD = "password"; public static final String KEY_CERTIFICATE = "certificate"; public static final String KEY_USER_CERTIFICATE = "user_certificate"; + public static final String KEY_MTU = "mtu"; + public static final String KEY_PORT = "port"; + public static final String KEY_SPLIT_TUNNELING = "split_tunneling"; private DatabaseHelper mDbHelper; private SQLiteDatabase mDatabase; @@ -48,7 +51,7 @@ public class VpnProfileDataSource private static final String DATABASE_NAME = "strongswan.db"; private static final String TABLE_VPNPROFILE = "vpnprofile"; - private static final int DATABASE_VERSION = 4; + private static final int DATABASE_VERSION = 7; public static final String DATABASE_CREATE = "CREATE TABLE " + TABLE_VPNPROFILE + " (" + @@ -59,7 +62,10 @@ public class VpnProfileDataSource KEY_USERNAME + " TEXT," + KEY_PASSWORD + " TEXT," + KEY_CERTIFICATE + " TEXT," + - KEY_USER_CERTIFICATE + " TEXT" + + KEY_USER_CERTIFICATE + " TEXT," + + KEY_MTU + " INTEGER," + + KEY_PORT + " INTEGER," + + KEY_SPLIT_TUNNELING + " INTEGER" + ");"; private static final String[] ALL_COLUMNS = new String[] { KEY_ID, @@ -70,6 +76,9 @@ public class VpnProfileDataSource KEY_PASSWORD, KEY_CERTIFICATE, KEY_USER_CERTIFICATE, + KEY_MTU, + KEY_PORT, + KEY_SPLIT_TUNNELING, }; private static class DatabaseHelper extends SQLiteOpenHelper @@ -104,6 +113,21 @@ public class VpnProfileDataSource { /* remove NOT NULL constraint from username column */ updateColumns(db); } + if (oldVersion < 5) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU + + " INTEGER;"); + } + if (oldVersion < 6) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT + + " INTEGER;"); + } + if (oldVersion < 7) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING + + " INTEGER;"); + } } private void updateColumns(SQLiteDatabase db) @@ -255,6 +279,9 @@ public class VpnProfileDataSource profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE))); + profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU))); + profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT))); + profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING))); return profile; } @@ -268,6 +295,14 @@ public class VpnProfileDataSource values.put(KEY_PASSWORD, profile.getPassword()); values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias()); + values.put(KEY_MTU, profile.getMTU()); + values.put(KEY_PORT, profile.getPort()); + values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling()); return values; } + + private Integer getInt(Cursor cursor, int columnIndex) + { + return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex); + } } diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnType.java b/src/frontends/android/src/org/strongswan/android/data/VpnType.java index bffa8384c..bb7fd09f3 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnType.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnType.java @@ -24,7 +24,7 @@ public enum VpnType IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)), IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)), IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)), - IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE, VpnTypeFeature.BYOD)); + IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD)); /** * Features of a VPN type. diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index 7cdaee735..e5241d5a7 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -18,11 +18,16 @@ package org.strongswan.android.logic; import java.io.File; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; @@ -32,7 +37,9 @@ import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.logic.imc.ImcState; import org.strongswan.android.logic.imc.RemediationInstruction; import org.strongswan.android.ui.MainActivity; +import org.strongswan.android.utils.SettingsWriter; +import android.annotation.TargetApi; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; @@ -40,11 +47,13 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.net.VpnService; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.security.KeyChain; import android.security.KeyChainException; +import android.system.OsConstants; import android.util.Log; public class CharonVpnService extends VpnService implements Runnable @@ -211,13 +220,19 @@ public class CharonVpnService extends VpnService implements Runnable startConnection(mCurrentProfile); mIsDisconnecting = false; - BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName()); + BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling()); if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) { Log.i(TAG, "charon started"); - initiate(mCurrentProfile.getVpnType().getIdentifier(), - mCurrentProfile.getGateway(), mCurrentProfile.getUsername(), - mCurrentProfile.getPassword()); + SettingsWriter writer = new SettingsWriter(); + writer.setValue("global.language", Locale.getDefault().getLanguage()); + writer.setValue("global.mtu", mCurrentProfile.getMTU()); + writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier()); + writer.setValue("connection.server", mCurrentProfile.getGateway()); + writer.setValue("connection.port", mCurrentProfile.getPort()); + writer.setValue("connection.username", mCurrentProfile.getUsername()); + writer.setValue("connection.password", mCurrentProfile.getPassword()); + initiate(writer.serialize()); } else { @@ -497,7 +512,6 @@ public class CharonVpnService extends VpnService implements Runnable private PrivateKey getUserKey() throws KeyChainException, InterruptedException { return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias); - } /** @@ -518,7 +532,7 @@ public class CharonVpnService extends VpnService implements Runnable /** * Initiate VPN, provided by libandroidbridge.so */ - public native void initiate(String type, String gateway, String username, String password); + public native void initiate(String config); /** * Adapter for VpnService.Builder which is used to access it safely via JNI. @@ -527,15 +541,17 @@ public class CharonVpnService extends VpnService implements Runnable public class BuilderAdapter { private final String mName; + private final Integer mSplitTunneling; private VpnService.Builder mBuilder; private BuilderCache mCache; private BuilderCache mEstablishedCache; - public BuilderAdapter(String name) + public BuilderAdapter(String name, Integer splitTunneling) { mName = name; + mSplitTunneling = splitTunneling; mBuilder = createBuilder(name); - mCache = new BuilderCache(); + mCache = new BuilderCache(mSplitTunneling); } private VpnService.Builder createBuilder(String name) @@ -557,7 +573,6 @@ public class CharonVpnService extends VpnService implements Runnable { try { - mBuilder.addAddress(address, prefixLength); mCache.addAddress(address, prefixLength); } catch (IllegalArgumentException ex) @@ -572,6 +587,7 @@ public class CharonVpnService extends VpnService implements Runnable try { mBuilder.addDnsServer(address); + mCache.recordAddressFamily(address); } catch (IllegalArgumentException ex) { @@ -584,7 +600,6 @@ public class CharonVpnService extends VpnService implements Runnable { try { - mBuilder.addRoute(address, prefixLength); mCache.addRoute(address, prefixLength); } catch (IllegalArgumentException ex) @@ -611,7 +626,6 @@ public class CharonVpnService extends VpnService implements Runnable { try { - mBuilder.setMtu(mtu); mCache.setMtu(mtu); } catch (IllegalArgumentException ex) @@ -626,6 +640,7 @@ public class CharonVpnService extends VpnService implements Runnable ParcelFileDescriptor fd; try { + mCache.applyData(mBuilder); fd = mBuilder.establish(); } catch (Exception ex) @@ -641,7 +656,7 @@ public class CharonVpnService extends VpnService implements Runnable * builder anymore, but we might need another when reestablishing */ mBuilder = createBuilder(mName); mEstablishedCache = mCache; - mCache = new BuilderCache(); + mCache = new BuilderCache(mSplitTunneling); return fd.detachFd(); } @@ -679,17 +694,40 @@ public class CharonVpnService extends VpnService implements Runnable public class BuilderCache { private final List<PrefixedAddress> mAddresses = new ArrayList<PrefixedAddress>(); - private final List<PrefixedAddress> mRoutes = new ArrayList<PrefixedAddress>(); + private final List<PrefixedAddress> mRoutesIPv4 = new ArrayList<PrefixedAddress>(); + private final List<PrefixedAddress> mRoutesIPv6 = new ArrayList<PrefixedAddress>(); + private final int mSplitTunneling; private int mMtu; + private boolean mIPv4Seen, mIPv6Seen; + + public BuilderCache(Integer splitTunneling) + { + mSplitTunneling = splitTunneling != null ? splitTunneling : 0; + } public void addAddress(String address, int prefixLength) { mAddresses.add(new PrefixedAddress(address, prefixLength)); + recordAddressFamily(address); } public void addRoute(String address, int prefixLength) { - mRoutes.add(new PrefixedAddress(address, prefixLength)); + try + { + if (isIPv6(address)) + { + mRoutesIPv6.add(new PrefixedAddress(address, prefixLength)); + } + else + { + mRoutesIPv4.add(new PrefixedAddress(address, prefixLength)); + } + } + catch (UnknownHostException ex) + { + ex.printStackTrace(); + } } public void setMtu(int mtu) @@ -697,19 +735,89 @@ public class CharonVpnService extends VpnService implements Runnable mMtu = mtu; } + public void recordAddressFamily(String address) + { + try + { + if (isIPv6(address)) + { + mIPv6Seen = true; + } + else + { + mIPv4Seen = true; + } + } + catch (UnknownHostException ex) + { + ex.printStackTrace(); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void applyData(VpnService.Builder builder) { for (PrefixedAddress address : mAddresses) { builder.addAddress(address.mAddress, address.mPrefix); } - for (PrefixedAddress route : mRoutes) + /* add routes depending on whether split tunneling is allowed or not, + * that is, whether we have to handle and block non-VPN traffic */ + if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0) + { + if (mIPv4Seen) + { /* split tunneling is used depending on the routes */ + for (PrefixedAddress route : mRoutesIPv4) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { /* allow traffic that would otherwise be blocked to bypass the VPN */ + builder.allowFamily(OsConstants.AF_INET); + } + } + else if (mIPv4Seen) + { /* only needed if we've seen any addresses. otherwise, traffic + * is blocked by default (we also install no routes in that case) */ + builder.addRoute("0.0.0.0", 0); + } + /* same thing for IPv6 */ + if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0) + { + if (mIPv6Seen) + { + for (PrefixedAddress route : mRoutesIPv6) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + builder.allowFamily(OsConstants.AF_INET6); + } + } + else if (mIPv6Seen) { - builder.addRoute(route.mAddress, route.mPrefix); + builder.addRoute("::", 0); } builder.setMtu(mMtu); } + private boolean isIPv6(String address) throws UnknownHostException + { + InetAddress addr = InetAddress.getByName(address); + if (addr instanceof Inet4Address) + { + return false; + } + else if (addr instanceof Inet6Address) + { + return true; + } + return false; + } + private class PrefixedAddress { public String mAddress; @@ -725,22 +833,25 @@ public class CharonVpnService extends VpnService implements Runnable /* * The libraries are extracted to /data/data/org.strongswan.android/... - * during installation. + * during installation. On newer releases most are loaded in JNI_OnLoad. */ static { - System.loadLibrary("strongswan"); - - if (MainActivity.USE_BYOD) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { - System.loadLibrary("tncif"); - System.loadLibrary("tnccs"); - System.loadLibrary("imcv"); - } + System.loadLibrary("strongswan"); - System.loadLibrary("hydra"); - System.loadLibrary("charon"); - System.loadLibrary("ipsec"); + if (MainActivity.USE_BYOD) + { + System.loadLibrary("tncif"); + System.loadLibrary("tnccs"); + System.loadLibrary("imcv"); + } + + System.loadLibrary("hydra"); + System.loadLibrary("charon"); + System.loadLibrary("ipsec"); + } System.loadLibrary("androidbridge"); } } diff --git a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java index 8ea07f4c0..ebe1d0080 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java +++ b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -42,12 +42,21 @@ public class NetworkManager extends BroadcastReceiver mContext.unregisterReceiver(this); } + public boolean isConnected() + { + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = null; + if (cm != null) + { + info = cm.getActiveNetworkInfo(); + } + return info != null && info.isConnected(); + } + @Override public void onReceive(Context context, Intent intent) { - ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - networkChanged(info == null || !info.isConnected()); + networkChanged(!isConnected()); } /** diff --git a/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java index 39f86b460..5b1799744 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java @@ -24,6 +24,7 @@ import org.strongswan.android.logic.imc.ImcState; import org.strongswan.android.logic.imc.RemediationInstruction; import android.app.Fragment; +import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.Service; import android.content.ComponentName; @@ -82,7 +83,7 @@ public class ImcStateFragment extends Fragment implements VpnStateListener public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.imc_state_fragment, null); + View view = inflater.inflate(R.layout.imc_state_fragment, container, false); mButton = (LinearLayout)view.findViewById(R.id.imc_state_button); mButton.setOnClickListener(new OnClickListener() { @@ -174,7 +175,12 @@ public class ImcStateFragment extends Fragment implements VpnStateListener public void updateView() { - FragmentTransaction ft = getFragmentManager().beginTransaction(); + FragmentManager fm = getFragmentManager(); + if (fm == null) + { + return; + } + FragmentTransaction ft = fm.beginTransaction(); switch (mService.getImcState()) { diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 41cd6e98c..a8b3daa06 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -60,6 +60,8 @@ import android.widget.TextView; public class VpnProfileDetailActivity extends Activity { private static final int SELECT_TRUSTED_CERTIFICATE = 0; + private static final int MTU_MIN = 1280; + private static final int MTU_MAX = 1500; private VpnProfileDataSource mDataSource; private Long mId; @@ -79,6 +81,12 @@ public class VpnProfileDetailActivity extends Activity private CheckBox mCheckAuto; private RelativeLayout mSelectCert; private RelativeLayout mTncNotice; + private CheckBox mShowAdvanced; + private ViewGroup mAdvancedSettings; + private EditText mMTU; + private EditText mPort; + private CheckBox mBlockIPv4; + private CheckBox mBlockIPv6; @Override public void onCreate(Bundle savedInstanceState) @@ -108,6 +116,14 @@ public class VpnProfileDetailActivity extends Activity mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); + mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); + mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); + + mMTU = (EditText)findViewById(R.id.mtu); + mPort = (EditText)findViewById(R.id.port); + mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); + mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); + mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) @@ -154,6 +170,14 @@ public class VpnProfileDetailActivity extends Activity } }); + mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + updateAdvancedSettings(); + } + }); + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); if (mId == null) { @@ -165,6 +189,7 @@ public class VpnProfileDetailActivity extends Activity updateCredentialView(); updateCertificateSelector(); + updateAdvancedSettings(); } @Override @@ -315,6 +340,22 @@ public class VpnProfileDetailActivity extends Activity } /** + * Update the advanced settings UI depending on whether any advanced + * settings have already been made. + */ + private void updateAdvancedSettings() + { + boolean show = mShowAdvanced.isChecked(); + if (!show && mProfile != null) + { + Integer st = mProfile.getSplitTunneling(); + show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0); + } + mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE); + mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE); + } + + /** * Save or update the profile depending on whether we actually have a * profile object or not (this was created in updateProfileData) */ @@ -368,6 +409,18 @@ public class VpnProfileDetailActivity extends Activity showCertificateAlert(); valid = false; } + Integer mtu = getInteger(mMTU); + if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX)) + { + mMTU.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX)); + valid = false; + } + Integer port = getInteger(mPort); + if (port != null && (port < 1 || port > 65535)) + { + mPort.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535)); + valid = false; + } return valid; } @@ -395,6 +448,12 @@ public class VpnProfileDetailActivity extends Activity } String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); mProfile.setCertificateAlias(certAlias); + mProfile.setMTU(getInteger(mMTU)); + mProfile.setPort(getInteger(mPort)); + int st = 0; + st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0; + st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0; + mProfile.setSplitTunneling(st == 0 ? null : st); } /** @@ -417,6 +476,10 @@ public class VpnProfileDetailActivity extends Activity mVpnType = mProfile.getVpnType(); mUsername.setText(mProfile.getUsername()); mPassword.setText(mProfile.getPassword()); + mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null); + mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null); + mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false); + mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false); useralias = mProfile.getUserCertificateAlias(); alias = mProfile.getCertificateAlias(); getActionBar().setTitle(mProfile.getName()); @@ -458,6 +521,17 @@ public class VpnProfileDetailActivity extends Activity } } + /** + * Get the integer value in the given text box or null if empty + * + * @param view text box (numeric entry assumed) + */ + private Integer getInteger(EditText view) + { + String value = view.getText().toString().trim(); + return value.isEmpty() ? null : Integer.valueOf(value); + } + private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback { @Override diff --git a/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java b/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java new file mode 100644 index 000000000..01c0ab8a8 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 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.utils; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.regex.Pattern; + + +/** + * Simple generator for data/files that may be parsed by libstrongswan's + * settings_t class. + */ +public class SettingsWriter +{ + /** + * Top-level section + */ + private final SettingsSection mTop = new SettingsSection(); + + /** + * Set a string value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, String value) + { + Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+"); + if (key == null || !pattern.matcher(key).matches()) + { + return this; + } + String[] keys = key.split("\\."); + SettingsSection section = mTop; + section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1)); + section.Settings.put(keys[keys.length-1], value); + return this; + } + + /** + * Set an integer value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, Integer value) + { + return setValue(key, value == null ? null : value.toString()); + } + + /** + * Set a boolean value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, Boolean value) + { + return setValue(key, value == null ? null : value ? "1" : "0"); + } + + /** + * Serializes the settings to a string in the format understood by + * libstrongswan's settings_t parser. + * @return serialized settings + */ + public String serialize() + { + StringBuilder builder = new StringBuilder(); + serializeSection(mTop, builder); + return builder.toString(); + } + + /** + * Serialize the settings in a section and recursively serialize sub-sections + * @param section + * @param builder + */ + private void serializeSection(SettingsSection section, StringBuilder builder) + { + for (Entry<String, String> setting : section.Settings.entrySet()) + { + builder.append(setting.getKey()).append('='); + if (setting.getValue() != null) + { + builder.append("\"").append(escapeValue(setting.getValue())).append("\""); + } + builder.append('\n'); + } + + for (Entry<String, SettingsSection> subsection : section.Sections.entrySet()) + { + builder.append(subsection.getKey()).append(" {\n"); + serializeSection(subsection.getValue(), builder); + builder.append("}\n"); + } + } + + /** + * Escape value so it may be wrapped in " + * @param value + * @return + */ + private String escapeValue(String value) + { + return value.replace("\"", "\\\""); + } + + /** + * Find or create the nested sections with the given names + * @param sections list of section names + * @return final section + */ + private SettingsSection findOrCreateSection(String[] sections) + { + SettingsSection section = mTop; + for (String name : sections) + { + SettingsSection subsection = section.Sections.get(name); + if (subsection == null) + { + subsection = new SettingsSection(); + section.Sections.put(name, subsection); + } + section = subsection; + } + return section; + } + + /** + * A section containing sub-sections and settings. + */ + private class SettingsSection + { + /** + * Assigned key/value pairs + */ + LinkedHashMap<String,String> Settings = new LinkedHashMap<String, String>(); + + /** + * Assigned sub-sections + */ + LinkedHashMap<String,SettingsSection> Sections = new LinkedHashMap<String, SettingsWriter.SettingsSection>(); + } +} |