android: Add a custom kernel-net implementation to replace kernel-netlink
authorTobias Brunner <tobias@strongswan.org>
Wed, 17 Jun 2015 15:21:21 +0000 (17:21 +0200)
committerTobias Brunner <tobias@strongswan.org>
Tue, 28 Jul 2015 11:27:33 +0000 (13:27 +0200)
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.

src/frontends/android/jni/Android.mk
src/frontends/android/jni/libandroidbridge/charonservice.c
src/frontends/android/jni/libandroidbridge/kernel/android_net.c
src/frontends/android/jni/libandroidbridge/kernel/android_net.h

index 670e83d..1fb233b 100644 (file)
@@ -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),)
index f94da05..2655f73 100644 (file)
@@ -83,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;
@@ -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"),
index 653e273..9cab74e 100644 (file)
@@ -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
  * 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"
 
@@ -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;
-};
+}
index ade83f3..761fa21 100644 (file)
@@ -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
 #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_ @}*/