service: Update ordering regarding connected preferred service
[framework/connectivity/connman.git] / src / service.c
index a21ee3f..5b5dac6 100644 (file)
@@ -30,6 +30,7 @@
 #include <gdbus.h>
 
 #include <connman/storage.h>
+#include <connman/setting.h>
 
 #include "connman.h"
 
@@ -89,6 +90,7 @@ struct connman_service {
        char **domains;
        char *domainname;
        char **timeservers;
+       char **timeservers_config;
        /* 802.1x settings from the config files */
        char *eap;
        char *identity;
@@ -110,6 +112,7 @@ struct connman_service {
        char *pac;
        connman_bool_t wps;
        int online_check_count;
+       connman_bool_t do_split_routing;
 };
 
 struct find_data {
@@ -168,6 +171,33 @@ const char *__connman_service_type2string(enum connman_service_type type)
        return NULL;
 }
 
+enum connman_service_type __connman_service_string2type(const char *str)
+{
+       if (str == NULL)
+               return CONNMAN_SERVICE_TYPE_UNKNOWN;
+
+       if (strcmp(str, "ethernet") == 0)
+               return CONNMAN_SERVICE_TYPE_ETHERNET;
+       if (strcmp(str, "gadget") == 0)
+               return CONNMAN_SERVICE_TYPE_GADGET;
+       if (strcmp(str, "wifi") == 0)
+               return CONNMAN_SERVICE_TYPE_WIFI;
+       if (strcmp(str, "cellular") == 0)
+               return CONNMAN_SERVICE_TYPE_CELLULAR;
+       if (strcmp(str, "bluetooth") == 0)
+               return CONNMAN_SERVICE_TYPE_BLUETOOTH;
+       if (strcmp(str, "wimax") == 0)
+               return CONNMAN_SERVICE_TYPE_WIMAX;
+       if (strcmp(str, "vpn") == 0)
+               return CONNMAN_SERVICE_TYPE_VPN;
+       if (strcmp(str, "gps") == 0)
+               return CONNMAN_SERVICE_TYPE_GPS;
+       if (strcmp(str, "system") == 0)
+               return CONNMAN_SERVICE_TYPE_SYSTEM;
+
+       return CONNMAN_SERVICE_TYPE_UNKNOWN;
+}
+
 static const char *security2string(enum connman_service_security security)
 {
        switch (security) {
@@ -295,11 +325,13 @@ static int service_load(struct connman_service *service)
        switch (service->type) {
        case CONNMAN_SERVICE_TYPE_UNKNOWN:
        case CONNMAN_SERVICE_TYPE_SYSTEM:
-       case CONNMAN_SERVICE_TYPE_ETHERNET:
        case CONNMAN_SERVICE_TYPE_GPS:
-       case CONNMAN_SERVICE_TYPE_VPN:
        case CONNMAN_SERVICE_TYPE_GADGET:
                break;
+       case CONNMAN_SERVICE_TYPE_VPN:
+               service->do_split_routing = g_key_file_get_boolean(keyfile,
+                               service->identifier, "SplitRouting", NULL);
+               break;
        case CONNMAN_SERVICE_TYPE_WIFI:
                if (service->name == NULL) {
                        gchar *name;
@@ -356,12 +388,6 @@ static int service_load(struct connman_service *service)
                service->favorite = g_key_file_get_boolean(keyfile,
                                service->identifier, "Favorite", NULL);
 
-               autoconnect = g_key_file_get_boolean(keyfile,
-                               service->identifier, "AutoConnect", &error);
-               if (error == NULL)
-                       service->autoconnect = autoconnect;
-               g_clear_error(&error);
-
                str = g_key_file_get_string(keyfile,
                                service->identifier, "Failure", NULL);
                if (str != NULL) {
@@ -371,6 +397,14 @@ static int service_load(struct connman_service *service)
                        service->error = string2error(str);
                        g_free(str);
                }
+               /* fall through */
+
+       case CONNMAN_SERVICE_TYPE_ETHERNET:
+               autoconnect = g_key_file_get_boolean(keyfile,
+                               service->identifier, "AutoConnect", &error);
+               if (error == NULL)
+                       service->autoconnect = autoconnect;
+               g_clear_error(&error);
                break;
        }
 
@@ -403,6 +437,13 @@ static int service_load(struct connman_service *service)
                service->nameservers_config = NULL;
        }
 
+       service->timeservers_config = g_key_file_get_string_list(keyfile,
+                       service->identifier, "Timeservers", &length, NULL);
+       if (service->timeservers_config != NULL && length == 0) {
+               g_strfreev(service->timeservers_config);
+               service->timeservers_config = NULL;
+       }
+
        service->domains = g_key_file_get_string_list(keyfile,
                        service->identifier, "Domains", &length, NULL);
        if (service->domains != NULL && length == 0) {
@@ -465,11 +506,13 @@ static int service_save(struct connman_service *service)
        switch (service->type) {
        case CONNMAN_SERVICE_TYPE_UNKNOWN:
        case CONNMAN_SERVICE_TYPE_SYSTEM:
-       case CONNMAN_SERVICE_TYPE_ETHERNET:
        case CONNMAN_SERVICE_TYPE_GPS:
-       case CONNMAN_SERVICE_TYPE_VPN:
        case CONNMAN_SERVICE_TYPE_GADGET:
                break;
+       case CONNMAN_SERVICE_TYPE_VPN:
+               g_key_file_set_boolean(keyfile, service->identifier,
+                               "SplitRouting", service->do_split_routing);
+               break;
        case CONNMAN_SERVICE_TYPE_WIFI:
                if (service->network) {
                        const unsigned char *ssid;
@@ -511,10 +554,6 @@ static int service_save(struct connman_service *service)
                g_key_file_set_boolean(keyfile, service->identifier,
                                        "Favorite", service->favorite);
 
-               if (service->favorite == TRUE)
-                       g_key_file_set_boolean(keyfile, service->identifier,
-                                       "AutoConnect", service->autoconnect);
-
                if (service->state_ipv4 == CONNMAN_SERVICE_STATE_FAILURE ||
                        service->state_ipv6 == CONNMAN_SERVICE_STATE_FAILURE) {
                        const char *failure = error2string(service->error);
@@ -526,6 +565,12 @@ static int service_save(struct connman_service *service)
                        g_key_file_remove_key(keyfile, service->identifier,
                                                        "Failure", NULL);
                }
+               /* fall through */
+
+       case CONNMAN_SERVICE_TYPE_ETHERNET:
+               if (service->favorite == TRUE)
+                       g_key_file_set_boolean(keyfile, service->identifier,
+                                       "AutoConnect", service->autoconnect);
                break;
        }
 
@@ -561,6 +606,16 @@ static int service_save(struct connman_service *service)
        g_key_file_remove_key(keyfile, service->identifier,
                                                        "Nameservers", NULL);
 
+       if (service->timeservers_config != NULL) {
+               guint len = g_strv_length(service->timeservers_config);
+
+               g_key_file_set_string_list(keyfile, service->identifier,
+                                                               "Timeservers",
+                               (const gchar **) service->timeservers_config, len);
+       } else
+               g_key_file_remove_key(keyfile, service->identifier,
+                                                       "Timeservers", NULL);
+
        if (service->domains != NULL) {
                guint len = g_strv_length(service->domains);
 
@@ -738,6 +793,25 @@ static connman_bool_t is_connected_state(const struct connman_service *service,
        return FALSE;
 }
 
+static connman_bool_t is_idle_state(const struct connman_service *service,
+                               enum connman_service_state state)
+{
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_READY:
+       case CONNMAN_SERVICE_STATE_ONLINE:
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+               break;
+       case CONNMAN_SERVICE_STATE_IDLE:
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
 static connman_bool_t is_connecting(struct connman_service *service)
 {
        return is_connecting_state(service, service->state);
@@ -933,6 +1007,28 @@ void __connman_service_nameserver_clear(struct connman_service *service)
        update_nameservers(service);
 }
 
+static void add_nameserver_route(int family, int index, char *nameserver,
+                               const char *gw)
+{
+       switch (family) {
+       case AF_INET:
+               if (connman_inet_compare_subnet(index, nameserver) == TRUE)
+                       break;
+
+               if (connman_inet_add_host_route(index, nameserver, gw) < 0)
+                       /* For P-t-P link the above route add will fail */
+                       connman_inet_add_host_route(index, nameserver, NULL);
+               break;
+
+       case AF_INET6:
+               if (connman_inet_add_ipv6_host_route(index, nameserver,
+                                                               gw) < 0)
+                       connman_inet_add_ipv6_host_route(index, nameserver,
+                                                       NULL);
+               break;
+       }
+}
+
 static void nameserver_add_routes(int index, char **nameservers,
                                        const char *gw)
 {
@@ -953,20 +1049,14 @@ static void nameserver_add_routes(int index, char **nameservers,
                else
                        family = addr->ai_family;
 
-               if (family == AF_INET) {
-                       if (connman_inet_compare_subnet(index,
-                                               nameservers[i]) != TRUE)
-                               connman_inet_add_host_route(index,
-                                                       nameservers[i], gw);
-               } else if (family == AF_INET6)
-                       connman_inet_add_ipv6_host_route(index,
-                                                       nameservers[i], gw);
+               add_nameserver_route(family, index, nameservers[i], gw);
 
                freeaddrinfo(addr);
        }
 }
 
-static void nameserver_del_routes(int index, char **nameservers)
+static void nameserver_del_routes(int index, char **nameservers,
+                               enum connman_ipconfig_type type)
 {
        int i, ret, family;
        struct addrinfo hints;
@@ -985,11 +1075,18 @@ static void nameserver_del_routes(int index, char **nameservers)
                else
                        family = addr->ai_family;
 
-               if (family == AF_INET)
-                       connman_inet_del_host_route(index, nameservers[i]);
-               else if (family == AF_INET6)
-                       connman_inet_del_ipv6_host_route(index,
+               switch (family) {
+               case AF_INET:
+                       if (type != CONNMAN_IPCONFIG_TYPE_IPV6)
+                               connman_inet_del_host_route(index,
                                                        nameservers[i]);
+                       break;
+               case AF_INET6:
+                       if (type != CONNMAN_IPCONFIG_TYPE_IPV4)
+                               connman_inet_del_ipv6_host_route(index,
+                                                       nameservers[i]);
+                       break;
+               }
 
                freeaddrinfo(addr);
        }
@@ -1026,7 +1123,8 @@ void __connman_service_nameserver_add_routes(struct connman_service *service,
        }
 }
 
-void __connman_service_nameserver_del_routes(struct connman_service *service)
+void __connman_service_nameserver_del_routes(struct connman_service *service,
+                                       enum connman_ipconfig_type type)
 {
        int index = -1;
 
@@ -1039,9 +1137,10 @@ void __connman_service_nameserver_del_routes(struct connman_service *service)
                index = connman_provider_get_index(service->provider);
 
        if (service->nameservers_config != NULL)
-               nameserver_del_routes(index, service->nameservers_config);
+               nameserver_del_routes(index, service->nameservers_config,
+                                       type);
        else if (service->nameservers != NULL)
-               nameserver_del_routes(index, service->nameservers);
+               nameserver_del_routes(index, service->nameservers, type);
 }
 
 static struct connman_stats *stats_get(struct connman_service *service)
@@ -1132,7 +1231,7 @@ static void reset_stats(struct connman_service *service)
        g_timer_reset(service->stats_roaming.timer);
 }
 
-static struct connman_service *get_default(void)
+struct connman_service *__connman_service_get_default(void)
 {
        struct connman_service *service;
        GSequenceIter *iter;
@@ -1152,7 +1251,7 @@ static struct connman_service *get_default(void)
 
 static void default_changed(void)
 {
-       struct connman_service *service = get_default();
+       struct connman_service *service = __connman_service_get_default();
 
        __connman_notifier_default_changed(service);
 }
@@ -1361,6 +1460,21 @@ static void append_dnsconfig(DBusMessageIter *iter, void *user_data)
        }
 }
 
+static void append_tsconfig(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_service *service = user_data;
+       int i;
+
+       if (service->timeservers_config == NULL)
+               return;
+
+       for (i = 0; service->timeservers_config[i]; i++) {
+               dbus_message_iter_append_basic(iter,
+                               DBUS_TYPE_STRING,
+                               &service->timeservers_config[i]);
+       }
+}
+
 static void append_domain(DBusMessageIter *iter, void *user_data)
 {
        struct connman_service *service = user_data;
@@ -1981,6 +2095,9 @@ static void append_properties(DBusMessageIter *dict, dbus_bool_t limited,
        connman_dbus_dict_append_array(dict, "Nameservers.Configuration",
                                DBUS_TYPE_STRING, append_dnsconfig, service);
 
+       connman_dbus_dict_append_array(dict, "Timeservers.Configuration",
+                               DBUS_TYPE_STRING, append_tsconfig, service);
+
        connman_dbus_dict_append_array(dict, "Domains",
                                DBUS_TYPE_STRING, append_domain, service);
 
@@ -1996,27 +2113,43 @@ static void append_properties(DBusMessageIter *dict, dbus_bool_t limited,
                                                append_provider, service);
 }
 
-static void append_struct(gpointer value, gpointer user_data)
+static void append_struct_service(DBusMessageIter *iter,
+               connman_dbus_append_cb_t function,
+               struct connman_service *service)
 {
-       struct connman_service *service = value;
-       DBusMessageIter *iter = user_data;
        DBusMessageIter entry, dict;
 
-       if (service->path == NULL)
-               return;
-
        dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &entry);
 
        dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
                                                        &service->path);
 
        connman_dbus_dict_open(&entry, &dict);
-       append_properties(&dict, TRUE, service);
+       if (function != NULL)
+               function(&dict, service);
        connman_dbus_dict_close(&entry, &dict);
 
        dbus_message_iter_close_container(iter, &entry);
 }
 
+static void append_dict_properties(DBusMessageIter *dict, void *user_data)
+{
+       struct connman_service *service = user_data;
+
+       append_properties(dict, TRUE, service);
+}
+
+static void append_struct(gpointer value, gpointer user_data)
+{
+       struct connman_service *service = value;
+       DBusMessageIter *iter = user_data;
+
+       if (service->path == NULL)
+               return;
+
+       append_struct_service(iter, append_dict_properties, service);
+}
+
 void __connman_service_list_struct(DBusMessageIter *iter)
 {
        g_sequence_foreach(service_list, append_struct, iter);
@@ -2027,6 +2160,12 @@ connman_bool_t __connman_service_is_hidden(struct connman_service *service)
        return service->hidden;
 }
 
+connman_bool_t
+__connman_service_is_split_routing(struct connman_service *service)
+{
+       return service->do_split_routing;
+}
+
 int __connman_service_get_index(struct connman_service *service)
 {
        if (service == NULL)
@@ -2097,6 +2236,14 @@ char **connman_service_get_nameservers(struct connman_service *service)
        return NULL;
 }
 
+char **connman_service_get_timeservers_config(struct connman_service *service)
+{
+       if (service == NULL)
+               return NULL;
+
+       return service->timeservers_config;
+}
+
 char **connman_service_get_timeservers(struct connman_service *service)
 {
        if (service == NULL)
@@ -2401,11 +2548,11 @@ static int update_proxy_configuration(struct connman_service *service,
                dbus_message_iter_get_basic(&entry, &key);
                dbus_message_iter_next(&entry);
 
-               if (dbus_message_iter_get_arg_type(&entry) !=
-                                                       DBUS_TYPE_VARIANT)
+               if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
                        goto error;
 
                dbus_message_iter_recurse(&entry, &variant);
+
                type = dbus_message_iter_get_arg_type(&variant);
 
                if (g_str_equal(key, "Method") == TRUE) {
@@ -2607,8 +2754,15 @@ static DBusMessage *set_property(DBusConnection *conn,
        if (dbus_message_iter_init(msg, &iter) == FALSE)
                return __connman_error_invalid_arguments(msg);
 
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+               return __connman_error_invalid_arguments(msg);
+
        dbus_message_iter_get_basic(&iter, &name);
        dbus_message_iter_next(&iter);
+
+       if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+               return __connman_error_invalid_arguments(msg);
+
        dbus_message_iter_recurse(&iter, &value);
 
        type = dbus_message_iter_get_arg_type(&value);
@@ -2649,7 +2803,8 @@ static DBusMessage *set_property(DBusConnection *conn,
                gw = __connman_ipconfig_get_gateway_from_index(index);
 
                if (gw && strlen(gw))
-                       __connman_service_nameserver_del_routes(service);
+                       __connman_service_nameserver_del_routes(service,
+                                               CONNMAN_IPCONFIG_TYPE_ALL);
 
                dbus_message_iter_recurse(&value, &entry);
 
@@ -2681,6 +2836,42 @@ static DBusMessage *set_property(DBusConnection *conn,
                dns_configuration_changed(service);
 
                service_save(service);
+       } else if (g_str_equal(name, "Timeservers.Configuration") == TRUE) {
+               DBusMessageIter entry;
+               GSList *list = NULL;
+               int count = 0;
+
+               if (type != DBUS_TYPE_ARRAY)
+                       return __connman_error_invalid_arguments(msg);
+
+               dbus_message_iter_recurse(&value, &entry);
+
+               while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
+                       const char *val;
+                       dbus_message_iter_get_basic(&entry, &val);
+
+                       list = g_slist_prepend(list, strdup(val));
+                       count++;
+
+                       dbus_message_iter_next(&entry);
+               }
+
+               g_strfreev(service->timeservers_config);
+               service->timeservers_config = NULL;
+
+               if (list != NULL) {
+                       service->timeservers_config = g_new0(char *, count+1);
+
+                       while (list != NULL) {
+                               count--;
+                               service->timeservers_config[count] = list->data;
+                               list = g_slist_delete_link(list, list);
+                       };
+               }
+
+               service_save(service);
+
+               __connman_timeserver_sync(service);
        } else if (g_str_equal(name, "Domains.Configuration") == TRUE) {
                DBusMessageIter entry;
                GString *str;
@@ -2829,48 +3020,118 @@ static connman_bool_t is_ignore(struct connman_service *service)
        return FALSE;
 }
 
-void __connman_service_auto_connect(void)
+struct preferred_tech_data {
+       GSequence *preferred_list;
+       enum connman_service_type type;
+};
+
+static void preferred_tech_add_by_type(gpointer data, gpointer user_data)
 {
-       struct connman_service *service = NULL;
-       GSequenceIter *iter;
+       struct connman_service *service = data;
+       struct preferred_tech_data *tech_data = user_data;
 
-       DBG("");
+       if (service->type == tech_data->type) {
+               g_sequence_append(tech_data->preferred_list, service);
 
-       if (__connman_session_mode() == TRUE) {
-               DBG("Session mode enabled: auto connect disabled");
-               return;
+               DBG("type %d service %p %s", tech_data->type, service,
+                               service->name);
        }
+}
 
-       iter = g_sequence_get_begin_iter(service_list);
+static GSequence* preferred_tech_list_get(GSequence *list)
+{
+       unsigned int *tech_array;
+       struct preferred_tech_data tech_data;
+       int i;
+
+       tech_array = connman_setting_get_uint_list("PreferredTechnologies");
+       if (tech_array == NULL)
+               return NULL;
+
+       tech_data.preferred_list = g_sequence_new(NULL);
+
+       for (i = 0; tech_array[i] != 0; i += 1) {
+               tech_data.type = tech_array[i];
+               g_sequence_foreach(service_list, preferred_tech_add_by_type,
+                               &tech_data);
+       }
+
+       return tech_data.preferred_list;
+}
+
+static connman_bool_t auto_connect_service(GSequenceIter* iter,
+               connman_bool_t preferred)
+{
+       struct connman_service *service = NULL;
 
        while (g_sequence_iter_is_end(iter) == FALSE) {
                service = g_sequence_get(iter);
 
                if (service->pending != NULL)
-                       return;
+                       return TRUE;
 
                if (is_connecting(service) == TRUE)
-                       return;
+                       return TRUE;
 
-               if (service->favorite == FALSE)
-                       return;
+               if (service->favorite == FALSE) {
+                       if (preferred == TRUE)
+                               goto next_service;
+                       return FALSE;
+               }
 
-               if (is_connected(service) == TRUE)
-                       return;
+               if (is_connected(service) == TRUE) {
+                       if (preferred == TRUE && service->state !=
+                                       CONNMAN_SERVICE_STATE_ONLINE)
+                               goto next_service;
+                       return TRUE;
+               }
 
                if (is_ignore(service) == FALSE && service->state ==
-                                               CONNMAN_SERVICE_STATE_IDLE)
+                               CONNMAN_SERVICE_STATE_IDLE)
                        break;
 
+       next_service:
                service = NULL;
 
                iter = g_sequence_iter_next(iter);
        }
 
        if (service != NULL) {
+
+               DBG("service %p %s %s", service, service->name,
+                               (preferred == TRUE)? "preferred": "auto");
+
                service->userconnect = FALSE;
                __connman_service_connect(service);
+               return TRUE;
+       }
+       return FALSE;
+}
+
+void __connman_service_auto_connect(void)
+{
+       GSequenceIter *iter = NULL;
+       GSequence *preferred_tech;
+
+       DBG("");
+
+       if (__connman_session_mode() == TRUE) {
+               DBG("Session mode enabled: auto connect disabled");
+               return;
        }
+
+       preferred_tech = preferred_tech_list_get(service_list);
+       if (preferred_tech != NULL)
+               iter = g_sequence_get_begin_iter(preferred_tech);
+
+       if (iter == NULL || auto_connect_service(iter, TRUE) == FALSE)
+               iter = g_sequence_get_begin_iter(service_list);
+
+       if (iter != NULL)
+               auto_connect_service(iter, FALSE);
+
+       if (preferred_tech != NULL)
+               g_sequence_free(preferred_tech);
 }
 
 static void remove_timeout(struct connman_service *service)
@@ -3130,7 +3391,7 @@ static void apply_relevant_default_downgrade(struct connman_service *service)
 {
        struct connman_service *def_service;
 
-       def_service = get_default();
+       def_service = __connman_service_get_default();
        if (def_service == NULL)
                return;
 
@@ -3139,6 +3400,18 @@ static void apply_relevant_default_downgrade(struct connman_service *service)
                def_service->state = CONNMAN_SERVICE_STATE_READY;
 }
 
+static void switch_default_service(struct connman_service *default_service,
+               struct connman_service *downgrade_service)
+{
+       GSequenceIter *src, *dst;
+
+       apply_relevant_default_downgrade(default_service);
+       src = g_hash_table_lookup(service_hash, downgrade_service->identifier);
+       dst = g_hash_table_lookup(service_hash, default_service->identifier);
+       g_sequence_move(src, dst);
+       downgrade_state(downgrade_service);
+}
+
 static DBusMessage *move_service(DBusConnection *conn,
                                        DBusMessage *msg, void *user_data,
                                                                gboolean before)
@@ -3146,7 +3419,6 @@ static DBusMessage *move_service(DBusConnection *conn,
        struct connman_service *service = user_data;
        struct connman_service *target;
        const char *path;
-       GSequenceIter *src, *dst;
        enum connman_ipconfig_method target4, target6;
        enum connman_ipconfig_method service4, service6;
 
@@ -3159,18 +3431,36 @@ static DBusMessage *move_service(DBusConnection *conn,
                return __connman_error_not_supported(msg);
 
        target = find_service(path);
-       if (target == NULL || target->favorite == FALSE || target == service ||
-                               target->type == CONNMAN_SERVICE_TYPE_VPN)
+       if (target == NULL || target->favorite == FALSE || target == service)
                return __connman_error_invalid_service(msg);
 
+       if (target->type == CONNMAN_SERVICE_TYPE_VPN) {
+               /*
+                * We only allow VPN route splitting if there are
+                * routes defined for a given VPN.
+                */
+               if (__connman_provider_check_routes(target->provider)
+                                                               == FALSE) {
+                       connman_info("Cannot move service. "
+                               "No routes defined for provider %s",
+                               __connman_provider_get_ident(target->provider));
+                       return __connman_error_invalid_service(msg);
+               }
+
+               target->do_split_routing = TRUE;
+       } else
+               target->do_split_routing = FALSE;
+
+       service->do_split_routing = FALSE;
+
        target4 = __connman_ipconfig_get_method(target->ipconfig_ipv4);
        target6 = __connman_ipconfig_get_method(target->ipconfig_ipv6);
        service4 = __connman_ipconfig_get_method(service->ipconfig_ipv4);
        service6 = __connman_ipconfig_get_method(service->ipconfig_ipv6);
 
-       DBG("target %s method %d/%d state %d/%d", target->identifier,
-                               target4, target6,
-                               target->state_ipv4, target->state_ipv6);
+       DBG("target %s method %d/%d state %d/%d split %d", target->identifier,
+               target4, target6, target->state_ipv4, target->state_ipv6,
+               target->do_split_routing);
 
        DBG("service %s method %d/%d state %d/%d", service->identifier,
                                service4, service6,
@@ -3214,9 +3504,7 @@ static DBusMessage *move_service(DBusConnection *conn,
 
        g_get_current_time(&service->modified);
        service_save(service);
-
-       src = g_hash_table_lookup(service_hash, service->identifier);
-       dst = g_hash_table_lookup(service_hash, target->identifier);
+       service_save(target);
 
        /*
         * If the service which goes down is the default service and is
@@ -3224,15 +3512,10 @@ static DBusMessage *move_service(DBusConnection *conn,
         * the service which goes up, needs to recompute its state which
         * is triggered via downgrading it - if relevant - to state ready.
         */
-       if (before == TRUE) {
-               apply_relevant_default_downgrade(target);
-               g_sequence_move(src, dst);
-               downgrade_state(service);
-       } else {
-               apply_relevant_default_downgrade(service);
-               g_sequence_move(dst, src);
-               downgrade_state(target);
-       }
+       if (before == TRUE)
+               switch_default_service(target, service);
+       else
+               switch_default_service(service, target);
 
        __connman_connection_update_gateway();
 
@@ -3263,18 +3546,23 @@ static DBusMessage *reset_counters(DBusConnection *conn,
 
 static struct _services_notify {
        int id;
-       GSList *added;
-       GSList *removed;
+       GHashTable *add;
+       GHashTable *remove;
 } *services_notify;
 
+static void append_removed(gpointer key, gpointer value, gpointer user_data)
+{
+       char *objpath = key;
+       DBusMessageIter *iter = user_data;
+
+       DBG("removed %s", objpath);
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &objpath);
+}
+
 static void service_send_removed(void)
 {
        DBusMessage *signal;
        DBusMessageIter iter, array;
-       GSList *list, *next;
-
-       if (services_notify->removed == NULL)
-               return;
 
        signal = dbus_message_new_signal(CONNMAN_MANAGER_PATH,
                        CONNMAN_MANAGER_INTERFACE, "ServicesRemoved");
@@ -3285,66 +3573,66 @@ static void service_send_removed(void)
        dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
                        DBUS_TYPE_OBJECT_PATH_AS_STRING, &array);
 
-       list = services_notify->removed;
-       services_notify->removed = NULL;
+       g_hash_table_foreach(services_notify->remove, append_removed, &array);
 
-       while (list != NULL) {
-               char *path = list->data;
-               DBG("removing %s", path);
-               next = list->next;
-               dbus_message_iter_append_basic(&array,
-                               DBUS_TYPE_OBJECT_PATH, &list->data);
-               g_free(list->data);
-               g_slist_free_1(list);
-               list = next;
-       }
        dbus_message_iter_close_container(&iter, &array);
 
        dbus_connection_send(connection, signal, NULL);
        dbus_message_unref(signal);
+
+       g_hash_table_remove_all(services_notify->remove);
 }
 
-static void append_service_structs(DBusMessageIter *iter, void *user_data)
+static void service_send_added_foreach(gpointer data, gpointer user_data)
 {
-       GSList *list = user_data;
-       GSList *next;
+       struct connman_service *service = data;
+       DBusMessageIter *iter = user_data;
 
-       while (list != NULL) {
-               struct connman_service *srv = list->data;
-               DBG("adding %s", srv->path);
-               next = list->next;
-               append_struct(list->data, iter);
-               g_slist_free_1(list);
-               list = next;
+       if (service == NULL || service->path == NULL) {
+               DBG("service %p or path is NULL", service);
+               return;
        }
+
+       DBG("added %s", service->path);
+
+       if (g_hash_table_lookup(services_notify->add, service->path) != NULL) {
+               append_struct(service, iter);
+               g_hash_table_remove(services_notify->add, service->path);
+       } else {
+               append_struct_service(iter, NULL, service);
+       }
+}
+
+static void service_send_added_ordered(DBusMessageIter *iter, void *user_data)
+{
+       g_sequence_foreach(service_list, service_send_added_foreach, iter);
 }
 
 static void service_send_added(void)
 {
        DBusMessage *signal;
-       GSList *list;
-
-       if (services_notify->added == NULL)
-               return;
 
        signal = dbus_message_new_signal(CONNMAN_MANAGER_PATH,
                        CONNMAN_MANAGER_INTERFACE, "ServicesAdded");
        if (signal == NULL)
                return;
 
-       list = services_notify->added;
-       services_notify->added = NULL;
        __connman_dbus_append_objpath_dict_array(signal,
-                       append_service_structs, list);
+                       service_send_added_ordered, NULL);
 
        dbus_connection_send(connection, signal, NULL);
        dbus_message_unref(signal);
+
+       g_hash_table_remove_all(services_notify->add);
 }
 
 static gboolean service_send_signals(gpointer data)
 {
-       service_send_removed();
-       service_send_added();
+       if (g_hash_table_size(services_notify->remove) > 0)
+               service_send_removed();
+
+       if (g_hash_table_size(services_notify->add) > 0)
+               service_send_added();
 
        services_notify->id = 0;
        return FALSE;
@@ -3362,37 +3650,24 @@ static void service_schedule_added(struct connman_service *service)
 {
        DBG("service %p", service);
 
-       services_notify->added = g_slist_prepend(services_notify->added,
-                       service);
+       g_hash_table_remove(services_notify->remove, service->path);
+       g_hash_table_insert(services_notify->add, service->path, service);
 
        service_schedule_signals();
 }
 
 static void service_schedule_removed(struct connman_service *service)
 {
-       GSList *list;
-
-       DBG("service %p", service);
+       DBG("service %p %s", service, service->path);
 
        if (service == NULL || service->path == NULL) {
                DBG("service %p or path is NULL", service);
                return;
        }
 
-       for (list = services_notify->added; list != NULL; list = list->next) {
-               struct connman_service *srv = list->data;
-               if (service == srv) {
-                       DBG("delete service %p from added list", srv);
-                       break;
-               }
-       }
-
-       if (list != NULL)
-               services_notify->added =
-                       g_slist_delete_link(services_notify->added, list);
-
-       services_notify->removed = g_slist_prepend(services_notify->removed,
-                       g_strdup(service->path));
+       g_hash_table_remove(services_notify->add, service->path);
+       g_hash_table_insert(services_notify->remove, g_strdup(service->path),
+                       NULL);
 
        service_schedule_signals();
 }
@@ -3468,6 +3743,7 @@ static void service_free(gpointer user_data)
        }
 
        g_strfreev(service->timeservers);
+       g_strfreev(service->timeservers_config);
        g_strfreev(service->nameservers);
        g_strfreev(service->nameservers_config);
        g_strfreev(service->nameservers_auto);
@@ -3795,6 +4071,23 @@ __connman_service_get_ipconfig(struct connman_service *service, int family)
 
 }
 
+connman_bool_t __connman_service_is_connected_state(struct connman_service *service,
+                                       enum connman_ipconfig_type type)
+{
+       if (service == NULL)
+               return FALSE;
+
+       switch (type) {
+       case CONNMAN_IPCONFIG_TYPE_UNKNOWN:
+               break;
+       case CONNMAN_IPCONFIG_TYPE_IPV4:
+               return is_connected_state(service, service->state_ipv4);
+       case CONNMAN_IPCONFIG_TYPE_IPV6:
+               return is_connected_state(service, service->state_ipv6);
+       }
+
+       return FALSE;
+}
 enum connman_service_security __connman_service_get_security(struct connman_service *service)
 {
        if (service == NULL)
@@ -4003,10 +4296,41 @@ static void downgrade_connected_services(void)
        }
 }
 
+static int service_update_preferred_order(struct connman_service *default_service,
+               struct connman_service *new_service,
+               enum connman_service_state new_state)
+{
+       unsigned int *tech_array;
+       int i;
+
+       if (default_service == NULL || default_service == new_service ||
+                       default_service->state != new_state )
+               return 0;
+
+       tech_array = connman_setting_get_uint_list("PreferredTechnologies");
+       if (tech_array != NULL) {
+
+               for (i = 0; tech_array[i] != 0; i += 1) {
+                       if (default_service->type == tech_array[i])
+                               return -EALREADY;
+
+                       if (new_service->type == tech_array[i]) {
+                               switch_default_service(new_service,
+                                               default_service);
+                               return 0;
+                       }
+               }
+               return -EAGAIN;
+       }
+
+       return -EALREADY;
+}
+
 static int service_indicate_state(struct connman_service *service)
 {
        enum connman_service_state old_state, new_state;
        struct connman_service *def_service;
+       int result;
        GSequenceIter *iter;
 
        if (service == NULL)
@@ -4025,12 +4349,15 @@ static int service_indicate_state(struct connman_service *service)
        if (old_state == new_state)
                return -EALREADY;
 
-       def_service = get_default();
+       def_service = __connman_service_get_default();
 
        if (new_state == CONNMAN_SERVICE_STATE_ONLINE) {
-               if (def_service != NULL && def_service != service &&
-                       def_service->state == CONNMAN_SERVICE_STATE_ONLINE)
-                       return -EALREADY;
+               result = service_update_preferred_order(def_service,
+                               service, new_state);
+               if (result == -EALREADY)
+                       return result;
+               if (result == -EAGAIN)
+                       __connman_service_auto_connect();
        }
 
        service->state = new_state;
@@ -4052,9 +4379,6 @@ static int service_indicate_state(struct connman_service *service)
                }
        }
 
-       if (new_state == CONNMAN_SERVICE_STATE_ONLINE)
-               __connman_timeserver_sync(service);
-
        if (new_state == CONNMAN_SERVICE_STATE_IDLE) {
                connman_bool_t reconnect;
 
@@ -4068,6 +4392,8 @@ static int service_indicate_state(struct connman_service *service)
        if (new_state == CONNMAN_SERVICE_STATE_READY) {
                enum connman_ipconfig_method method;
 
+               service_update_preferred_order(def_service, service, new_state);
+
                set_reconnect_state(service, TRUE);
 
                __connman_service_set_favorite(service, TRUE);
@@ -4107,7 +4433,7 @@ static int service_indicate_state(struct connman_service *service)
                                                service->ipconfig_ipv6);
 
        } else if (new_state == CONNMAN_SERVICE_STATE_DISCONNECT) {
-               def_service = get_default();
+               def_service = __connman_service_get_default();
 
                if (__connman_notifier_count_connected() == 0 &&
                        def_service != NULL &&
@@ -4124,8 +4450,6 @@ static int service_indicate_state(struct connman_service *service)
                dns_changed(service);
                domain_changed(service);
 
-               __connman_timeserver_stop();
-
                __connman_notifier_disconnect(service->type);
 
                /*
@@ -4218,7 +4542,12 @@ int __connman_service_clear_error(struct connman_service *service)
 
 int __connman_service_indicate_default(struct connman_service *service)
 {
-       DBG("service %p", service);
+       struct connman_service *current = __connman_service_get_default();
+
+       DBG("service %p default %p", service, current);
+
+       if (current == service)
+               return 0;
 
        default_changed();
 
@@ -4339,12 +4668,12 @@ int __connman_service_online_check_failed(struct connman_service *service,
        DBG("service %p type %d count %d", service, type,
                                                service->online_check_count);
 
-       if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
-               /* currently we only retry IPv6 stuff */
-               return 0;
-
-       if (service->online_check_count != 1)
+       /* currently we only retry IPv6 stuff */
+       if (type == CONNMAN_IPCONFIG_TYPE_IPV4 ||
+                       service->online_check_count != 1) {
+               __connman_service_auto_connect();
                return 0;
+       }
 
        service->online_check_count = 0;
 
@@ -4896,6 +5225,12 @@ static void service_lower_down(struct connman_ipconfig *ipconfig)
 
        DBG("%s lower down", __connman_ipconfig_get_ifname(ipconfig));
 
+       if (is_idle_state(service, service->state_ipv4) == FALSE)
+               __connman_ipconfig_disable(service->ipconfig_ipv4);
+
+       if (is_idle_state(service, service->state_ipv6) == FALSE)
+               __connman_ipconfig_disable(service->ipconfig_ipv6);
+
        stats_stop(service);
        service_save(service);
 }
@@ -5130,16 +5465,29 @@ unsigned int __connman_service_get_order(struct connman_service *service)
        if (iter != NULL) {
                if (g_sequence_iter_get_position(iter) == 0)
                        service->order = 1;
-               else if (service->type == CONNMAN_SERVICE_TYPE_VPN)
+               else if (service->type == CONNMAN_SERVICE_TYPE_VPN &&
+                               service->do_split_routing == FALSE)
                        service->order = 10;
                else
                        service->order = 0;
        }
 
+       DBG("service %p name %s order %d split %d", service, service->name,
+               service->order, service->do_split_routing);
+
 done:
        return service->order;
 }
 
+void __connman_service_update_ordering(void)
+{
+       GSequenceIter *iter;
+
+       iter = g_sequence_get_begin_iter(service_list);
+       if (iter != NULL)
+               g_sequence_sort_changed(iter, service_compare, NULL);
+}
+
 static enum connman_service_type convert_network_type(struct connman_network *network)
 {
        enum connman_network_type type = connman_network_get_type(network);
@@ -5254,7 +5602,8 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne
        struct connman_device *device;
        const char *ident, *group;
        char *name;
-       int index;
+       unsigned int *auto_connect_types;
+       int i, index;
 
        DBG("network %p", network);
 
@@ -5288,20 +5637,28 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne
 
        service->type = convert_network_type(network);
 
+       auto_connect_types = connman_setting_get_uint_list("DefaultAutoConnectTechnologies");
+       service->autoconnect = FALSE;
+       for (i = 0; auto_connect_types[i] != 0; i += 1) {
+               if (service->type == auto_connect_types[i]) {
+                       service->autoconnect = TRUE;
+                       break;
+               }
+       }
+
        switch (service->type) {
        case CONNMAN_SERVICE_TYPE_UNKNOWN:
        case CONNMAN_SERVICE_TYPE_SYSTEM:
-       case CONNMAN_SERVICE_TYPE_ETHERNET:
        case CONNMAN_SERVICE_TYPE_WIMAX:
        case CONNMAN_SERVICE_TYPE_BLUETOOTH:
        case CONNMAN_SERVICE_TYPE_GPS:
        case CONNMAN_SERVICE_TYPE_VPN:
        case CONNMAN_SERVICE_TYPE_GADGET:
-               service->autoconnect = FALSE;
-               break;
        case CONNMAN_SERVICE_TYPE_WIFI:
        case CONNMAN_SERVICE_TYPE_CELLULAR:
-               service->autoconnect = TRUE;
+               break;
+       case CONNMAN_SERVICE_TYPE_ETHERNET:
+               service->favorite = TRUE;
                break;
        }
 
@@ -5487,6 +5844,9 @@ int __connman_service_init(void)
        service_list = g_sequence_new(service_free);
 
        services_notify = g_new0(struct _services_notify, 1);
+       services_notify->remove = g_hash_table_new_full(g_str_hash,
+                       g_str_equal, g_free, NULL);
+       services_notify->add = g_hash_table_new(g_str_hash, g_str_equal);
 
        return 0;
 }
@@ -5510,6 +5870,8 @@ void __connman_service_cleanup(void)
        if (services_notify->id != 0) {
                g_source_remove(services_notify->id);
                service_send_signals(NULL);
+               g_hash_table_destroy(services_notify->remove);
+               g_hash_table_destroy(services_notify->add);
        }
        g_free(services_notify);