service: Update ordering regarding connected preferred service
[framework/connectivity/connman.git] / src / service.c
index 54d29a6..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;
@@ -169,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) {
@@ -408,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) {
@@ -570,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);
 
@@ -1414,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;
@@ -2034,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);
 
@@ -2172,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)
@@ -2764,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;
@@ -2912,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)
@@ -3222,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)
@@ -3229,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;
 
@@ -3317,24 +3506,16 @@ static DBusMessage *move_service(DBusConnection *conn,
        service_save(service);
        service_save(target);
 
-       src = g_hash_table_lookup(service_hash, service->identifier);
-       dst = g_hash_table_lookup(service_hash, target->identifier);
-
        /*
         * If the service which goes down is the default service and is
         * online, we downgrade directly its state to ready so:
         * 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();
 
@@ -3562,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);
@@ -4114,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)
@@ -4139,9 +4352,12 @@ static int service_indicate_state(struct connman_service *service)
        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;
@@ -4176,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);
@@ -4450,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;
 
@@ -5384,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);
 
@@ -5418,6 +5637,15 @@ 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:
@@ -5426,13 +5654,11 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne
        case CONNMAN_SERVICE_TYPE_GPS:
        case CONNMAN_SERVICE_TYPE_VPN:
        case CONNMAN_SERVICE_TYPE_GADGET:
-               service->autoconnect = FALSE;
+       case CONNMAN_SERVICE_TYPE_WIFI:
+       case CONNMAN_SERVICE_TYPE_CELLULAR:
                break;
        case CONNMAN_SERVICE_TYPE_ETHERNET:
                service->favorite = TRUE;
-       case CONNMAN_SERVICE_TYPE_WIFI:
-       case CONNMAN_SERVICE_TYPE_CELLULAR:
-               service->autoconnect = TRUE;
                break;
        }