From: Tobias Brunner Date: Wed, 17 Jun 2015 15:21:21 +0000 (+0200) Subject: android: Add a custom kernel-net implementation to replace kernel-netlink X-Git-Tag: 5.3.3dr3~8^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1082372448d8055b9ab2cd296a011b2f71eee3fe;p=thirdparty%2Fstrongswan.git android: Add a custom kernel-net implementation to replace kernel-netlink When roaming from a mobile network to WiFi on Android 5.x the event received via ConnectivityManager is triggered before the mobile connection is fully torn down (i.e. before the interface is disabled and the routes disappear). So for strongSwan the current path still seems valid and since no roam event is triggered later the daemon never switches to WiFi and the connection is broken afterwards. A possible solution to this is enabling roam events in the kernel-netlink plugin. That would trigger an event when the device is finally disconnected from the mobile network. However, this could actually take a some time, during which traffic continues to be sent via mobile network instead of WiFi. That's because Android now uses multiple routing tables, routing rules and fwmarks to direct traffic to the appropriate interface/table, but in our plugin we don't have the information available that would allow us to make the switch to a different network/routing table earlier (and we actually prefer the current path if it is still valid). Additionally, the plugin produces quite a bit more events than ConnectivityManager (which was one of the reasons to use the latter in the first place). This custom kernel-net implementation is now specifically tailored for Android. Roam events are still triggered via ConnectivityManager but the source address is determined via connect()/getsockname() on a VPN excluded UDP socket, which does use the correct routing table as intended by Android. That way the daemon immediately sees a different source IP when connectivity changes even if the device is connected to multiple networks concurrently. --- diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk index 670e83de18..1fb233b488 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/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index f94da05157..2655f7361c 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -82,11 +82,6 @@ struct private_charonservice_t { */ network_manager_t *network_manager; - /** - * Handle network events - */ - android_net_t *net_handler; - /** * CharonVpnService reference */ @@ -431,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); @@ -491,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, @@ -527,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"), diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c index 653e2738ba..9cab74e13d 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,6 +11,10 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +#include +#include +#include +#include #include "android_net.h" @@ -29,7 +33,7 @@ struct private_android_net_t { /** * Public kernel interface */ - android_net_t public; + kernel_net_t public; /** * Reference to NetworkManager object @@ -37,14 +41,24 @@ 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; }; /** @@ -83,32 +97,151 @@ 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; + + 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 (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)); + } + 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_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->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->network_manager->add_connectivity_cb(this->network_manager, (void*)connectivity_cb, this); 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 ade83f32ad..761fa21bcf 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 - -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 /** - * 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_ @}*/