[connman] Added Tizen Wi-Fi Mesh
[platform/upstream/connman.git] / src / session.c
index de25f78..26cbf87 100644 (file)
@@ -2,8 +2,8 @@
  *
  *  Connection Manager
  *
- *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
- *  Copyright (C) 2011  BWM CarIT GmbH. All rights reserved.
+ *  Copyright (C) 2007-2014  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2011-2014  BMW Car IT GmbH.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
 
 static DBusConnection *connection;
 static GHashTable *session_hash;
-static connman_bool_t sessionmode;
+static GHashTable *service_hash;
 static struct connman_session *ecall_session;
-static GSList *policy_list;
-
-enum connman_session_trigger {
-       CONNMAN_SESSION_TRIGGER_UNKNOWN         = 0,
-       CONNMAN_SESSION_TRIGGER_SETTING         = 1,
-       CONNMAN_SESSION_TRIGGER_CONNECT         = 2,
-       CONNMAN_SESSION_TRIGGER_DISCONNECT      = 3,
-       CONNMAN_SESSION_TRIGGER_SERVICE         = 4,
-       CONNMAN_SESSION_TRIGGER_ECALL           = 5,
-};
-
-enum connman_session_reason {
-       CONNMAN_SESSION_REASON_UNKNOWN          = 0,
-       CONNMAN_SESSION_REASON_CONNECT          = 1,
-       CONNMAN_SESSION_REASON_FREE_RIDE        = 2,
-};
-
-enum connman_session_state {
-       CONNMAN_SESSION_STATE_DISCONNECTED   = 0,
-       CONNMAN_SESSION_STATE_CONNECTED      = 1,
-       CONNMAN_SESSION_STATE_ONLINE         = 2,
-};
-
-struct service_entry {
-       /* track why this service was selected */
-       enum connman_session_reason reason;
-       enum connman_service_state state;
-       const char *name;
-       struct connman_service *service;
-       char *ifname;
-       const char *bearer;
-       GSList *pending_timeouts;
-};
+static uint32_t session_mark = 256;
 
 struct session_info {
        struct connman_session_config config;
        enum connman_session_state state;
-       struct service_entry *entry;
-       enum connman_session_reason reason;
 };
 
 struct connman_session {
@@ -83,51 +49,52 @@ struct connman_session {
        char *notify_path;
        guint notify_watch;
 
-       struct connman_session_policy *policy;
-
-       connman_bool_t append_all;
+       bool active;
+       bool append_all;
        struct session_info *info;
        struct session_info *info_last;
+       struct connman_service *service;
+       struct connman_service *service_last;
        struct connman_session_config *policy_config;
+       GSList *user_allowed_bearers;
+       char *user_allowed_interface;
+
+       bool ecall;
+
+       enum connman_session_id_type id_type;
+       struct firewall_context *fw;
+       uint32_t mark;
+       int index;
+       char *gateway;
+       bool policy_routing;
+       bool snat_enabled;
+};
 
-       connman_bool_t ecall;
+struct connman_service_info {
+       struct connman_service *service;
+       GSList *sessions;
+};
 
-       GSequence *service_list;
-       GHashTable *service_hash;
+struct fw_snat {
+       GSList *sessions;
+       int id;
+       int index;
+       struct firewall_context *fw;
 };
 
-static const char *trigger2string(enum connman_session_trigger trigger)
-{
-       switch (trigger) {
-       case CONNMAN_SESSION_TRIGGER_UNKNOWN:
-               break;
-       case CONNMAN_SESSION_TRIGGER_SETTING:
-               return "setting";
-       case CONNMAN_SESSION_TRIGGER_CONNECT:
-               return "connect";
-       case CONNMAN_SESSION_TRIGGER_DISCONNECT:
-               return "disconnect";
-       case CONNMAN_SESSION_TRIGGER_SERVICE:
-               return "service";
-       case CONNMAN_SESSION_TRIGGER_ECALL:
-               return "ecall";
-       }
+GSList *fw_snat_list;
 
-       return NULL;
-}
+static struct connman_session_policy *policy;
+static void session_activate(struct connman_session *session);
+static void session_deactivate(struct connman_session *session);
+static void update_session_state(struct connman_session *session);
 
-static const char *reason2string(enum connman_session_reason reason)
+static void cleanup_service(gpointer data)
 {
-       switch (reason) {
-       case CONNMAN_SESSION_REASON_UNKNOWN:
-               return "unknown";
-       case CONNMAN_SESSION_REASON_CONNECT:
-               return "connect";
-       case CONNMAN_SESSION_REASON_FREE_RIDE:
-               return "free-ride";
-       }
+       struct connman_service_info *info = data;
 
-       return NULL;
+       g_slist_free(info->sessions);
+       g_free(info);
 }
 
 static const char *state2string(enum connman_session_state state)
@@ -160,7 +127,23 @@ static const char *type2string(enum connman_session_type type)
        return NULL;
 }
 
-static enum connman_session_type string2type(const char *type)
+enum connman_session_roaming_policy connman_session_parse_roaming_policy(const char *policy)
+{
+       if (g_strcmp0(policy, "default") == 0)
+               return CONNMAN_SESSION_ROAMING_POLICY_DEFAULT;
+       else if (g_strcmp0(policy, "always") == 0)
+               return CONNMAN_SESSION_ROAMING_POLICY_ALWAYS;
+       else if (g_strcmp0(policy, "forbidden") == 0)
+               return CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN;
+       else if (g_strcmp0(policy, "national") == 0)
+               return CONNMAN_SESSION_ROAMING_POLICY_NATIONAL;
+       else if (g_strcmp0(policy, "international") == 0)
+               return CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL;
+       else
+               return CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN;
+}
+
+enum connman_session_type connman_session_parse_connection_type(const char *type)
 {
        if (g_strcmp0(type, "any") == 0)
                return CONNMAN_SESSION_TYPE_ANY;
@@ -172,25 +155,24 @@ static enum connman_session_type string2type(const char *type)
        return CONNMAN_SESSION_TYPE_UNKNOWN;
 }
 
-static enum connman_service_type bearer2service(const char *bearer)
+static int bearer2service(const char *bearer, enum connman_service_type *type)
 {
-       if (bearer == NULL)
-               return CONNMAN_SERVICE_TYPE_UNKNOWN;
-
        if (g_strcmp0(bearer, "ethernet") == 0)
-               return CONNMAN_SERVICE_TYPE_ETHERNET;
+               *type = CONNMAN_SERVICE_TYPE_ETHERNET;
        else if (g_strcmp0(bearer, "wifi") == 0)
-               return CONNMAN_SERVICE_TYPE_WIFI;
-       else if (g_strcmp0(bearer, "wimax") == 0)
-               return CONNMAN_SERVICE_TYPE_WIMAX;
+               *type = CONNMAN_SERVICE_TYPE_WIFI;
+       else if (g_strcmp0(bearer, "gadget") == 0)
+               *type = CONNMAN_SERVICE_TYPE_GADGET;
        else if (g_strcmp0(bearer, "bluetooth") == 0)
-               return CONNMAN_SERVICE_TYPE_BLUETOOTH;
+               *type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
        else if (g_strcmp0(bearer, "cellular") == 0)
-               return CONNMAN_SERVICE_TYPE_CELLULAR;
+               *type = CONNMAN_SERVICE_TYPE_CELLULAR;
        else if (g_strcmp0(bearer, "vpn") == 0)
-               return CONNMAN_SERVICE_TYPE_VPN;
+               *type = CONNMAN_SERVICE_TYPE_VPN;
        else
-               return CONNMAN_SERVICE_TYPE_UNKNOWN;
+               return -EINVAL;
+
+       return 0;
 }
 
 static char *service2bearer(enum connman_service_type type)
@@ -198,1189 +180,900 @@ static char *service2bearer(enum connman_service_type type)
        switch (type) {
        case CONNMAN_SERVICE_TYPE_ETHERNET:
                return "ethernet";
+       case CONNMAN_SERVICE_TYPE_GADGET:
+               return "gadget";
        case CONNMAN_SERVICE_TYPE_WIFI:
                return "wifi";
-       case CONNMAN_SERVICE_TYPE_WIMAX:
-               return "wimax";
        case CONNMAN_SERVICE_TYPE_BLUETOOTH:
                return "bluetooth";
        case CONNMAN_SERVICE_TYPE_CELLULAR:
                return "cellular";
        case CONNMAN_SERVICE_TYPE_VPN:
                return "vpn";
-       case CONNMAN_SERVICE_TYPE_UNKNOWN:
        case CONNMAN_SERVICE_TYPE_SYSTEM:
        case CONNMAN_SERVICE_TYPE_GPS:
-       case CONNMAN_SERVICE_TYPE_GADGET:
+       case CONNMAN_SERVICE_TYPE_P2P:
+       case CONNMAN_SERVICE_TYPE_UNKNOWN:
+#if defined TIZEN_EXT_WIFI_MESH
+       case CONNMAN_SERVICE_TYPE_MESH:
+#endif
                return "";
        }
 
        return "";
 }
 
-static int assign_policy_plugin(struct connman_session *session)
+static struct fw_snat *fw_snat_lookup(int index)
 {
+       struct fw_snat *fw_snat;
        GSList *list;
-       struct connman_session_policy *policy;
-
-       if (session->policy != NULL)
-               return -EALREADY;
 
-       for (list = policy_list; list != NULL; list = list->next) {
-               policy = list->data;
+       for (list = fw_snat_list; list; list = list->next) {
+               fw_snat = list->data;
 
-               session->policy = policy;
-               break;
+               if (fw_snat->index == index)
+                       return fw_snat;
        }
-
-       return 0;
+       return NULL;
 }
 
-static int create_policy_config(struct connman_session *session)
+static int fw_snat_create(struct connman_session *session,
+                               int index, const char *ifname, const char *addr)
 {
-       struct connman_session_config *config;
+       struct fw_snat *fw_snat;
+       int err;
 
-       config = (*session->policy->create)(session);
-       if (config == NULL)
-               return -ENOMEM;
+       fw_snat = g_new0(struct fw_snat, 1);
 
-       session->policy_config = config;
+       fw_snat->fw = __connman_firewall_create();
+       fw_snat->index = index;
+
+       fw_snat->id = __connman_firewall_enable_snat(fw_snat->fw,
+                                               index, ifname, addr);
+       if (fw_snat->id < 0) {
+               err = fw_snat->id;
+               goto err;
+       }
+
+       fw_snat_list = g_slist_prepend(fw_snat_list, fw_snat);
+       fw_snat->sessions = g_slist_prepend(fw_snat->sessions, session);
 
        return 0;
+err:
+       __connman_firewall_destroy(fw_snat->fw);
+       g_free(fw_snat);
+       return err;
 }
 
-static void destroy_policy_config(struct connman_session *session)
+static void fw_snat_ref(struct connman_session *session,
+                               struct fw_snat *fw_snat)
 {
-       (*session->policy->destroy)(session);
+       if (g_slist_find(fw_snat->sessions, session))
+               return;
+       fw_snat->sessions = g_slist_prepend(fw_snat->sessions, session);
 }
 
-static void probe_policy(struct connman_session_policy *policy)
+static void fw_snat_unref(struct connman_session *session,
+                               struct fw_snat *fw_snat)
 {
+       fw_snat->sessions = g_slist_remove(fw_snat->sessions, session);
+       if (fw_snat->sessions)
+               return;
 
-       GHashTableIter iter;
-       gpointer key, value;
-       struct connman_session *session;
-
-       DBG("policy %p name %s", policy, policy->name);
-
-       g_hash_table_iter_init(&iter, session_hash);
-
-       while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
-               session = value;
-
-               if (session->policy != NULL)
-                       continue;
+       fw_snat_list = g_slist_remove(fw_snat_list, fw_snat);
 
-               assign_policy_plugin(session);
-       }
+       __connman_firewall_disable_snat(fw_snat->fw);
+       __connman_firewall_destroy(fw_snat->fw);
+       g_free(fw_snat);
 }
 
-static void remove_policy(struct connman_session_policy *policy)
+static int init_firewall_session(struct connman_session *session)
 {
-       GHashTableIter iter;
-       gpointer key, value;
-       struct connman_session *session;
+       struct firewall_context *fw;
+       int err;
+       struct connman_ipconfig *ipconfig = NULL;
+       const char *addr = NULL;
 
-       DBG("policy %p name %s", policy, policy->name);
+       if (session->policy_config->id_type == CONNMAN_SESSION_ID_TYPE_UNKNOWN &&
+                       !session->info->config.source_ip_rule)
+               return 0;
 
-       g_hash_table_iter_init(&iter, session_hash);
+       DBG("");
 
-       while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
-               session = value;
+       if (session->info->config.source_ip_rule) {
+               ipconfig = __connman_service_get_ip4config(session->service);
+               if (session->policy_config->id_type == CONNMAN_SESSION_ID_TYPE_UNKNOWN && !ipconfig)
+                       return 0;
+       }
 
-               if (session->policy != policy)
-                       continue;
+       fw = __connman_firewall_create();
+       if (!fw)
+               return -ENOMEM;
+
+       if (session->info->config.source_ip_rule && ipconfig) {
+               addr = __connman_ipconfig_get_local(ipconfig);
+       }
 
-               session->policy = NULL;
-               assign_policy_plugin(session);
+       err =__connman_firewall_enable_marking(fw,
+                                       session->policy_config->id_type,
+                                       session->policy_config->id,
+                                       addr, session->mark);
+       if (err < 0) {
+               __connman_firewall_destroy(fw);
+               return err;
        }
+       session->id_type = session->policy_config->id_type;
+       session->fw = fw;
+
+       return 0;
 }
 
-static gint compare_priority(gconstpointer a, gconstpointer b)
+static void cleanup_firewall_session(struct connman_session *session)
 {
-       const struct connman_session_policy *policy1 = a;
-       const struct connman_session_policy *policy2 = b;
+       if (!session->fw)
+               return;
 
-       return policy2->priority - policy1->priority;
-}
+       __connman_firewall_disable_marking(session->fw);
+       __connman_firewall_disable_snat(session->fw);
+       __connman_firewall_destroy(session->fw);
 
+       session->fw = NULL;
+}
 
-int connman_session_policy_register(struct connman_session_policy *policy)
+static int init_routing_table(struct connman_session *session)
 {
-       DBG("name %s", policy->name);
+       int err;
 
-       policy_list = g_slist_insert_sorted(policy_list, policy,
-                                               compare_priority);
+       if (session->policy_config->id_type == CONNMAN_SESSION_ID_TYPE_UNKNOWN &&
+                       !session->info->config.source_ip_rule)
+               return 0;
 
-       probe_policy(policy);
+       if (!session->service)
+               return 0;
 
-       return 0;
-}
+       DBG("");
 
-void connman_session_policy_unregister(struct connman_session_policy *policy)
-{
-       DBG("name %s", policy->name);
+       err = __connman_inet_add_fwmark_rule(session->mark,
+                                               AF_INET, session->mark);
+       if (err < 0)
+               return err;
 
-       policy_list = g_slist_remove(policy_list, policy);
+       err = __connman_inet_add_fwmark_rule(session->mark,
+                                               AF_INET6, session->mark);
+       if (err < 0)
+               __connman_inet_del_fwmark_rule(session->mark,
+                                               AF_INET, session->mark);
+       session->policy_routing = true;
 
-       remove_policy(policy);
+       return err;
 }
 
-struct connman_session_config *connman_session_create_default_config(void)
+static void del_default_route(struct connman_session *session)
 {
-       struct connman_session_config *config;
-
-       config = g_try_new0(struct connman_session_config, 1);
-       if (config == NULL)
-               return NULL;
+       if (!session->gateway)
+               return;
 
-       config->priority = FALSE;
-       config->roaming_policy = CONNMAN_SESSION_ROAMING_POLICY_DEFAULT;
-       config->type = CONNMAN_SESSION_TYPE_ANY;
-       config->ecall = FALSE;
-       config->allowed_bearers = connman_session_allowed_bearers_any();
-       if (config->allowed_bearers == NULL) {
-               g_free(config);
-               return NULL;
-       }
+       DBG("index %d routing table %d default gateway %s",
+               session->index, session->mark, session->gateway);
 
-       return config;
+       __connman_inet_del_default_from_table(session->mark,
+                                       session->index, session->gateway);
+       g_free(session->gateway);
+       session->gateway = NULL;
+       session->index = -1;
 }
 
-static enum connman_session_type apply_policy_on_type(
-                       enum connman_session_type policy,
-                       enum connman_session_type type)
+static void add_default_route(struct connman_session *session)
 {
-       if (type == CONNMAN_SESSION_TYPE_UNKNOWN)
-               return CONNMAN_SESSION_TYPE_UNKNOWN;
+       struct connman_ipconfig *ipconfig;
+       int err;
+       struct in_addr addr = { INADDR_ANY };
 
-       if (policy == CONNMAN_SESSION_TYPE_ANY)
-               return type;
+       if (!session->service)
+               return;
 
-       if (policy == CONNMAN_SESSION_TYPE_LOCAL)
-               return CONNMAN_SESSION_TYPE_LOCAL;
+       ipconfig = __connman_service_get_ip4config(session->service);
+       session->index = __connman_ipconfig_get_index(ipconfig);
+       session->gateway = g_strdup(__connman_ipconfig_get_gateway(ipconfig));
 
-       return CONNMAN_SESSION_TYPE_INTERNET;
-}
+       if (!session->gateway)
+               session->gateway = g_strdup(inet_ntoa(addr));
 
-static void cleanup_bearer(gpointer data)
-{
-       struct connman_session_bearer *bearer = data;
+       DBG("index %d routing table %d default gateway %s",
+               session->index, session->mark, session->gateway);
 
-       g_free(bearer->name);
-       g_free(bearer);
+       err = __connman_inet_add_default_to_table(session->mark,
+                                       session->index, session->gateway);
+       if (err < 0)
+               DBG("session %p %s", session, strerror(-err));
 }
 
-void connman_session_free_bearers(GSList *bearers)
+static void del_nat_rules(struct connman_session *session)
 {
-       g_slist_free_full(bearers, cleanup_bearer);
-}
+       struct fw_snat *fw_snat;
 
-static int session_parse_allowed_bearers(DBusMessageIter *iter, GSList **list)
-{
-       struct connman_session_bearer *bearer;
-       DBusMessageIter array;
+       if (!session->snat_enabled)
+               return;
 
-       dbus_message_iter_recurse(iter, &array);
+       session->snat_enabled = false;
+       fw_snat = fw_snat_lookup(session->index);
 
-       *list = NULL;
+       if (!fw_snat)
+               return;
 
-       while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
-               char *bearer_name = NULL;
+       fw_snat_unref(session, fw_snat);
+}
 
-               dbus_message_iter_get_basic(&array, &bearer_name);
+static void add_nat_rules(struct connman_session *session)
+{
+       struct connman_ipconfig *ipconfig;
+       struct fw_snat *fw_snat;
+       const char *addr;
+       int index, err;
+       char *ifname;
 
-               bearer = g_try_new0(struct connman_session_bearer, 1);
-               if (bearer == NULL) {
-                       connman_session_free_bearers(*list);
-                       *list = NULL;
-                       return -ENOMEM;
-               }
+       if (!session->service)
+               return;
 
-               bearer->name = g_strdup(bearer_name);
-               bearer->service_type = bearer2service(bearer->name);
+       ipconfig = __connman_service_get_ip4config(session->service);
+       index = __connman_ipconfig_get_index(ipconfig);
+       ifname = connman_inet_ifname(index);
+       addr = __connman_ipconfig_get_local(ipconfig);
 
-               if (bearer->service_type == CONNMAN_SERVICE_TYPE_UNKNOWN &&
-                               g_strcmp0(bearer->name, "*") == 0) {
-                       bearer->match_all = TRUE;
-               } else {
-                       bearer->match_all = FALSE;
-               }
+       if (!addr)
+               return;
 
-               *list = g_slist_append(*list, bearer);
+       session->snat_enabled = true;
+       fw_snat = fw_snat_lookup(index);
+       if (fw_snat) {
+               fw_snat_ref(session, fw_snat);
+               return;
+       }
 
-               dbus_message_iter_next(&array);
+       err = fw_snat_create(session, index, ifname, addr);
+       if (err < 0) {
+               DBG("failed to add SNAT rule");
+               session->snat_enabled = false;
        }
 
-       return 0;
+       g_free(ifname);
 }
 
-static struct connman_session_bearer *clone_bearer(
-                                       struct connman_session_bearer *orig)
+uint32_t connman_session_firewall_get_fwmark(struct connman_session *session)
 {
-       struct connman_session_bearer *bearer;
+       return session->mark;
+}
 
-       bearer = g_try_new0(struct connman_session_bearer, 1);
-       if (bearer == NULL)
-               return NULL;
+static void cleanup_routing_table(struct connman_session *session)
+{
+       DBG("");
 
-       bearer->name = g_strdup(orig->name);
-       bearer->match_all = orig->match_all;
-       bearer->service_type = orig->service_type;
+       if (session->policy_routing) {
+               __connman_inet_del_fwmark_rule(session->mark,
+                                       AF_INET6, session->mark);
+
+               __connman_inet_del_fwmark_rule(session->mark,
+                                       AF_INET, session->mark);
+               session->policy_routing = false;
+       }
 
-       return bearer;
+       del_default_route(session);
 }
 
-static int filter_bearer(GSList *policy_bearers,
-                               struct connman_session_bearer *bearer,
-                               GSList **list)
+static void update_firewall(struct connman_session *session)
 {
-       struct connman_session_bearer *policy, *tmp;
-       GSList *it;
-
-       if (policy_bearers == NULL)
-               goto clone;
+       cleanup_firewall_session(session);
+       init_firewall_session(session);
+}
 
-       for (it = policy_bearers; it != NULL; it = it->next) {
-               policy = it->data;
+static void update_routing_table(struct connman_session *session)
+{
+       cleanup_routing_table(session);
+       init_routing_table(session);
+       add_default_route(session);
+}
 
-               if (policy->match_all == FALSE &&
-                               bearer->service_type != policy->service_type)
-                       continue;
+static void cleanup_nat_rules(struct connman_session *session)
+{
+       del_nat_rules(session);
+}
 
-               goto clone;
+static void destroy_policy_config(struct connman_session *session)
+{
+       if (!policy) {
+               g_free(session->policy_config);
+               return;
        }
 
-       *list = NULL;
+       policy->destroy(session);
+}
 
-       return 0;
+static void free_session(struct connman_session *session)
+{
+       if (!session)
+               return;
 
-clone:
-       tmp = clone_bearer(bearer);
-       if (tmp == NULL) {
-               connman_session_free_bearers(*list);
-               *list = NULL;
-               return -ENOMEM;
-       }
-       *list = g_slist_append(*list, tmp);
+       if (session->notify_watch > 0)
+               g_dbus_remove_watch(connection, session->notify_watch);
 
-       return 0;
+       destroy_policy_config(session);
+       g_slist_free(session->info->config.allowed_bearers);
+       g_free(session->info->config.allowed_interface);
+       g_free(session->owner);
+       g_free(session->session_path);
+       g_free(session->notify_path);
+       g_free(session->info);
+       g_free(session->info_last);
+       g_free(session->gateway);
+
+       g_free(session);
 }
 
-static connman_bool_t is_bearer_valid(struct connman_session_bearer *bearer)
+static void set_active_session(struct connman_session *session, bool enable)
 {
-       if (bearer->match_all == FALSE &&
-                       bearer->service_type == CONNMAN_SERVICE_TYPE_UNKNOWN)
-               return FALSE;
 
-       return TRUE;
+       if (policy && policy->session_changed)
+               policy->session_changed(session, enable,
+                                       session->info->config.allowed_bearers);
+
+       __connman_service_set_active_session(enable,
+                               session->info->config.allowed_bearers);
 }
 
-static int apply_policy_on_bearers(GSList *policy_bearers, GSList *bearers,
-                               GSList **list)
+static void cleanup_session(gpointer user_data)
 {
-       struct connman_session_bearer *bearer;
-       GSList *it;
-       int err;
+       struct connman_session *session = user_data;
 
-       *list = NULL;
+       DBG("remove %s", session->session_path);
 
-       for (it = bearers; it != NULL; it = it->next) {
-               bearer = it->data;
+       cleanup_nat_rules(session);
+       cleanup_routing_table(session);
+       cleanup_firewall_session(session);
 
-               if (is_bearer_valid(bearer) == FALSE)
-                       continue;
+       if (session->active)
+               set_active_session(session, false);
 
-               err = filter_bearer(policy_bearers, bearer, list);
-               if (err < 0)
-                       return err;
-       }
+       session_deactivate(session);
+       update_session_state(session);
 
-       return 0;
+       g_slist_free(session->user_allowed_bearers);
+       g_free(session->user_allowed_interface);
+
+       free_session(session);
 }
 
-GSList *connman_session_allowed_bearers_any(void)
-{
-       struct connman_session_bearer *bearer;
-       GSList *list = NULL;
+struct creation_data {
+       DBusMessage *pending;
+       struct connman_session *session;
 
-       bearer = g_try_new0(struct connman_session_bearer, 1);
-       if (bearer == NULL)
-               return NULL;
+       /* user config */
+       enum connman_session_type type;
+       GSList *allowed_bearers;
+       char *allowed_interface;
+       bool source_ip_rule;
+};
 
-       bearer->name = g_strdup("");
-       bearer->match_all = TRUE;
-       bearer->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
+static void cleanup_creation_data(struct creation_data *creation_data)
+{
+       if (!creation_data)
+               return;
 
-       list = g_slist_append(list, bearer);
+       if (creation_data->pending)
+               dbus_message_unref(creation_data->pending);
 
-       return list;
+       g_slist_free(creation_data->allowed_bearers);
+       g_free(creation_data->allowed_interface);
+       g_free(creation_data);
 }
 
-static void append_allowed_bearers(DBusMessageIter *iter, void *user_data)
+static int create_policy_config(struct connman_session *session,
+                               connman_session_config_func_t cb,
+                               struct creation_data *creation_data)
 {
-       struct session_info *info = user_data;
-       GSList *list;
+       struct connman_session_config *config;
 
-       for (list = info->config.allowed_bearers;
-                       list != NULL; list = list->next) {
-               struct connman_session_bearer *bearer = list->data;
+       if (!policy) {
+               config = connman_session_create_default_config();
+               if (!config) {
+                       free_session(session);
+                       cleanup_creation_data(creation_data);
+                       return -ENOMEM;
+               }
 
-               dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
-                                               &bearer->name);
+               return cb(session, config, creation_data, 0);
        }
+
+       return policy->create(session, cb, creation_data);
 }
 
-static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data)
+int connman_session_policy_register(struct connman_session_policy *plugin)
 {
-       struct connman_service *service = user_data;
-       struct connman_ipconfig *ipconfig_ipv4;
+       if (policy)
+               return -EINVAL;
 
-       if (service == NULL)
-               return;
+       if (!plugin->create || !plugin->destroy)
+               return -EINVAL;
 
-       if (__connman_service_is_connected_state(service,
-                               CONNMAN_IPCONFIG_TYPE_IPV4) == FALSE) {
-               return;
-       }
+       DBG("name %s", plugin->name);
 
-       ipconfig_ipv4 = __connman_service_get_ip4config(service);
-       if (ipconfig_ipv4 == NULL)
-               return;
+       policy = plugin;
 
-       __connman_ipconfig_append_ipv4(ipconfig_ipv4, iter);
+       return 0;
 }
 
-static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
+void connman_session_policy_unregister(struct connman_session_policy *plugin)
 {
-       struct connman_service *service = user_data;
-       struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6;
-
-       if (service == NULL)
+       if (plugin != policy)
                return;
 
-       if (__connman_service_is_connected_state(service,
-                               CONNMAN_IPCONFIG_TYPE_IPV6) == FALSE) {
-               return;
-       }
+       DBG("name %s", policy->name);
 
-       ipconfig_ipv4 = __connman_service_get_ip4config(service);
-       ipconfig_ipv6 = __connman_service_get_ip6config(service);
-       if (ipconfig_ipv6 == NULL)
-               return;
+       policy = NULL;
+}
 
-       __connman_ipconfig_append_ipv6(ipconfig_ipv6, iter, ipconfig_ipv4);
+static int default_bearers[] = {
+       CONNMAN_SERVICE_TYPE_ETHERNET,
+       CONNMAN_SERVICE_TYPE_WIFI,
+       CONNMAN_SERVICE_TYPE_BLUETOOTH,
+       CONNMAN_SERVICE_TYPE_CELLULAR,
+       CONNMAN_SERVICE_TYPE_GADGET,
+};
+
+static void add_default_bearer_types(GSList **list)
+{
+       unsigned int i;
+
+       for (i = 0; i < G_N_ELEMENTS(default_bearers); i++)
+               *list = g_slist_append(*list,
+                               GINT_TO_POINTER(default_bearers[i]));
 }
 
-static void append_notify(DBusMessageIter *dict,
-                                       struct connman_session *session)
+void connman_session_set_default_config(struct connman_session_config *config)
 {
-       struct session_info *info = session->info;
-       struct session_info *info_last = session->info_last;
-       struct connman_service *service;
-       const char *name, *ifname, *bearer;
+       config->id_type = CONNMAN_SESSION_ID_TYPE_UNKNOWN;
+       g_free(config->id);
+       config->id = NULL;
 
-       if (session->append_all == TRUE ||
-                       info->state != info_last->state) {
-               const char *state = state2string(info->state);
+       config->priority = FALSE;
+       config->roaming_policy = CONNMAN_SESSION_ROAMING_POLICY_DEFAULT;
+       config->type = CONNMAN_SESSION_TYPE_ANY;
+       config->ecall = FALSE;
 
-               connman_dbus_dict_append_basic(dict, "State",
-                                               DBUS_TYPE_STRING,
-                                               &state);
-               info_last->state = info->state;
-       }
+       g_slist_free(config->allowed_bearers);
+       config->allowed_bearers = NULL;
+       add_default_bearer_types(&config->allowed_bearers);
+}
 
-       if (session->append_all == TRUE ||
-                       info->entry != info_last->entry) {
-               if (info->entry == NULL) {
-                       name = "";
-                       ifname = "";
-                       service = NULL;
-                       bearer = "";
-               } else {
-                       name = info->entry->name;
-                       ifname = info->entry->ifname;
-                       service = info->entry->service;
-                       bearer = info->entry->bearer;
-               }
-
-               connman_dbus_dict_append_basic(dict, "Name",
-                                               DBUS_TYPE_STRING,
-                                               &name);
-
-               connman_dbus_dict_append_dict(dict, "IPv4",
-                                               append_ipconfig_ipv4,
-                                               service);
-
-               connman_dbus_dict_append_dict(dict, "IPv6",
-                                               append_ipconfig_ipv6,
-                                               service);
-
-               connman_dbus_dict_append_basic(dict, "Interface",
-                                               DBUS_TYPE_STRING,
-                                               &ifname);
-
-               connman_dbus_dict_append_basic(dict, "Bearer",
-                                               DBUS_TYPE_STRING,
-                                               &bearer);
-
-               info_last->entry = info->entry;
-       }
-
-       if (session->append_all == TRUE ||
-                       info->config.type != info_last->config.type) {
-               const char *type = type2string(info->config.type);
-
-               connman_dbus_dict_append_basic(dict, "ConnectionType",
-                                               DBUS_TYPE_STRING,
-                                               &type);
-               info_last->config.type = info->config.type;
-       }
-
-       if (session->append_all == TRUE ||
-                       info->config.allowed_bearers != info_last->config.allowed_bearers) {
-               connman_dbus_dict_append_array(dict, "AllowedBearers",
-                                               DBUS_TYPE_STRING,
-                                               append_allowed_bearers,
-                                               info);
-               info_last->config.allowed_bearers = info->config.allowed_bearers;
-       }
-
-       session->append_all = FALSE;
-}
-
-static connman_bool_t is_type_matching_state(enum connman_session_state *state,
-                                               enum connman_session_type type)
-{
-       switch (type) {
-       case CONNMAN_SESSION_TYPE_UNKNOWN:
-               return FALSE;
-       case CONNMAN_SESSION_TYPE_ANY:
-               return TRUE;
-       case CONNMAN_SESSION_TYPE_LOCAL:
-               if (*state >= CONNMAN_SESSION_STATE_CONNECTED) {
-                       *state = CONNMAN_SESSION_STATE_CONNECTED;
-                       return TRUE;
-               }
-
-               break;
-       case CONNMAN_SESSION_TYPE_INTERNET:
-               if (*state == CONNMAN_SESSION_STATE_ONLINE)
-                       return TRUE;
-               break;
-       }
-
-       return FALSE;
-}
-
-static connman_bool_t compute_notifiable_changes(struct connman_session *session)
-{
-       struct session_info *info_last = session->info_last;
-       struct session_info *info = session->info;
-
-       if (session->append_all == TRUE)
-               return TRUE;
-
-       if (info->state != info_last->state)
-               return TRUE;
-
-       if (info->entry != info_last->entry &&
-                       info->state >= CONNMAN_SESSION_STATE_CONNECTED)
-               return TRUE;
-
-       if (info->config.allowed_bearers != info_last->config.allowed_bearers ||
-                       info->config.type != info_last->config.type)
-               return TRUE;
-
-       return FALSE;
-}
-
-static gboolean session_notify(gpointer user_data)
+struct connman_session_config *connman_session_create_default_config(void)
 {
-       struct connman_session *session = user_data;
-       DBusMessage *msg;
-       DBusMessageIter array, dict;
-
-       if (compute_notifiable_changes(session) == FALSE)
-               return FALSE;
-
-       DBG("session %p owner %s notify_path %s", session,
-               session->owner, session->notify_path);
-
-       msg = dbus_message_new_method_call(session->owner, session->notify_path,
-                                               CONNMAN_NOTIFICATION_INTERFACE,
-                                               "Update");
-       if (msg == NULL)
-               return FALSE;
-
-       dbus_message_iter_init_append(msg, &array);
-       connman_dbus_dict_open(&array, &dict);
-
-       append_notify(&dict, session);
-
-       connman_dbus_dict_close(&array, &dict);
+       struct connman_session_config *config;
 
-       g_dbus_send_message(connection, msg);
+       config = g_new0(struct connman_session_config, 1);
+       connman_session_set_default_config(config);
 
-       return FALSE;
+       return config;
 }
 
-static void ipconfig_ipv4_changed(struct connman_session *session)
+static enum connman_session_type apply_policy_on_type(
+                       enum connman_session_type policy,
+                       enum connman_session_type type)
 {
-       struct session_info *info = session->info;
+       if (type == CONNMAN_SESSION_TYPE_UNKNOWN)
+               return CONNMAN_SESSION_TYPE_UNKNOWN;
 
-       connman_dbus_setting_changed_dict(session->owner, session->notify_path,
-                                               "IPv4", append_ipconfig_ipv4,
-                                               info->entry->service);
-}
+       if (policy == CONNMAN_SESSION_TYPE_ANY)
+               return type;
 
-static void ipconfig_ipv6_changed(struct connman_session *session)
-{
-       struct session_info *info = session->info;
+       if (policy == CONNMAN_SESSION_TYPE_LOCAL)
+               return CONNMAN_SESSION_TYPE_LOCAL;
 
-       connman_dbus_setting_changed_dict(session->owner, session->notify_path,
-                                               "IPv6", append_ipconfig_ipv6,
-                                               info->entry->service);
+       return CONNMAN_SESSION_TYPE_INTERNET;
 }
 
-static connman_bool_t service_type_match(struct connman_session *session,
-                                       struct connman_service *service)
+int connman_session_parse_bearers(const char *token, GSList **list)
 {
-       struct session_info *info = session->info;
-       GSList *list;
-
-       for (list = info->config.allowed_bearers;
-                       list != NULL; list = list->next) {
-               struct connman_session_bearer *bearer = list->data;
-               enum connman_service_type service_type;
+       enum connman_service_type bearer;
+       int err;
 
-               if (bearer->match_all == TRUE)
-                       return TRUE;
+       if (g_strcmp0(token, "") == 0)
+               return 0;
 
-               service_type = connman_service_get_type(service);
-               if (bearer->service_type == service_type)
-                       return TRUE;
+       if (g_strcmp0(token, "*") == 0) {
+               add_default_bearer_types(list);
+               return 0;
        }
 
-       return FALSE;
-}
-
-static connman_bool_t service_match(struct connman_session *session,
-                                       struct connman_service *service)
-{
-       if (service_type_match(session, service) == FALSE)
-               return FALSE;
-
-       return TRUE;
-}
-
-static int service_type_weight(enum connman_service_type type)
-{
-       /*
-        * The session doesn't care which service
-        * to use. Nevertheless we have to sort them
-        * according their type. The ordering is
-        *
-        * 1. Ethernet
-        * 2. Bluetooth
-        * 3. WiFi/WiMAX
-        * 4. Cellular
-        */
+       err = bearer2service(token, &bearer);
+       if (err < 0)
+               return err;
 
-       switch (type) {
-       case CONNMAN_SERVICE_TYPE_ETHERNET:
-               return 4;
-       case CONNMAN_SERVICE_TYPE_BLUETOOTH:
-               return 3;
-       case CONNMAN_SERVICE_TYPE_WIFI:
-       case CONNMAN_SERVICE_TYPE_WIMAX:
-               return 2;
-       case CONNMAN_SERVICE_TYPE_CELLULAR:
-               return 1;
-       case CONNMAN_SERVICE_TYPE_UNKNOWN:
-       case CONNMAN_SERVICE_TYPE_SYSTEM:
-       case CONNMAN_SERVICE_TYPE_GPS:
-       case CONNMAN_SERVICE_TYPE_VPN:
-       case CONNMAN_SERVICE_TYPE_GADGET:
-               break;
-       }
+       *list = g_slist_append(*list, GINT_TO_POINTER(bearer));
 
        return 0;
 }
 
-static gint sort_allowed_bearers(struct connman_service *service_a,
-                                       struct connman_service *service_b,
-                                       struct connman_session *session)
+static int parse_bearers(DBusMessageIter *iter, GSList **list)
 {
-       struct session_info *info = session->info;
-       GSList *list;
-       enum connman_service_type type_a, type_b;
-       int weight_a, weight_b;
-
-       type_a = connman_service_get_type(service_a);
-       type_b = connman_service_get_type(service_b);
-
-       for (list = info->config.allowed_bearers;
-                       list != NULL; list = list->next) {
-               struct connman_session_bearer *bearer = list->data;
+       DBusMessageIter array;
+       int type, err;
 
-               if (bearer->match_all == TRUE) {
-                       if (type_a != type_b) {
-                               weight_a = service_type_weight(type_a);
-                               weight_b = service_type_weight(type_b);
+       dbus_message_iter_recurse(iter, &array);
 
-                               if (weight_a > weight_b)
-                                       return -1;
+       *list = NULL;
 
-                               if (weight_a < weight_b)
-                                       return 1;
+       while ((type = dbus_message_iter_get_arg_type(&array)) !=
+                       DBUS_TYPE_INVALID) {
+               char *bearer_name = NULL;
 
-                               return 0;
-                       }
+               if (type != DBUS_TYPE_STRING) {
+                       g_slist_free(*list);
+                       *list = NULL;
+                       return -EINVAL;
                }
 
-               if (type_a == bearer->service_type &&
-                               type_b == bearer->service_type) {
-                       return 0;
-               }
+               dbus_message_iter_get_basic(&array, &bearer_name);
 
-               if (type_a == bearer->service_type &&
-                               type_b != bearer->service_type) {
-                       return -1;
+               err = connman_session_parse_bearers(bearer_name, list);
+               if (err < 0) {
+                       g_slist_free(*list);
+                       *list = NULL;
+                       return err;
                }
 
-               if (type_a != bearer->service_type &&
-                               type_b == bearer->service_type) {
-                       return 1;
-               }
+               dbus_message_iter_next(&array);
        }
 
        return 0;
 }
 
-static gint sort_services(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-       struct service_entry *entry_a = (void *)a;
-       struct service_entry *entry_b = (void *)b;
-       struct connman_session *session = user_data;
-
-       return sort_allowed_bearers(entry_a->service, entry_b->service,
-                               session);
-}
-
-static void cleanup_session(gpointer user_data)
+static void filter_bearer(GSList *bearers,
+                               enum connman_service_type policy,
+                               GSList **list)
 {
-       struct connman_session *session = user_data;
-       struct session_info *info = session->info;
-
-       DBG("remove %s", session->session_path);
-
-       g_hash_table_destroy(session->service_hash);
-       g_sequence_free(session->service_list);
+       enum connman_service_type bearer;
+       GSList *it;
 
-       if (info->entry != NULL &&
-                       info->entry->reason == CONNMAN_SESSION_REASON_CONNECT) {
-               __connman_service_disconnect(info->entry->service);
-       }
+       if (!bearers)
+               return;
 
-       destroy_policy_config(session);
-       connman_session_free_bearers(session->info->config.allowed_bearers);
-       g_free(session->owner);
-       g_free(session->session_path);
-       g_free(session->notify_path);
-       g_free(session->info);
-       g_free(session->info_last);
+       for (it = bearers; it; it = it->next) {
+               bearer = GPOINTER_TO_INT(it->data);
 
-       g_free(session);
-}
+               if (policy != bearer)
+                       continue;
 
-static enum connman_session_state service_to_session_state(enum connman_service_state state)
-{
-       switch (state) {
-       case CONNMAN_SERVICE_STATE_UNKNOWN:
-       case CONNMAN_SERVICE_STATE_IDLE:
-       case CONNMAN_SERVICE_STATE_ASSOCIATION:
-       case CONNMAN_SERVICE_STATE_CONFIGURATION:
-       case CONNMAN_SERVICE_STATE_DISCONNECT:
-       case CONNMAN_SERVICE_STATE_FAILURE:
-               break;
-       case CONNMAN_SERVICE_STATE_READY:
-               return CONNMAN_SESSION_STATE_CONNECTED;
-       case CONNMAN_SERVICE_STATE_ONLINE:
-               return CONNMAN_SESSION_STATE_ONLINE;
+               *list = g_slist_append(*list, GINT_TO_POINTER(bearer));
+               return;
        }
-
-       return CONNMAN_SESSION_STATE_DISCONNECTED;
 }
 
-static connman_bool_t is_connected(enum connman_service_state state)
+static void apply_policy_on_bearers(GSList *policy_bearers, GSList *bearers,
+                               GSList **list)
 {
-       switch (state) {
-       case CONNMAN_SERVICE_STATE_UNKNOWN:
-       case CONNMAN_SERVICE_STATE_IDLE:
-       case CONNMAN_SERVICE_STATE_ASSOCIATION:
-       case CONNMAN_SERVICE_STATE_CONFIGURATION:
-       case CONNMAN_SERVICE_STATE_DISCONNECT:
-       case CONNMAN_SERVICE_STATE_FAILURE:
-               break;
-       case CONNMAN_SERVICE_STATE_READY:
-       case CONNMAN_SERVICE_STATE_ONLINE:
-               return TRUE;
-       }
-
-       return FALSE;
-}
+       enum connman_service_type policy_bearer;
+       GSList *it;
 
-static connman_bool_t is_connecting(enum connman_service_state state)
-{
-       switch (state) {
-       case CONNMAN_SERVICE_STATE_UNKNOWN:
-       case CONNMAN_SERVICE_STATE_IDLE:
-               break;
-       case CONNMAN_SERVICE_STATE_ASSOCIATION:
-       case CONNMAN_SERVICE_STATE_CONFIGURATION:
-               return TRUE;
-       case CONNMAN_SERVICE_STATE_DISCONNECT:
-       case CONNMAN_SERVICE_STATE_FAILURE:
-       case CONNMAN_SERVICE_STATE_READY:
-       case CONNMAN_SERVICE_STATE_ONLINE:
-               break;
-       }
+       *list = NULL;
 
-       return FALSE;
-}
+       for (it = policy_bearers; it; it = it->next) {
+               policy_bearer = GPOINTER_TO_INT(it->data);
 
-static connman_bool_t explicit_connect(enum connman_session_reason reason)
-{
-       switch (reason) {
-       case CONNMAN_SESSION_REASON_UNKNOWN:
-       case CONNMAN_SESSION_REASON_FREE_RIDE:
-               break;
-       case CONNMAN_SESSION_REASON_CONNECT:
-               return TRUE;
+               filter_bearer(bearers, policy_bearer, list);
        }
-
-       return FALSE;
 }
 
-static connman_bool_t explicit_disconnect(struct session_info *info)
+static char * apply_policy_on_interface(const char *policy_interface,
+                               const char *user_interface)
 {
-       if (info->entry == NULL)
-               return FALSE;
-
-       DBG("reason %s service %p state %d",
-               reason2string(info->entry->reason),
-               info->entry->service, info->entry->state);
-
-       if (info->entry->reason == CONNMAN_SESSION_REASON_UNKNOWN)
-               return FALSE;
-
-       if (explicit_connect(info->entry->reason) == FALSE)
-               return FALSE;
-
-       if (__connman_service_session_dec(info->entry->service) == FALSE)
-               return FALSE;
-
-       return TRUE;
+       if (policy_interface)
+               return g_strdup(policy_interface);
+       else if (user_interface)
+               return g_strdup(user_interface);
+       else
+               return NULL;
 }
 
-struct pending_data {
-       unsigned int timeout;
-       struct service_entry *entry;
-       gboolean (*cb)(gpointer);
-};
-
-static void pending_timeout_free(gpointer data, gpointer user_data)
+const char *connman_session_get_owner(struct connman_session *session)
 {
-       struct pending_data *pending = data;
-
-       DBG("pending %p timeout %d", pending, pending->timeout);
-       g_source_remove(pending->timeout);
-       g_free(pending);
+       return session->owner;
 }
 
-static void pending_timeout_remove_all(struct service_entry *entry)
-{
-       DBG("");
-
-       g_slist_foreach(entry->pending_timeouts, pending_timeout_free, NULL);
-       g_slist_free(entry->pending_timeouts);
-       entry->pending_timeouts = NULL;
-}
-
-static gboolean pending_timeout_cb(gpointer data)
+static void append_allowed_bearers(DBusMessageIter *iter, void *user_data)
 {
-       struct pending_data *pending = data;
-       struct service_entry *entry = pending->entry;
-       gboolean ret;
-
-       DBG("pending %p timeout %d", pending, pending->timeout);
+       struct session_info *info = user_data;
+       GSList *list;
 
-       ret = pending->cb(pending->entry);
-       if (ret == FALSE) {
-               entry->pending_timeouts =
-                       g_slist_remove(entry->pending_timeouts,
-                                       pending);
-               g_free(pending);
-       }
-       return ret;
-}
+       for (list = info->config.allowed_bearers;
+                       list; list = list->next) {
+               enum connman_service_type bearer = GPOINTER_TO_INT(list->data);
+               const char *name = __connman_service_type2string(bearer);
 
-static connman_bool_t pending_timeout_add(unsigned int seconds,
-                                       gboolean (*cb)(gpointer),
-                                       struct service_entry *entry)
-{
-       struct pending_data *pending = g_try_new0(struct pending_data, 1);
+               if (!name)
+                       name = "";
 
-       if (pending == NULL || cb == NULL || entry == NULL) {
-               g_free(pending);
-               return FALSE;
+               dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
+                                               &name);
        }
-
-       pending->cb = cb;
-       pending->entry = entry;
-       pending->timeout = g_timeout_add_seconds(seconds, pending_timeout_cb,
-                                               pending);
-       entry->pending_timeouts = g_slist_prepend(entry->pending_timeouts,
-                                               pending);
-
-       DBG("pending %p entry %p timeout id %d", pending, entry,
-               pending->timeout);
-
-       return TRUE;
 }
 
-static gboolean call_disconnect(gpointer user_data)
+static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data)
 {
-       struct service_entry *entry = user_data;
-       struct connman_service *service = entry->service;
-
-       /*
-        * TODO: We should mark this entry as pending work. In case
-        * disconnect fails we just unassign this session from the
-        * service and can't do anything later on it
-        */
-       DBG("disconnect service %p", service);
-       __connman_service_disconnect(service);
+       struct connman_service *service = user_data;
+       struct connman_ipconfig *ipconfig_ipv4;
 
-       return FALSE;
-}
+       if (!service)
+               return;
 
-static gboolean call_connect(gpointer user_data)
-{
-       struct service_entry *entry = user_data;
-       struct connman_service *service = entry->service;
+       if (!__connman_service_is_connected_state(service,
+                                               CONNMAN_IPCONFIG_TYPE_IPV4))
+               return;
 
-       DBG("connect service %p", service);
-       __connman_service_connect(service);
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       if (!ipconfig_ipv4)
+               return;
 
-       return FALSE;
+       __connman_ipconfig_append_ipv4(ipconfig_ipv4, iter);
 }
 
-static void deselect_service(struct session_info *info)
+static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
 {
-       struct service_entry *entry;
-       connman_bool_t disconnect, connected;
-
-       DBG("");
+       struct connman_service *service = user_data;
+       struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6;
 
-       if (info->entry == NULL)
+       if (!service)
                return;
 
-       disconnect = explicit_disconnect(info);
-
-       connected = is_connecting(info->entry->state) == TRUE ||
-                       is_connected(info->entry->state) == TRUE;
-
-       info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
-       info->entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
-
-       entry = info->entry;
-       info->entry = NULL;
-
-       DBG("disconnect %d connected %d", disconnect, connected);
-
-       if (disconnect == TRUE && connected == TRUE)
-               pending_timeout_add(0, call_disconnect, entry);
-}
-
-static void deselect_and_disconnect(struct connman_session *session)
-{
-       struct session_info *info = session->info;
+       if (!__connman_service_is_connected_state(service,
+                                               CONNMAN_IPCONFIG_TYPE_IPV6))
+               return;
 
-       deselect_service(info);
+       ipconfig_ipv4 = __connman_service_get_ip4config(service);
+       ipconfig_ipv6 = __connman_service_get_ip6config(service);
+       if (!ipconfig_ipv6)
+               return;
 
-       info->reason = CONNMAN_SESSION_REASON_FREE_RIDE;
+       __connman_ipconfig_append_ipv6(ipconfig_ipv6, iter, ipconfig_ipv4);
 }
 
-static void select_connected_service(struct session_info *info,
-                                       struct service_entry *entry)
+static void append_notify(DBusMessageIter *dict,
+                                       struct connman_session *session)
 {
-       enum connman_session_state state;
-
-       state = service_to_session_state(entry->state);
-       if (is_type_matching_state(&state, info->config.type) == FALSE)
-               return;
+       struct session_info *info = session->info;
+       struct session_info *info_last = session->info_last;
+       struct connman_service *service;
+       enum connman_service_type type;
+       const char *name, *bearer;
+       char *ifname;
+       int idx;
 
-       info->state = state;
+       if (session->append_all || info->state != info_last->state) {
+               const char *state = state2string(info->state);
 
-       info->entry = entry;
-       info->entry->reason = info->reason;
+               connman_dbus_dict_append_basic(dict, "State",
+                                               DBUS_TYPE_STRING,
+                                               &state);
+               info_last->state = info->state;
+       }
 
-       if (explicit_connect(info->reason) == FALSE)
-               return;
+       if (session->append_all || session->service != session->service_last) {
+               if (session->service) {
+                       service = session->service;
+                       name = __connman_service_get_name(service);
+                       idx = __connman_service_get_index(service);
 
-       __connman_service_session_inc(info->entry->service);
-}
+                       ifname = connman_inet_ifname(idx);
+                       if (!ifname)
+                               ifname = g_strdup("");
 
-static void select_offline_service(struct session_info *info,
-                                       struct service_entry *entry)
-{
-       if (explicit_connect(info->reason) == FALSE)
-               return;
+                       type = connman_service_get_type(service);
+                       bearer = service2bearer(type);
+               } else {
+                       service = NULL;
+                       name = "";
+                       ifname = g_strdup("");
+                       bearer = "";
+               }
 
-       info->state = service_to_session_state(entry->state);
+               connman_dbus_dict_append_basic(dict, "Name",
+                                               DBUS_TYPE_STRING,
+                                               &name);
 
-       info->entry = entry;
-       info->entry->reason = info->reason;
+               connman_dbus_dict_append_dict(dict, "IPv4",
+                                               append_ipconfig_ipv4,
+                                               service);
 
-       __connman_service_session_inc(info->entry->service);
-       pending_timeout_add(0, call_connect, entry);
-}
+               connman_dbus_dict_append_dict(dict, "IPv6",
+                                               append_ipconfig_ipv6,
+                                               service);
 
-static void select_service(struct session_info *info,
-                               struct service_entry *entry)
-{
-       DBG("service %p", entry->service);
+               connman_dbus_dict_append_basic(dict, "Interface",
+                                               DBUS_TYPE_STRING,
+                                               &ifname);
 
-       if (is_connected(entry->state) == TRUE)
-               select_connected_service(info, entry);
-       else
-               select_offline_service(info, entry);
-}
+               connman_dbus_dict_append_basic(dict, "Bearer",
+                                               DBUS_TYPE_STRING,
+                                               &bearer);
 
-static void select_and_connect(struct connman_session *session,
-                               enum connman_session_reason reason)
-{
-       struct session_info *info = session->info;
-       struct service_entry *entry = NULL;
-       GSequenceIter *iter;
+               g_free(ifname);
 
-       DBG("session %p reason %s", session, reason2string(reason));
+               session->service_last = session->service;
+       }
 
-       info->reason = reason;
+       if (session->append_all ||
+                       info->config.type != info_last->config.type) {
+               const char *type = type2string(info->config.type);
 
-       iter = g_sequence_get_begin_iter(session->service_list);
+               connman_dbus_dict_append_basic(dict, "ConnectionType",
+                                               DBUS_TYPE_STRING,
+                                               &type);
+               info_last->config.type = info->config.type;
+       }
 
-       while (g_sequence_iter_is_end(iter) == FALSE) {
-               entry = g_sequence_get(iter);
+       if (session->append_all ||
+                       info->config.allowed_bearers != info_last->config.allowed_bearers) {
+               connman_dbus_dict_append_array(dict, "AllowedBearers",
+                                               DBUS_TYPE_STRING,
+                                               append_allowed_bearers,
+                                               info);
+               info_last->config.allowed_bearers = info->config.allowed_bearers;
+       }
 
-               switch (entry->state) {
-               case CONNMAN_SERVICE_STATE_ASSOCIATION:
-               case CONNMAN_SERVICE_STATE_CONFIGURATION:
-               case CONNMAN_SERVICE_STATE_READY:
-               case CONNMAN_SERVICE_STATE_ONLINE:
-               case CONNMAN_SERVICE_STATE_IDLE:
-               case CONNMAN_SERVICE_STATE_DISCONNECT:
-                       select_service(info, entry);
-                       return;
-               case CONNMAN_SERVICE_STATE_UNKNOWN:
-               case CONNMAN_SERVICE_STATE_FAILURE:
-                       break;
-               }
+       if (session->append_all ||
+                       info->config.allowed_interface != info_last->config.allowed_interface) {
+               char *ifname = info->config.allowed_interface;
+               if (!ifname)
+                       ifname = "*";
+               connman_dbus_dict_append_basic(dict, "AllowedInterface",
+                                               DBUS_TYPE_STRING,
+                                               &ifname);
+               info_last->config.allowed_interface = info->config.allowed_interface;
+       }
 
-               iter = g_sequence_iter_next(iter);
+       if (session->append_all ||
+                       info->config.source_ip_rule != info_last->config.source_ip_rule) {
+               dbus_bool_t source_ip_rule = FALSE;
+               if (info->config.source_ip_rule)
+                       source_ip_rule = TRUE;
+               connman_dbus_dict_append_basic(dict, "SourceIPRule",
+                                               DBUS_TYPE_BOOLEAN,
+                                               &source_ip_rule);
+               info_last->config.source_ip_rule = info->config.source_ip_rule;
        }
+
+       session->append_all = false;
 }
 
-static struct service_entry *create_service_entry(struct connman_service *service,
-                                       const char *name,
-                                       enum connman_service_state state)
+static bool compute_notifiable_changes(struct connman_session *session)
 {
-       struct service_entry *entry;
-       enum connman_service_type type;
-       int idx;
+       struct session_info *info_last = session->info_last;
+       struct session_info *info = session->info;
 
-       entry = g_try_new0(struct service_entry, 1);
-       if (entry == NULL)
-               return entry;
+       if (session->append_all)
+               return true;
 
-       entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
-       entry->state = state;
-       if (name != NULL)
-               entry->name = name;
-       else
-               entry->name = "";
-       entry->service = service;
+       if (info->state != info_last->state)
+               return true;
 
-       idx = __connman_service_get_index(entry->service);
-       entry->ifname = connman_inet_ifname(idx);
-       if (entry->ifname == NULL)
-               entry->ifname = g_strdup("");
+       if (session->service != session->service_last &&
+                       info->state >= CONNMAN_SESSION_STATE_CONNECTED)
+               return true;
 
-       type = connman_service_get_type(entry->service);
-       entry->bearer = service2bearer(type);
+       if (info->config.allowed_bearers != info_last->config.allowed_bearers ||
+                       info->config.type != info_last->config.type ||
+                       info->config.allowed_interface != info_last->config.allowed_interface ||
+                       info->config.source_ip_rule != info_last->config.source_ip_rule)
+               return true;
 
-       return entry;
+       return false;
 }
 
-static void destroy_service_entry(gpointer data)
+static gboolean session_notify(gpointer user_data)
 {
-       struct service_entry *entry = data;
+       struct connman_session *session = user_data;
+       DBusMessage *msg;
+       DBusMessageIter array, dict;
 
-       pending_timeout_remove_all(entry);
-       g_free(entry->ifname);
+       if (!compute_notifiable_changes(session))
+               return FALSE;
 
-       g_free(entry);
-}
+       DBG("session %p owner %s notify_path %s", session,
+               session->owner, session->notify_path);
 
-static void populate_service_list(struct connman_session *session)
-{
-       struct service_entry *entry;
-       GSequenceIter *iter;
+       msg = dbus_message_new_method_call(session->owner, session->notify_path,
+                                               CONNMAN_NOTIFICATION_INTERFACE,
+                                               "Update");
+       if (!msg)
+               return FALSE;
 
-       session->service_hash =
-               g_hash_table_new_full(g_direct_hash, g_direct_equal,
-                                       NULL, NULL);
-       session->service_list = __connman_service_get_list(session,
-                                                       service_match,
-                                                       create_service_entry,
-                                                       destroy_service_entry);
+       dbus_message_iter_init_append(msg, &array);
+       connman_dbus_dict_open(&array, &dict);
 
-       g_sequence_sort(session->service_list, sort_services, session);
+       append_notify(&dict, session);
 
-       iter = g_sequence_get_begin_iter(session->service_list);
+       connman_dbus_dict_close(&array, &dict);
 
-       while (g_sequence_iter_is_end(iter) == FALSE) {
-               entry = g_sequence_get(iter);
+       g_dbus_send_message(connection, msg);
 
-               DBG("service %p type %s name %s", entry->service,
-                       service2bearer(connman_service_get_type(entry->service)),
-                       entry->name);
+       return FALSE;
+}
 
-               g_hash_table_replace(session->service_hash,
-                                       entry->service, iter);
+static void ipconfig_ipv4_changed(struct connman_session *session)
+{
+       connman_dbus_setting_changed_dict(session->owner, session->notify_path,
+                                               "IPv4", append_ipconfig_ipv4,
+                                               session->service);
+}
 
-               iter = g_sequence_iter_next(iter);
-       }
+static void ipconfig_ipv6_changed(struct connman_session *session)
+{
+       connman_dbus_setting_changed_dict(session->owner, session->notify_path,
+                                               "IPv6", append_ipconfig_ipv6,
+                                               session->service);
 }
 
-static void session_changed(struct connman_session *session,
-                               enum connman_session_trigger trigger)
+int connman_session_config_update(struct connman_session *session)
 {
        struct session_info *info = session->info;
-       struct session_info *info_last = session->info_last;
-       GSequenceIter *service_iter = NULL, *service_iter_last = NULL;
-       GSequence *service_list_last;
-       GHashTable *service_hash_last;
+       GSList *allowed_bearers;
+       char *allowed_interface;
+       int err;
+
+       DBG("session %p", session);
 
        /*
-        * TODO: This only a placeholder for the 'real' algorithm to
-        * play a bit around. So we are going to improve it step by step.
+        * We update all configuration even though only one entry
+        * might have changed. We can still optimize this later.
         */
 
-       DBG("session %p trigger %s reason %s", session, trigger2string(trigger),
-                                               reason2string(info->reason));
-
-       if (info->entry != NULL) {
-               enum connman_session_state state;
-
-               state = service_to_session_state(info->entry->state);
+       if (session->id_type != session->policy_config->id_type) {
+               cleanup_firewall_session(session);
+               err = init_firewall_session(session);
+               if (err < 0) {
+                       connman_session_destroy(session);
+                       return err;
+               }
 
-               if (is_type_matching_state(&state, info->config.type) == TRUE)
-                       info->state = state;
+               session->id_type = session->policy_config->id_type;
        }
 
-       switch (trigger) {
-       case CONNMAN_SESSION_TRIGGER_UNKNOWN:
-               DBG("ignore session changed event");
-               return;
-       case CONNMAN_SESSION_TRIGGER_SETTING:
-               if (info->config.allowed_bearers != info_last->config.allowed_bearers) {
-
-                       service_hash_last = session->service_hash;
-                       service_list_last = session->service_list;
-
-                       populate_service_list(session);
-
-                       if (info->entry != NULL) {
-                               service_iter_last = g_hash_table_lookup(
-                                                       service_hash_last,
-                                                       info->entry->service);
-                               service_iter = g_hash_table_lookup(
-                                                       session->service_hash,
-                                                       info->entry->service);
-                       }
-
-                       if (service_iter == NULL && service_iter_last != NULL) {
-                               /*
-                                * The currently selected service is
-                                * not part of this session anymore.
-                                */
-                               deselect_and_disconnect(session);
-                       }
-
-                       g_hash_table_remove_all(service_hash_last);
-                       g_sequence_free(service_list_last);
-               }
+       apply_policy_on_bearers(
+               session->policy_config->allowed_bearers,
+               session->user_allowed_bearers,
+               &allowed_bearers);
 
-               if (info->config.type != info_last->config.type) {
-                       if (info->state >= CONNMAN_SESSION_STATE_CONNECTED &&
-                                       is_type_matching_state(&info->state,
-                                                       info->config.type) == FALSE)
-                               deselect_and_disconnect(session);
-               }
+       allowed_interface = apply_policy_on_interface(
+               session->policy_config->allowed_interface,
+               session->user_allowed_interface);
 
-               if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED) {
-                       select_and_connect(session,
-                                       CONNMAN_SESSION_REASON_FREE_RIDE);
-               }
+       if (session->active)
+               set_active_session(session, false);
 
-               break;
-       case CONNMAN_SESSION_TRIGGER_ECALL:
-               /*
-                * For the time beeing we fallback to normal connect
-                * strategy.
-                */
-       case CONNMAN_SESSION_TRIGGER_CONNECT:
-               if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) {
-                       if (info->entry->reason == CONNMAN_SESSION_REASON_CONNECT)
-                               break;
-                       info->entry->reason = CONNMAN_SESSION_REASON_CONNECT;
-                       __connman_service_session_inc(info->entry->service);
-                       break;
-               }
+       session->active = false;
+       session_deactivate(session);
 
-               if (info->entry != NULL &&
-                               is_connecting(info->entry->state) == TRUE) {
-                       break;
-               }
+       g_slist_free(info->config.allowed_bearers);
+       info->config.allowed_bearers = allowed_bearers;
 
-               select_and_connect(session,
-                               CONNMAN_SESSION_REASON_CONNECT);
+       g_free(info->config.allowed_interface);
+       info->config.allowed_interface = allowed_interface;
 
-               break;
-       case CONNMAN_SESSION_TRIGGER_DISCONNECT:
-               deselect_and_disconnect(session);
+       session_activate(session);
 
-               break;
-       case CONNMAN_SESSION_TRIGGER_SERVICE:
-               if (info->entry != NULL &&
-                       (is_connecting(info->entry->state) == TRUE ||
-                               is_connected(info->entry->state) == TRUE)) {
-                       break;
-               }
+       info->config.type = apply_policy_on_type(
+                               session->policy_config->type,
+                               info->config.type);
 
-               deselect_and_disconnect(session);
+       info->config.roaming_policy = session->policy_config->roaming_policy;
 
-               if (info->reason == CONNMAN_SESSION_REASON_FREE_RIDE) {
-                       select_and_connect(session, info->reason);
-               }
+       info->config.ecall = session->policy_config->ecall;
+       if (info->config.ecall)
+               ecall_session = session;
 
-               break;
-       }
+       info->config.priority = session->policy_config->priority;
 
        session_notify(session);
+
+       return 0;
 }
 
 static DBusMessage *connect_session(DBusConnection *conn,
@@ -1390,14 +1083,21 @@ static DBusMessage *connect_session(DBusConnection *conn,
 
        DBG("session %p", session);
 
-       if (ecall_session != NULL) {
-               if (ecall_session->ecall == TRUE && ecall_session != session)
+       if (ecall_session) {
+               if (ecall_session->ecall && ecall_session != session)
                        return __connman_error_failed(msg, EBUSY);
 
-               session->ecall = TRUE;
-               session_changed(session, CONNMAN_SESSION_TRIGGER_ECALL);
-       } else
-               session_changed(session, CONNMAN_SESSION_TRIGGER_CONNECT);
+               session->ecall = true;
+       }
+
+       if (!session->active) {
+               session->active = true;
+               set_active_session(session, true);
+       }
+
+       session_activate(session);
+
+       __connman_service_auto_connect(CONNMAN_SERVICE_CONNECT_REASON_SESSION);
 
        return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
 }
@@ -1409,14 +1109,20 @@ static DBusMessage *disconnect_session(DBusConnection *conn,
 
        DBG("session %p", session);
 
-       if (ecall_session != NULL) {
-               if (ecall_session->ecall == TRUE && ecall_session != session)
+       if (ecall_session) {
+               if (ecall_session->ecall && ecall_session != session)
                        return __connman_error_failed(msg, EBUSY);
 
-               session->ecall = FALSE;
+               session->ecall = false;
+       }
+
+       if (session->active) {
+               session->active = false;
+               set_active_session(session, false);
        }
 
-       session_changed(session, CONNMAN_SESSION_TRIGGER_DISCONNECT);
+       session_deactivate(session);
+       update_session_state(session);
 
        return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
 }
@@ -1433,7 +1139,7 @@ static DBusMessage *change_session(DBusConnection *conn,
        int err;
 
        DBG("session %p", session);
-       if (dbus_message_iter_init(msg, &iter) == FALSE)
+       if (!dbus_message_iter_init(msg, &iter))
                return __connman_error_invalid_arguments(msg);
 
        if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
@@ -1449,31 +1155,69 @@ static DBusMessage *change_session(DBusConnection *conn,
 
        switch (dbus_message_iter_get_arg_type(&value)) {
        case DBUS_TYPE_ARRAY:
-               if (g_str_equal(name, "AllowedBearers") == TRUE) {
-                       err = session_parse_allowed_bearers(&value,
-                                                       &allowed_bearers);
+               if (g_str_equal(name, "AllowedBearers")) {
+                       err = parse_bearers(&value, &allowed_bearers);
                        if (err < 0)
-                               return __connman_error_failed(msg, err);
+                               return __connman_error_failed(msg, -err);
 
-                       connman_session_free_bearers(info->config.allowed_bearers);
-                       err = apply_policy_on_bearers(
+                       if (session->active)
+                               set_active_session(session, false);
+
+                       session->active = false;
+                       session_deactivate(session);
+                       update_session_state(session);
+
+                       g_slist_free(info->config.allowed_bearers);
+                       session->user_allowed_bearers = allowed_bearers;
+
+                       apply_policy_on_bearers(
                                        session->policy_config->allowed_bearers,
-                                       allowed_bearers,
+                                       session->user_allowed_bearers,
                                        &info->config.allowed_bearers);
 
-                       connman_session_free_bearers(allowed_bearers);
-                       if (err < 0)
-                               return __connman_error_failed(msg, err);
+                       session_activate(session);
                } else {
                        goto err;
                }
                break;
        case DBUS_TYPE_STRING:
-               if (g_str_equal(name, "ConnectionType") == TRUE) {
+               if (g_str_equal(name, "ConnectionType")) {
                        dbus_message_iter_get_basic(&value, &val);
                        info->config.type = apply_policy_on_type(
                                session->policy_config->type,
-                               string2type(val));
+                               connman_session_parse_connection_type(val));
+               } else if (g_str_equal(name, "AllowedInterface")) {
+                       dbus_message_iter_get_basic(&value, &val);
+                       if (session->active)
+                               set_active_session(session, false);
+
+                       session->active = false;
+                       session_deactivate(session);
+                       update_session_state(session);
+
+                       g_free(session->user_allowed_interface);
+                       /* empty string means allow any interface */
+                       if (!g_strcmp0(val, ""))
+                               session->user_allowed_interface = NULL;
+                       else
+                               session->user_allowed_interface = g_strdup(val);
+
+                       info->config.allowed_interface = apply_policy_on_interface(
+                               session->policy_config->allowed_interface,
+                               session->user_allowed_interface);
+
+                       session_activate(session);
+               } else {
+                       goto err;
+               }
+               break;
+       case DBUS_TYPE_BOOLEAN:
+               if (g_str_equal(name, "SourceIPRule")) {
+                       dbus_bool_t source_ip_rule;
+                       dbus_message_iter_get_basic(&value, &source_ip_rule);
+
+                       info->config.source_ip_rule = source_ip_rule;
+                       update_session_state(session);
                } else {
                        goto err;
                }
@@ -1482,7 +1226,7 @@ static DBusMessage *change_session(DBusConnection *conn,
                goto err;
        }
 
-       session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
+       session_notify(session);
 
        return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
 
@@ -1507,7 +1251,7 @@ static void release_session(gpointer key, gpointer value, gpointer user_data)
                                                session->notify_path,
                                                CONNMAN_NOTIFICATION_INTERFACE,
                                                "Release");
-       if (message == NULL)
+       if (!message)
                return;
 
        dbus_message_set_no_reply(message, TRUE);
@@ -1525,66 +1269,164 @@ static int session_disconnect(struct connman_session *session)
        g_dbus_unregister_interface(connection, session->session_path,
                                                CONNMAN_SESSION_INTERFACE);
 
-       deselect_and_disconnect(session);
-
        g_hash_table_remove(session_hash, session->session_path);
 
-       return 0;
-}
+       return 0;
+}
+
+static void owner_disconnect(DBusConnection *conn, void *user_data)
+{
+       struct connman_session *session = user_data;
+
+       DBG("session %p, %s died", session, session->owner);
+
+       session_disconnect(session);
+}
+
+static DBusMessage *destroy_session(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct connman_session *session = user_data;
+
+       DBG("session %p", session);
+
+       if (ecall_session && ecall_session != session)
+               return __connman_error_failed(msg, EBUSY);
+
+       session_disconnect(session);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static const GDBusMethodTable session_methods[] = {
+       { GDBUS_METHOD("Destroy", NULL, NULL, destroy_session) },
+       { GDBUS_METHOD("Connect", NULL, NULL, connect_session) },
+       { GDBUS_METHOD("Disconnect", NULL, NULL,
+                       disconnect_session ) },
+       { GDBUS_METHOD("Change",
+                       GDBUS_ARGS({ "name", "s" }, { "value", "v" }),
+                       NULL, change_session) },
+       { },
+};
+
+static int session_policy_config_cb(struct connman_session *session,
+                               struct connman_session_config *config,
+                               void *user_data, int err)
+{
+       struct creation_data *creation_data = user_data;
+       struct session_info *info, *info_last;
+       DBusMessage *reply;
+
+       DBG("session %p config %p", session, config);
+
+       if (err < 0)
+               goto err;
+
+       session->policy_config = config;
+       session->info->config.source_ip_rule = creation_data->source_ip_rule;
+
+       session->mark = session_mark++;
+       session->index = -1;
+
+       err = init_firewall_session(session);
+       if (err < 0)
+               goto err;
+
+       err = init_routing_table(session);
+       if (err < 0)
+               goto err;
+
+       info = session->info;
+       info_last = session->info_last;
+
+       if (session->policy_config->ecall)
+               ecall_session = session;
+
+       info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
+       info->config.type = apply_policy_on_type(
+                               session->policy_config->type,
+                               creation_data->type);
+       info->config.priority = session->policy_config->priority;
+       info->config.roaming_policy = session->policy_config->roaming_policy;
+
+       session->user_allowed_bearers = creation_data->allowed_bearers;
+       creation_data->allowed_bearers = NULL;
+
+       session->user_allowed_interface = creation_data->allowed_interface;
+       creation_data->allowed_interface = NULL;
+
+       apply_policy_on_bearers(
+                       session->policy_config->allowed_bearers,
+                       session->user_allowed_bearers,
+                       &info->config.allowed_bearers);
+
+       info->config.allowed_interface = apply_policy_on_interface(
+               session->policy_config->allowed_interface,
+               session->user_allowed_interface);
+
+       g_hash_table_replace(session_hash, session->session_path, session);
+
+       DBG("add %s", session->session_path);
+
+       if (!g_dbus_register_interface(connection, session->session_path,
+                                       CONNMAN_SESSION_INTERFACE,
+                                       session_methods, NULL, NULL,
+                                       session, NULL)) {
+               connman_error("Failed to register %s", session->session_path);
+               g_hash_table_remove(session_hash, session->session_path);
+               err = -EINVAL;
+               goto err;
+       }
+
+       reply = g_dbus_create_reply(creation_data->pending,
+                               DBUS_TYPE_OBJECT_PATH, &session->session_path,
+                               DBUS_TYPE_INVALID);
+       g_dbus_send_message(connection, reply);
+       creation_data->pending = NULL;
 
-static void owner_disconnect(DBusConnection *conn, void *user_data)
-{
-       struct connman_session *session = user_data;
+       info_last->state = info->state;
+       info_last->config.priority = info->config.priority;
+       info_last->config.roaming_policy = info->config.roaming_policy;
+       info_last->config.allowed_bearers = info->config.allowed_bearers;
+       info_last->config.allowed_interface = info->config.allowed_interface;
+       info_last->config.source_ip_rule = info->config.source_ip_rule;
 
-       DBG("session %p, %s died", session, session->owner);
+       session->append_all = true;
 
-       session_disconnect(session);
-}
+       cleanup_creation_data(creation_data);
 
-static DBusMessage *destroy_session(DBusConnection *conn,
-                                       DBusMessage *msg, void *user_data)
-{
-       struct connman_session *session = user_data;
+       session_activate(session);
 
-       DBG("session %p", session);
+       return 0;
 
-       if (ecall_session != NULL && ecall_session != session)
-               return __connman_error_failed(msg, EBUSY);
+err:
+       reply = __connman_error_failed(creation_data->pending, -err);
+       g_dbus_send_message(connection, reply);
+       creation_data->pending = NULL;
 
-       session_disconnect(session);
+       cleanup_session(session);
+       cleanup_creation_data(creation_data);
 
-       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+       return err;
 }
 
-static const GDBusMethodTable session_methods[] = {
-       { GDBUS_METHOD("Destroy", NULL, NULL, destroy_session) },
-       { GDBUS_METHOD("Connect", NULL, NULL, connect_session) },
-       { GDBUS_METHOD("Disconnect", NULL, NULL,
-                       disconnect_session ) },
-       { GDBUS_METHOD("Change",
-                       GDBUS_ARGS({ "name", "s" }, { "value", "v" }),
-                       NULL, change_session) },
-       { },
-};
-
 int __connman_session_create(DBusMessage *msg)
 {
        const char *owner, *notify_path;
        char *session_path = NULL;
        DBusMessageIter iter, array;
        struct connman_session *session = NULL;
-       struct session_info *info, *info_last;
-       enum connman_session_type type = CONNMAN_SESSION_TYPE_ANY;
-       GSList *allowed_bearers = NULL;
-       connman_bool_t allowed_bearers_valid = FALSE;
-       connman_bool_t type_valid = FALSE;
-       int err;
+       struct creation_data *creation_data = NULL;
+       bool user_allowed_bearers = false;
+       bool user_connection_type = false;
+       int err, i;
+       char *str;
 
        owner = dbus_message_get_sender(msg);
 
        DBG("owner %s", owner);
 
-       if (ecall_session != NULL && ecall_session->ecall == TRUE) {
+       if (ecall_session && ecall_session->ecall) {
                /*
                 * If there is an emergency call already going on,
                 * ignore session creation attempt
@@ -1593,6 +1435,14 @@ int __connman_session_create(DBusMessage *msg)
                goto err;
        }
 
+       creation_data = g_try_new0(struct creation_data, 1);
+       if (!creation_data) {
+               err = -ENOMEM;
+               goto err;
+       }
+
+       creation_data->pending = dbus_message_ref(msg);
+
        dbus_message_iter_init(msg, &iter);
        dbus_message_iter_recurse(&iter, &array);
 
@@ -1608,164 +1458,154 @@ int __connman_session_create(DBusMessage *msg)
 
                switch (dbus_message_iter_get_arg_type(&value)) {
                case DBUS_TYPE_ARRAY:
-                       if (g_str_equal(key, "AllowedBearers") == TRUE) {
-                               err = session_parse_allowed_bearers(&value,
-                                                       &allowed_bearers);
+                       if (g_str_equal(key, "AllowedBearers")) {
+                               err = parse_bearers(&value,
+                                       &creation_data->allowed_bearers);
                                if (err < 0)
                                        goto err;
 
-                               allowed_bearers_valid = TRUE;
+                               user_allowed_bearers = true;
                        } else {
-                               return -EINVAL;
+                               err = -EINVAL;
+                               goto err;
                        }
                        break;
                case DBUS_TYPE_STRING:
-                       if (g_str_equal(key, "ConnectionType") == TRUE) {
+                       if (g_str_equal(key, "ConnectionType")) {
                                dbus_message_iter_get_basic(&value, &val);
-                               type = string2type(val);
-                               type_valid = TRUE;
+                               creation_data->type =
+                                       connman_session_parse_connection_type(val);
+
+                               user_connection_type = true;
+                       } else if (g_str_equal(key, "AllowedInterface")) {
+                               dbus_message_iter_get_basic(&value, &val);
+                               creation_data->allowed_interface = g_strdup(val);
+                       } else {
+                               err = -EINVAL;
+                               goto err;
+                       }
+                       break;
+               case DBUS_TYPE_BOOLEAN:
+                       if (g_str_equal(key, "SourceIPRule")) {
+                               dbus_bool_t source_ip_rule;
+                               dbus_message_iter_get_basic(&value, &source_ip_rule);
+                               creation_data->source_ip_rule = source_ip_rule;
                        } else {
-                               return -EINVAL;
+                               err = -EINVAL;
+                               goto err;
                        }
+                       break;
+               default:
+                       err = -EINVAL;
+                       goto err;
                }
+
                dbus_message_iter_next(&array);
        }
 
+       /*
+        * If the user hasn't provided a configuration, we set
+        * the default configuration.
+        *
+        * For AllowedBearers this is '*', ...
+        */
+       if (!user_allowed_bearers) {
+               add_default_bearer_types(&creation_data->allowed_bearers);
+               if (!creation_data->allowed_bearers) {
+                       err = -ENOMEM;
+                       goto err;
+               }
+       }
+
+       /* ... and for ConnectionType it is 'any'. */
+       if (!user_connection_type)
+               creation_data->type = CONNMAN_SESSION_TYPE_ANY;
+
        dbus_message_iter_next(&iter);
        dbus_message_iter_get_basic(&iter, &notify_path);
 
-       if (notify_path == NULL) {
+       if (!notify_path) {
                err = -EINVAL;
                goto err;
        }
 
-       session_path = g_strdup_printf("/sessions%s", notify_path);
-       if (session_path == NULL) {
+       str = g_strdup(owner);
+       for (i = 0; str[i] != '\0'; i++)
+               if (str[i] == ':' || str[i] == '.')
+                       str[i] = '_';
+       session_path = g_strdup_printf("/sessions/%s%s", str, notify_path);
+       g_free(str);
+
+       if (!session_path) {
                err = -ENOMEM;
                goto err;
        }
 
        session = g_hash_table_lookup(session_hash, session_path);
-       if (session != NULL) {
+       if (session) {
+               g_free(session_path);
                session = NULL;
                err = -EEXIST;
                goto err;
        }
 
        session = g_try_new0(struct connman_session, 1);
-       if (session == NULL) {
+       if (!session) {
+               g_free(session_path);
                err = -ENOMEM;
                goto err;
        }
 
+       creation_data->session = session;
+       session->session_path = session_path;
+
        session->info = g_try_new0(struct session_info, 1);
-       if (session->info == NULL) {
+       if (!session->info) {
                err = -ENOMEM;
                goto err;
        }
 
        session->info_last = g_try_new0(struct session_info, 1);
-       if (session->info_last == NULL) {
+       if (!session->info_last) {
                err = -ENOMEM;
                goto err;
        }
 
-       info = session->info;
-       info_last = session->info_last;
-
        session->owner = g_strdup(owner);
-       session->session_path = session_path;
        session->notify_path = g_strdup(notify_path);
        session->notify_watch =
                g_dbus_add_disconnect_watch(connection, session->owner,
                                        owner_disconnect, session, NULL);
 
-       err = assign_policy_plugin(session);
-       if (err < 0)
-               goto err;
-       err = create_policy_config(session);
-       if (err < 0)
-               goto err;
-
-       if (session->policy_config->ecall == TRUE)
-               ecall_session = session;
-
-       info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
-       if (type_valid == FALSE)
-               type = CONNMAN_SESSION_TYPE_ANY;
-       info->config.type = apply_policy_on_type(
-                               session->policy_config->type,
-                               type);
-       info->config.priority = session->policy_config->priority;
-       info->config.roaming_policy = session->policy_config->roaming_policy;
-       info->entry = NULL;
-
-       if (allowed_bearers_valid == FALSE) {
-               allowed_bearers = connman_session_allowed_bearers_any();
-               if (allowed_bearers == NULL) {
-                       err = -ENOMEM;
-                       goto err;
-               }
-       }
-
-       err = apply_policy_on_bearers(
-                       session->policy_config->allowed_bearers,
-                       allowed_bearers,
-                       &info->config.allowed_bearers);
-       if (err < 0)
-               goto err;
-
-       g_hash_table_replace(session_hash, session->session_path, session);
-
-       DBG("add %s", session->session_path);
-
-       if (g_dbus_register_interface(connection, session->session_path,
-                                       CONNMAN_SESSION_INTERFACE,
-                                       session_methods, NULL,
-                                       NULL, session, NULL) == FALSE) {
-               connman_error("Failed to register %s", session->session_path);
-               g_hash_table_remove(session_hash, session->session_path);
-               session = NULL;
-
-               err = -EINVAL;
-               goto err;
-       }
-
-       g_dbus_send_reply(connection, msg,
-                               DBUS_TYPE_OBJECT_PATH, &session->session_path,
-                               DBUS_TYPE_INVALID);
-
-
-       populate_service_list(session);
-
-       info_last->state = info->state;
-       info_last->config.priority = info->config.priority;
-       info_last->config.roaming_policy = info->config.roaming_policy;
-       info_last->entry = info->entry;
-       info_last->config.allowed_bearers = info->config.allowed_bearers;
-
-       session->append_all = TRUE;
-
-       session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
+       err = create_policy_config(session, session_policy_config_cb,
+                                       creation_data);
+       if (err < 0 && err != -EINPROGRESS)
+               return err;
 
-       return 0;
+       return -EINPROGRESS;
 
 err:
        connman_error("Failed to create session");
 
-       if (session != NULL) {
-               if (session->info_last != NULL)
-                       g_free(session->info_last);
-               if (session->info != NULL)
-                       g_free(session->info);
-               g_free(session);
-       }
+       free_session(session);
+
+       cleanup_creation_data(creation_data);
+       return err;
+}
 
-       g_free(session_path);
+bool __connman_session_policy_autoconnect(enum connman_service_connect_reason reason)
+{
+       if (!policy || !policy->autoconnect)
+               return true;
 
-       connman_session_free_bearers(allowed_bearers);
+       return policy->autoconnect(reason);
+}
 
-       return err;
+void connman_session_destroy(struct connman_session *session)
+{
+       DBG("session %p", session);
+
+       session_disconnect(session);
 }
 
 int __connman_session_destroy(DBusMessage *msg)
@@ -1779,131 +1619,312 @@ int __connman_session_destroy(DBusMessage *msg)
 
        dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &session_path,
                                                        DBUS_TYPE_INVALID);
-       if (session_path == NULL)
+       if (!session_path)
                return -EINVAL;
 
        session = g_hash_table_lookup(session_hash, session_path);
-       if (session == NULL)
+       if (!session)
                return -EINVAL;
 
        if (g_strcmp0(owner, session->owner) != 0)
                return -EACCES;
 
-       session_disconnect(session);
+       connman_session_destroy(session);
 
        return 0;
 }
 
-connman_bool_t __connman_session_mode()
+int connman_session_connect(struct connman_service *service)
+{
+       DBG("service %p name %s", service, __connman_service_get_name(service));
+
+       return __connman_service_connect(service,
+                               CONNMAN_SERVICE_CONNECT_REASON_SESSION);
+}
+
+int connman_session_disconnect(struct connman_service *service)
+{
+       DBG("service %p", service);
+
+       return __connman_service_disconnect(service);
+}
+
+static enum connman_session_state service_to_session_state(
+                                       enum connman_service_state state)
+{
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_IDLE:
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+               break;
+       case CONNMAN_SERVICE_STATE_READY:
+               return CONNMAN_SESSION_STATE_CONNECTED;
+       case CONNMAN_SERVICE_STATE_ONLINE:
+               return CONNMAN_SESSION_STATE_ONLINE;
+       }
+
+       return CONNMAN_SESSION_STATE_DISCONNECTED;
+}
+
+static void update_session_state(struct connman_session *session)
 {
-       return sessionmode;
+       enum connman_service_state service_state;
+       enum connman_session_state state = CONNMAN_SESSION_STATE_DISCONNECTED;
+
+       if (session->service) {
+               service_state = __connman_service_get_state(session->service);
+               state = service_to_session_state(service_state);
+               session->info->state = state;
+       }
+       session->info->state = state;
+
+       DBG("session %p state %s", session, state2string(state));
+
+       update_firewall(session);
+       del_nat_rules(session);
+       update_routing_table(session);
+       add_nat_rules(session);
+
+       if (policy && policy->update_session_state)
+               policy->update_session_state(session, state);
+
+       session_notify(session);
 }
 
-void __connman_session_set_mode(connman_bool_t enable)
+static bool session_match_service(struct connman_session *session,
+                               struct connman_service *service)
 {
-       DBG("enable %d", enable);
+       enum connman_service_type bearer_type;
+       enum connman_service_type service_type;
+       enum connman_service_type current_service_type;
+       GSList *list;
+       char *ifname;
+
+       if (policy && policy->allowed)
+               return policy->allowed(session, service);
+
+       current_service_type = connman_service_get_type(session->service);
+
+       for (list = session->info->config.allowed_bearers; list; list = list->next) {
+               bearer_type = GPOINTER_TO_INT(list->data);
+               service_type = connman_service_get_type(service);
+               ifname = connman_service_get_interface(service);
+
+               if (bearer_type == current_service_type)
+                       return false;
+
+               if (bearer_type == service_type &&
+                       (session->info->config.allowed_interface == NULL ||
+                       !g_strcmp0(session->info->config.allowed_interface, "*") ||
+                       !g_strcmp0(session->info->config.allowed_interface, ifname))) {
+                               g_free(ifname);
+                               return true;
+               }
+               g_free(ifname);
+       }
+
+       return false;
+}
 
-       if (sessionmode != enable) {
-               sessionmode = enable;
+static bool is_session_connected(struct connman_session *session,
+                               enum connman_service_state state)
 
-               connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH,
-                               CONNMAN_MANAGER_INTERFACE, "SessionMode",
-                               DBUS_TYPE_BOOLEAN, &sessionmode);
+{
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_IDLE:
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+                         break;
+       case CONNMAN_SERVICE_STATE_READY:
+               if (session->info->config.type == CONNMAN_SESSION_TYPE_INTERNET)
+                       return false;
+               /* fall through */
+       case CONNMAN_SERVICE_STATE_ONLINE:
+               return true;
        }
 
-       if (sessionmode == TRUE)
-               __connman_service_disconnect_all();
+       return false;
 }
 
-static void service_add(struct connman_service *service,
-                       const char *name)
+static void session_activate(struct connman_session *session)
 {
        GHashTableIter iter;
-       GSequenceIter *iter_service_list;
        gpointer key, value;
-       struct connman_session *session;
-       struct service_entry *entry;
 
-       DBG("service %p", service);
+       if (!service_hash)
+               return;
 
-       g_hash_table_iter_init(&iter, session_hash);
+       if (policy && policy->get_service_for_session) {
+               struct connman_service *service;
+               struct connman_service_info *info;
+               GSList *service_list = NULL;
+               enum connman_service_state state = CONNMAN_SESSION_STATE_DISCONNECTED;
 
-       while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
-               session = value;
+               g_hash_table_iter_init(&iter, service_hash);
 
-               if (service_match(session, service) == FALSE)
-                       continue;
+               while (g_hash_table_iter_next(&iter, &key, &value)) {
+                       struct connman_service_info *info = value;
+                       state = __connman_service_get_state(info->service);
 
-               entry = create_service_entry(service, name,
-                                               CONNMAN_SERVICE_STATE_IDLE);
-               if (entry == NULL)
-                       continue;
+                       if (is_session_connected(session, state))
+                               service_list = g_slist_prepend(service_list,
+                                                              info->service);
+               }
+
+               service_list = g_slist_reverse(service_list);
+               service = policy->get_service_for_session(session, service_list);
+
+               if (service) {
+                       info = g_hash_table_lookup(service_hash, service);
+                       DBG("session %p add service %p", session, info->service);
 
-               iter_service_list =
-                       g_sequence_insert_sorted(session->service_list,
-                                                       entry, sort_services,
+                       info->sessions = g_slist_prepend(info->sessions,
                                                        session);
+                       session->service = info->service;
+                       update_session_state(session);
+               }
+
+               g_slist_free(service_list);
+               return;
+       }
 
-               g_hash_table_replace(session->service_hash, service,
-                                       iter_service_list);
+       g_hash_table_iter_init(&iter, service_hash);
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               struct connman_service_info *info = value;
+               enum connman_service_state state;
 
-               session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
+               state = __connman_service_get_state(info->service);
+
+               if (is_session_connected(session, state) &&
+                               session_match_service(session, info->service)) {
+                       DBG("session %p add service %p", session, info->service);
+
+                       info->sessions = g_slist_prepend(info->sessions,
+                                                       session);
+                       session->service = info->service;
+                       update_session_state(session);
+
+                       return;
+               }
        }
+
+       session_notify(session);
 }
 
-static void service_remove(struct connman_service *service)
+static void session_deactivate(struct connman_session *session)
 {
+       struct connman_service_info *info;
 
+       if (!service_hash)
+               return;
+
+       if (!session->service)
+               return;
+
+       info = g_hash_table_lookup(service_hash, session->service);
+       if (!info)
+               return;
+
+       info->sessions = g_slist_remove(info->sessions, session);
+       session->service = NULL;
+
+       session->info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
+}
+
+static void handle_service_state_online(struct connman_service *service,
+                                       enum connman_service_state state,
+                                       struct connman_service_info *info)
+{
        GHashTableIter iter;
        gpointer key, value;
-       struct connman_session *session;
-       struct session_info *info;
-
-       DBG("service %p", service);
 
        g_hash_table_iter_init(&iter, session_hash);
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               struct connman_session *session = value;
+               bool connected;
 
-       while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
-               GSequenceIter *seq_iter;
-               session = value;
-               info = session->info;
+               connected = is_session_connected(session, state);
+
+               if (session->service == service) {
+                       if (!connected) {
+                               DBG("session %p remove service %p", session, service);
+                               info->sessions = g_slist_remove(info->sessions,
+                                                       session);
+                               session->service = NULL;
+                               update_session_state(session);
+                       }
+               } else if (connected && session_match_service(session, service)) {
+                       DBG("session %p add service %p", session, service);
+
+                       info->sessions = g_slist_prepend(info->sessions,
+                                                       session);
+                       session->service = service;
+                       update_session_state(session);
+               }
+       }
+}
+
+static void handle_service_state_offline(struct connman_service *service,
+                                       struct connman_service_info *info)
+{
+       GSList *list;
 
-               seq_iter = g_hash_table_lookup(session->service_hash, service);
-               if (seq_iter == NULL)
+       for (list = info->sessions; list; list = list->next) {
+               struct connman_session *session = list->data;
+
+               if (session->service != service) {
+                       connman_warn("session %p should have session %p assigned",
+                                       session, service);
                        continue;
+               }
 
-               g_sequence_remove(seq_iter);
+               DBG("session %p remove service %p", session, service);
 
-               if (info->entry != NULL && info->entry->service == service)
-                       info->entry = NULL;
-               session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
+               session->service = NULL;
+               update_session_state(session);
+               session_activate(session);
        }
 }
 
 static void service_state_changed(struct connman_service *service,
-                                       enum connman_service_state state)
+                               enum connman_service_state state)
 {
-       GHashTableIter iter;
-       gpointer key, value;
+       struct connman_service_info *info;
 
        DBG("service %p state %d", service, state);
 
-       g_hash_table_iter_init(&iter, session_hash);
+       info = g_hash_table_lookup(service_hash, service);
 
-       while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
-               struct connman_session *session = value;
-               GSequenceIter *service_iter;
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_IDLE:
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+               if (!info)
+                       return;
+
+               handle_service_state_offline(service, info);
 
-               service_iter = g_hash_table_lookup(session->service_hash, service);
-               if (service_iter != NULL) {
-                       struct service_entry *entry;
+               g_hash_table_remove(service_hash, service);
 
-                       entry = g_sequence_get(service_iter);
-                       entry->state = state;
+               return;
+       case CONNMAN_SERVICE_STATE_READY:
+       case CONNMAN_SERVICE_STATE_ONLINE:
+               if (!info) {
+                       info = g_new0(struct connman_service_info, 1);
+                       g_hash_table_replace(service_hash, service, info);
                }
 
-               session_changed(session,
-                               CONNMAN_SESSION_TRIGGER_SERVICE);
+               info->service = service;
+               handle_service_state_online(service, state, info);
        }
 }
 
@@ -1922,14 +1943,16 @@ static void ipconfig_changed(struct connman_service *service,
 
        g_hash_table_iter_init(&iter, session_hash);
 
-       while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
                session = value;
                info = session->info;
 
                if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED)
                        continue;
 
-               if (info->entry != NULL && info->entry->service == service) {
+               if (session->service && session->service == service) {
+                       update_session_state(session);
+
                        if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
                                ipconfig_ipv4_changed(session);
                        else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
@@ -1940,8 +1963,6 @@ static void ipconfig_changed(struct connman_service *service,
 
 static struct connman_notifier session_notifier = {
        .name                   = "session",
-       .service_add            = service_add,
-       .service_remove         = service_remove,
        .service_state_changed  = service_state_changed,
        .ipconfig_changed       = ipconfig_changed,
 };
@@ -1953,7 +1974,7 @@ int __connman_session_init(void)
        DBG("");
 
        connection = connman_dbus_get_connection();
-       if (connection == NULL)
+       if (!connection)
                return -1;
 
        err = connman_notifier_register(&session_notifier);
@@ -1965,7 +1986,8 @@ int __connman_session_init(void)
        session_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                NULL, cleanup_session);
 
-       sessionmode = FALSE;
+       service_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                                               NULL, cleanup_service);
        return 0;
 }
 
@@ -1973,7 +1995,7 @@ void __connman_session_cleanup(void)
 {
        DBG("");
 
-       if (connection == NULL)
+       if (!connection)
                return;
 
        connman_notifier_unregister(&session_notifier);
@@ -1981,6 +2003,8 @@ void __connman_session_cleanup(void)
        g_hash_table_foreach(session_hash, release_session, NULL);
        g_hash_table_destroy(session_hash);
        session_hash = NULL;
+       g_hash_table_destroy(service_hash);
+       service_hash = NULL;
 
        dbus_connection_unref(connection);
 }