]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
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>
Mon, 22 Jun 2015 15:39:56 +0000 (17:39 +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.

 #865.

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 670e83de187b7281e011ad34c03ef6d477912e44..1fb233b48896a21b401c578047abf5304de9ea28 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 f94da051571af276b6cfeeb9f67bb5073bc0e87a..2655f7361cb20b223c39aa37854bc90bbd77d778 100644 (file)
@@ -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"),
index 653e2738baee8b343a576e88443854dc4ff16bca..9cab74e13df0c1bfb4ff205424f7b63827fbf0a1 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 ade83f32ad8a98226359c3e0df2890fbf60aa1ee..761fa21bcf4c401bb84eec784667ba7913eaf8f0 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_ @}*/