service: Support automatically added nameservers
[framework/connectivity/connman.git] / src / service.c
index 23916f7..bb01e6f 100644 (file)
@@ -56,8 +56,8 @@ struct connman_stats_counter {
 };
 
 struct connman_service {
-       gint refcount;
-       gint session_usage_count;
+       int refcount;
+       int session_usage_count;
        char *identifier;
        char *path;
        enum connman_service_type type;
@@ -87,6 +87,7 @@ struct connman_service {
        struct connman_provider *provider;
        char **nameservers;
        char **nameservers_config;
+       char **nameservers_auto;
        char **domains;
        char *domainname;
        char **timeservers;
@@ -391,6 +392,7 @@ static int service_load(struct connman_service *service)
                                service->state_ipv4 = service->state_ipv6 =
                                        CONNMAN_SERVICE_STATE_FAILURE;
                        service->error = string2error(str);
+                       g_free(str);
                }
                break;
        }
@@ -469,6 +471,7 @@ static int service_save(struct connman_service *service)
 {
        GKeyFile *keyfile;
        gchar *str;
+       guint freq;
        const char *cst_str = NULL;
        int err = 0;
 
@@ -518,6 +521,10 @@ static int service_save(struct connman_service *service)
 
                                g_string_free(str, TRUE);
                        }
+
+                       freq = connman_network_get_frequency(service->network);
+                       g_key_file_set_integer(keyfile, service->identifier,
+                                               "Frequency", freq);
                }
                /* fall through */
 
@@ -830,8 +837,6 @@ static void update_nameservers(struct connman_service *service)
                break;
        }
 
-       connman_resolver_remove_all(ifname);
-
        if (service->nameservers_config != NULL) {
                int i;
 
@@ -860,63 +865,95 @@ static void update_nameservers(struct connman_service *service)
        connman_resolver_flush();
 }
 
+/*
+ * The is_auto variable is set to true when IPv6 autoconf nameservers are
+ * inserted to resolver via netlink message (see rtnl.c:rtnl_newnduseropt()
+ * for details) and not through service.c
+ */
 int __connman_service_nameserver_append(struct connman_service *service,
-                                               const char *nameserver)
+                               const char *nameserver, gboolean is_auto)
 {
-       int len;
+       char **nameservers;
+       int len, i;
 
-       DBG("service %p nameserver %s", service, nameserver);
+       DBG("service %p nameserver %s auto %d", service, nameserver, is_auto);
 
        if (nameserver == NULL)
                return -EINVAL;
 
-       if (service->nameservers != NULL) {
-               int i;
+       if (is_auto == TRUE)
+               nameservers = service->nameservers_auto;
+       else
+               nameservers = service->nameservers;
 
-               for (i = 0; service->nameservers[i] != NULL; i++)
-                       if (g_strcmp0(service->nameservers[i], nameserver) == 0)
-                               return -EEXIST;
+       for (i = 0; nameservers != NULL && nameservers[i] != NULL; i++)
+               if (g_strcmp0(nameservers[i], nameserver) == 0)
+                       return -EEXIST;
 
-               len = g_strv_length(service->nameservers);
-               service->nameservers = g_try_renew(char *, service->nameservers,
-                                                       len + 2);
+       if (nameservers != NULL) {
+               len = g_strv_length(nameservers);
+               nameservers = g_try_renew(char *, nameservers, len + 2);
        } else {
                len = 0;
-               service->nameservers = g_try_new0(char *, len + 2);
+               nameservers = g_try_new0(char *, len + 2);
        }
 
-       if (service->nameservers == NULL)
+       if (nameservers == NULL)
                return -ENOMEM;
 
-       service->nameservers[len] = g_strdup(nameserver);
-       service->nameservers[len + 1] = NULL;
+       nameservers[len] = g_strdup(nameserver);
+       if (nameservers[len] == NULL)
+               return -ENOMEM;
 
-       update_nameservers(service);
+       nameservers[len + 1] = NULL;
+
+       if (is_auto == TRUE) {
+               service->nameservers_auto = nameservers;
+       } else {
+               service->nameservers = nameservers;
+               update_nameservers(service);
+       }
 
        return 0;
 }
 
 int __connman_service_nameserver_remove(struct connman_service *service,
-                                               const char *nameserver)
+                               const char *nameserver, gboolean is_auto)
 {
-       char **servers;
+       char **servers, **nameservers;
+       gboolean found = FALSE;
        int len, i, j;
 
-       DBG("service %p nameserver %s", service, nameserver);
+       DBG("service %p nameserver %s auto %d", service, nameserver, is_auto);
 
        if (nameserver == NULL)
                return -EINVAL;
 
-       if (service->nameservers == NULL)
+       if (is_auto == TRUE)
+               nameservers = service->nameservers_auto;
+       else
+               nameservers = service->nameservers;
+
+       if (nameservers == NULL)
                return 0;
 
-       len = g_strv_length(service->nameservers);
-       if (len == 1) {
-               if (g_strcmp0(service->nameservers[0], nameserver) != 0)
-                       return 0;
+       for (i = 0; nameservers != NULL && nameservers[i] != NULL; i++)
+               if (g_strcmp0(nameservers[i], nameserver) == 0) {
+                       found = TRUE;
+                       break;
+               }
+
+       if (found == FALSE)
+               return 0;
 
-               g_strfreev(service->nameservers);
-               service->nameservers = NULL;
+       len = g_strv_length(nameservers);
+
+       if (len == 1) {
+               g_strfreev(nameservers);
+               if (is_auto == TRUE)
+                       service->nameservers_auto = NULL;
+               else
+                       service->nameservers = NULL;
 
                return 0;
        }
@@ -926,17 +963,24 @@ int __connman_service_nameserver_remove(struct connman_service *service,
                return -ENOMEM;
 
        for (i = 0, j = 0; i < len; i++) {
-               if (g_strcmp0(service->nameservers[i], nameserver) != 0) {
-                       servers[j] = g_strdup(service->nameservers[i]);
+               if (g_strcmp0(nameservers[i], nameserver) != 0) {
+                       servers[j] = g_strdup(nameservers[i]);
+                       if (servers[j] == NULL)
+                               return -ENOMEM;
                        j++;
                }
        }
        servers[len - 1] = NULL;
 
-       g_strfreev(service->nameservers);
-       service->nameservers = servers;
+       g_strfreev(nameservers);
+       nameservers = servers;
 
-       update_nameservers(service);
+       if (is_auto == TRUE) {
+               service->nameservers_auto = nameservers;
+       } else {
+               service->nameservers = nameservers;
+               update_nameservers(service);
+       }
 
        return 0;
 }
@@ -1416,9 +1460,12 @@ static void append_dns(DBusMessageIter *iter, void *user_data)
        if (service->nameservers_config != NULL) {
                append_nameserver(iter, &service->nameservers_config);
                return;
-       } else if (service->nameservers != NULL) {
-               append_nameserver(iter, &service->nameservers);
-               return;
+       } else {
+               if (service->nameservers != NULL)
+                       append_nameserver(iter, &service->nameservers);
+
+               if (service->nameservers_auto != NULL)
+                       append_nameserver(iter, &service->nameservers_auto);
        }
 }
 
@@ -1980,17 +2027,17 @@ GSequence *__connman_service_get_list(struct connman_session *session,
 void __connman_service_session_inc(struct connman_service *service)
 {
        DBG("service %p ref count %d", service,
-               g_atomic_int_get(&service->session_usage_count) + 1);
+               service->session_usage_count + 1);
 
-       g_atomic_int_inc(&service->session_usage_count);
+       __sync_fetch_and_add(&service->session_usage_count, 1);
 }
 
 connman_bool_t __connman_service_session_dec(struct connman_service *service)
 {
        DBG("service %p ref count %d", service,
-               g_atomic_int_get(&service->session_usage_count) - 1);
+               service->session_usage_count - 1);
 
-       if (g_atomic_int_dec_and_test(&service->session_usage_count) == FALSE)
+       if (__sync_fetch_and_sub(&service->session_usage_count, 1) != 1)
                return FALSE;
 
        return TRUE;
@@ -2055,9 +2102,6 @@ static void append_properties(DBusMessageIter *dict, dbus_bool_t limited,
                connman_dbus_dict_append_basic(dict, "Roaming",
                                        DBUS_TYPE_BOOLEAN, &service->roaming);
 
-               required = FALSE;
-               connman_dbus_dict_append_basic(dict, "SetupRequired",
-                                               DBUS_TYPE_BOOLEAN, &required);
                connman_dbus_dict_append_dict(dict, "Ethernet",
                                                append_ethernet, service);
                break;
@@ -3208,11 +3252,9 @@ static DBusMessage *remove_service(DBusConnection *conn,
                                                CONNMAN_SERVICE_STATE_FAILURE)
                return __connman_error_not_supported(msg);
 
-       if (service->network != NULL) {
-               set_reconnect_state(service, FALSE);
+       set_reconnect_state(service, FALSE);
 
-               __connman_network_disconnect(service->network);
-       }
+       __connman_service_disconnect(service);
 
        g_free(service->passphrase);
        service->passphrase = NULL;
@@ -3421,6 +3463,7 @@ static void service_free(gpointer user_data)
 
        g_strfreev(service->nameservers);
        g_strfreev(service->nameservers_config);
+       g_strfreev(service->nameservers_auto);
        g_strfreev(service->domains);
        g_strfreev(service->proxies);
        g_strfreev(service->excludes);
@@ -3456,20 +3499,22 @@ static void service_free(gpointer user_data)
  */
 void __connman_service_put(struct connman_service *service)
 {
+       GSequenceIter *iter;
+
        DBG("service %p", service);
 
-       if (g_atomic_int_dec_and_test(&service->refcount) == TRUE) {
-               GSequenceIter *iter;
+       if (__sync_fetch_and_sub(&service->refcount, 1) != 1)
+               return;
 
-               iter = g_hash_table_lookup(service_hash, service->identifier);
-               if (iter != NULL) {
-                       reply_pending(service, ECONNABORTED);
+       iter = g_hash_table_lookup(service_hash, service->identifier);
+       if (iter != NULL) {
+               reply_pending(service, ECONNABORTED);
 
-                       __connman_service_disconnect(service);
+               __connman_service_disconnect(service);
 
-                       g_sequence_remove(iter);
-               } else
-                       service_free(service);
+               g_sequence_remove(iter);
+       } else {
+               service_free(service);
        }
 }
 
@@ -3574,7 +3619,7 @@ struct connman_service *connman_service_ref(struct connman_service *service)
 {
        DBG("%p", service);
 
-       g_atomic_int_inc(&service->refcount);
+       __sync_fetch_and_add(&service->refcount, 1);
 
        return service;
 }
@@ -3794,6 +3839,7 @@ int __connman_service_set_favorite(struct connman_service *service,
                return -EALREADY;
 
        service->favorite = favorite;
+       service->order = __connman_service_get_order(service);
 
        favorite_changed(service);
 
@@ -4124,6 +4170,9 @@ enum connman_service_state __connman_service_ipconfig_get_state(
                                        struct connman_service *service,
                                        enum connman_ipconfig_type type)
 {
+       if (service == NULL)
+               return CONNMAN_SERVICE_STATE_UNKNOWN;
+
        if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
                return service->state_ipv4;
 
@@ -4152,6 +4201,7 @@ static void check_proxy_setup(struct connman_service *service)
        if (__connman_wpad_start(service) < 0) {
                service->proxy = CONNMAN_SERVICE_PROXY_METHOD_DIRECT;
                __connman_notifier_proxy_changed(service);
+               goto done;
        }
 
        return;
@@ -4160,6 +4210,59 @@ done:
        __connman_wispr_start(service, CONNMAN_IPCONFIG_TYPE_IPV4);
 }
 
+/*
+ * How many networks are connected at the same time. If more than 1,
+ * then set the rp_filter setting properly (loose mode routing) so that network
+ * connectivity works ok. This is only done for IPv4 networks as IPv6
+ * does not have rp_filter knob.
+ */
+static int connected_networks_count;
+static int original_rp_filter;
+
+static void service_rp_filter(struct connman_service *service,
+                               gboolean connected)
+{
+       enum connman_ipconfig_method method;
+
+       method = __connman_ipconfig_get_method(service->ipconfig_ipv4);
+
+       switch (method) {
+       case CONNMAN_IPCONFIG_METHOD_UNKNOWN:
+       case CONNMAN_IPCONFIG_METHOD_OFF:
+       case CONNMAN_IPCONFIG_METHOD_AUTO:
+               return;
+       case CONNMAN_IPCONFIG_METHOD_FIXED:
+       case CONNMAN_IPCONFIG_METHOD_MANUAL:
+       case CONNMAN_IPCONFIG_METHOD_DHCP:
+               break;
+       }
+
+       if (connected == TRUE) {
+               if (connected_networks_count == 1) {
+                       int filter_value;
+                       filter_value = __connman_ipconfig_set_rp_filter();
+                       if (filter_value < 0)
+                               return;
+
+                       original_rp_filter = filter_value;
+               }
+               connected_networks_count++;
+
+       } else {
+               if (connected_networks_count == 2)
+                       __connman_ipconfig_unset_rp_filter(original_rp_filter);
+
+               connected_networks_count--;
+               if (connected_networks_count < 0)
+                       connected_networks_count = 0;
+       }
+
+       DBG("%s %s ipconfig %p method %d count %d filter %d",
+               connected ? "connected" : "disconnected", service->identifier,
+               service->ipconfig_ipv4, method,
+               connected_networks_count, original_rp_filter);
+}
+
 int __connman_service_ipconfig_indicate_state(struct connman_service *service,
                                        enum connman_service_state new_state,
                                        enum connman_ipconfig_type type)
@@ -4205,9 +4308,10 @@ int __connman_service_ipconfig_indicate_state(struct connman_service *service,
        case CONNMAN_SERVICE_STATE_READY:
                update_nameservers(service);
 
-               if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+               if (type == CONNMAN_IPCONFIG_TYPE_IPV4) {
                        check_proxy_setup(service);
-               else
+                       service_rp_filter(service, TRUE);
+               } else
                        __connman_wispr_start(service, type);
                break;
        case CONNMAN_SERVICE_STATE_ONLINE:
@@ -4215,6 +4319,10 @@ int __connman_service_ipconfig_indicate_state(struct connman_service *service,
        case CONNMAN_SERVICE_STATE_DISCONNECT:
                if (service->state == CONNMAN_SERVICE_STATE_IDLE)
                        return -EINVAL;
+
+               if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
+                       service_rp_filter(service, FALSE);
+
                break;
        case CONNMAN_SERVICE_STATE_FAILURE:
                break;
@@ -4542,6 +4650,7 @@ int __connman_service_disconnect(struct connman_service *service)
 int __connman_service_disconnect_all(void)
 {
        GSequenceIter *iter;
+       GSList *services = NULL, *list;
 
        DBG("");
 
@@ -4550,15 +4659,23 @@ int __connman_service_disconnect_all(void)
        while (g_sequence_iter_is_end(iter) == FALSE) {
                struct connman_service *service = g_sequence_get(iter);
 
+               services = g_slist_prepend(services, service);
+
+               iter = g_sequence_iter_next(iter);
+       }
+
+       for (list = services; list != NULL; list = list->next) {
+               struct connman_service *service = list->data;
+
                service->ignore = TRUE;
 
                set_reconnect_state(service, FALSE);
 
                __connman_service_disconnect(service);
-
-               iter = g_sequence_iter_next(iter);
        }
 
+       g_slist_free(list);
+
        return 0;
 
 }
@@ -4574,6 +4691,8 @@ int __connman_service_lookup(const char *pattern, const char **path)
 {
        GHashTableIter iter;
        gpointer key, value;
+       struct connman_device *device;
+       const char *ifname;
 
        g_hash_table_iter_init(&iter, service_hash);
 
@@ -4586,6 +4705,20 @@ int __connman_service_lookup(const char *pattern, const char **path)
                        *path = (const char *) service->path;
                        return 0;
                }
+
+               if (service->network == NULL)
+                       continue;
+
+               device = connman_network_get_device(service->network);
+               if (device == NULL)
+                       continue;
+
+               ifname = connman_device_get_string(device, "Interface");
+               if (ifname != NULL && g_strcmp0(ifname, pattern) == 0) {
+                       *path = (const char *) service->path;
+                       return 0;
+               }
+
        }
 
        return -ENXIO;
@@ -4669,6 +4802,7 @@ int __connman_service_create_and_connect(DBusMessage *msg)
        DBusMessageIter iter, array;
        const char *mode = "managed", *security = "none", *group_security;
        const char *type = NULL, *ssid = NULL, *passphrase = NULL;
+       connman_bool_t network_created = FALSE;
        unsigned int ssid_len = 0;
        const char *ident;
        char *name, *group;
@@ -4753,16 +4887,17 @@ int __connman_service_create_and_connect(DBusMessage *msg)
 
        service = lookup_by_identifier(name);
 
-       if (service != NULL)
-               goto done;
-
-       network = create_hidden_wifi(device, ssid, mode, security, group);
-       if (network != NULL)
-               connman_network_set_group(network, group);
+       if (service == NULL) {
+               network = create_hidden_wifi(device, ssid,
+                                               mode, security, group);
+               if (network != NULL) {
+                       connman_network_set_group(network, group);
+                       network_created = TRUE;
+               }
 
-       service = lookup_by_identifier(name);
+               service = lookup_by_identifier(name);
+       }
 
-done:
        g_free(name);
        g_free(group);
 
@@ -4771,7 +4906,7 @@ done:
                goto failed;
        }
 
-       service->network_created = TRUE;
+       service->network_created = network_created;
 
        if (is_connected(service) == TRUE) {
                err = -EISCONN;
@@ -5092,7 +5227,7 @@ void __connman_service_read_ip4config(struct connman_service *service)
        if (service->ipconfig_ipv4 == NULL)
                return;
 
-       keyfile = __connman_storage_load_global();
+       keyfile = connman_storage_load_service(service->identifier);
        if (keyfile == NULL)
                return;
 
@@ -5496,6 +5631,9 @@ void __connman_service_remove_from_network(struct connman_network *network)
        if (service == NULL)
                return;
 
+       __connman_connection_gateway_remove(service,
+                                       CONNMAN_IPCONFIG_TYPE_ALL);
+
        __connman_service_put(service);
 }