blob: 9e3c55941ee62e23e08da9a902e851b4c1ed45db [file] [log] [blame]
/*
*
* Connection Manager
*
* 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
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <gdbus.h>
#include <connman/session.h>
#include "connman.h"
static DBusConnection *connection;
static GHashTable *session_hash;
static GHashTable *service_hash;
static struct connman_session *ecall_session;
static uint32_t session_mark = 256;
struct session_info {
struct connman_session_config config;
enum connman_session_state state;
};
struct connman_session {
char *owner;
char *session_path;
char *notify_path;
guint notify_watch;
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;
};
struct connman_service_info {
struct connman_service *service;
GSList *sessions;
};
struct fw_snat {
GSList *sessions;
int id;
int index;
struct firewall_context *fw;
};
GSList *fw_snat_list;
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 void cleanup_service(gpointer data)
{
struct connman_service_info *info = data;
g_slist_free(info->sessions);
g_free(info);
}
static const char *state2string(enum connman_session_state state)
{
switch (state) {
case CONNMAN_SESSION_STATE_DISCONNECTED:
return "disconnected";
case CONNMAN_SESSION_STATE_CONNECTED:
return "connected";
case CONNMAN_SESSION_STATE_ONLINE:
return "online";
}
return NULL;
}
static const char *type2string(enum connman_session_type type)
{
switch (type) {
case CONNMAN_SESSION_TYPE_UNKNOWN:
return "";
case CONNMAN_SESSION_TYPE_ANY:
return "any";
case CONNMAN_SESSION_TYPE_LOCAL:
return "local";
case CONNMAN_SESSION_TYPE_INTERNET:
return "internet";
}
return NULL;
}
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;
if (g_strcmp0(type, "local") == 0)
return CONNMAN_SESSION_TYPE_LOCAL;
else if (g_strcmp0(type, "internet") == 0)
return CONNMAN_SESSION_TYPE_INTERNET;
return CONNMAN_SESSION_TYPE_UNKNOWN;
}
static int bearer2service(const char *bearer, enum connman_service_type *type)
{
if (g_strcmp0(bearer, "ethernet") == 0)
*type = CONNMAN_SERVICE_TYPE_ETHERNET;
else if (g_strcmp0(bearer, "wifi") == 0)
*type = CONNMAN_SERVICE_TYPE_WIFI;
else if (g_strcmp0(bearer, "gadget") == 0)
*type = CONNMAN_SERVICE_TYPE_GADGET;
else if (g_strcmp0(bearer, "bluetooth") == 0)
*type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
else if (g_strcmp0(bearer, "cellular") == 0)
*type = CONNMAN_SERVICE_TYPE_CELLULAR;
else if (g_strcmp0(bearer, "vpn") == 0)
*type = CONNMAN_SERVICE_TYPE_VPN;
else
return -EINVAL;
return 0;
}
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_BLUETOOTH:
return "bluetooth";
case CONNMAN_SERVICE_TYPE_CELLULAR:
return "cellular";
case CONNMAN_SERVICE_TYPE_VPN:
return "vpn";
case CONNMAN_SERVICE_TYPE_SYSTEM:
case CONNMAN_SERVICE_TYPE_GPS:
case CONNMAN_SERVICE_TYPE_P2P:
case CONNMAN_SERVICE_TYPE_UNKNOWN:
return "";
}
return "";
}
static struct fw_snat *fw_snat_lookup(int index)
{
struct fw_snat *fw_snat;
GSList *list;
for (list = fw_snat_list; list; list = list->next) {
fw_snat = list->data;
if (fw_snat->index == index)
return fw_snat;
}
return NULL;
}
static int fw_snat_create(struct connman_session *session,
int index, const char *ifname, const char *addr)
{
struct fw_snat *fw_snat;
int err;
fw_snat = g_new0(struct fw_snat, 1);
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 fw_snat_ref(struct connman_session *session,
struct fw_snat *fw_snat)
{
if (g_slist_find(fw_snat->sessions, session))
return;
fw_snat->sessions = g_slist_prepend(fw_snat->sessions, session);
}
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;
fw_snat_list = g_slist_remove(fw_snat_list, fw_snat);
__connman_firewall_disable_snat(fw_snat->fw);
__connman_firewall_destroy(fw_snat->fw);
g_free(fw_snat);
}
static int init_firewall_session(struct connman_session *session)
{
struct firewall_context *fw;
int err;
struct connman_ipconfig *ipconfig = NULL;
const char *addr = NULL;
if (session->policy_config->id_type == CONNMAN_SESSION_ID_TYPE_UNKNOWN &&
!session->info->config.source_ip_rule)
return 0;
DBG("");
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;
}
fw = __connman_firewall_create();
if (!fw)
return -ENOMEM;
if (session->info->config.source_ip_rule && ipconfig) {
addr = __connman_ipconfig_get_local(ipconfig);
}
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 void cleanup_firewall_session(struct connman_session *session)
{
if (!session->fw)
return;
__connman_firewall_disable_marking(session->fw);
__connman_firewall_disable_snat(session->fw);
__connman_firewall_destroy(session->fw);
session->fw = NULL;
}
static int init_routing_table(struct connman_session *session)
{
int err;
if (session->policy_config->id_type == CONNMAN_SESSION_ID_TYPE_UNKNOWN &&
!session->info->config.source_ip_rule)
return 0;
if (!session->service)
return 0;
DBG("");
err = __connman_inet_add_fwmark_rule(session->mark,
AF_INET, session->mark);
if (err < 0)
return err;
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;
return err;
}
static void del_default_route(struct connman_session *session)
{
if (!session->gateway)
return;
DBG("index %d routing table %d default gateway %s",
session->index, session->mark, session->gateway);
__connman_inet_del_default_from_table(session->mark,
session->index, session->gateway);
g_free(session->gateway);
session->gateway = NULL;
session->index = -1;
}
static void add_default_route(struct connman_session *session)
{
struct connman_ipconfig *ipconfig;
int err;
struct in_addr addr = { INADDR_ANY };
if (!session->service)
return;
ipconfig = __connman_service_get_ip4config(session->service);
session->index = __connman_ipconfig_get_index(ipconfig);
session->gateway = g_strdup(__connman_ipconfig_get_gateway(ipconfig));
if (!session->gateway)
session->gateway = g_strdup(inet_ntoa(addr));
DBG("index %d routing table %d default gateway %s",
session->index, session->mark, session->gateway);
err = __connman_inet_add_default_to_table(session->mark,
session->index, session->gateway);
if (err < 0)
DBG("session %p %s", session, strerror(-err));
}
static void del_nat_rules(struct connman_session *session)
{
struct fw_snat *fw_snat;
if (!session->snat_enabled)
return;
session->snat_enabled = false;
fw_snat = fw_snat_lookup(session->index);
if (!fw_snat)
return;
fw_snat_unref(session, fw_snat);
}
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;
if (!session->service)
return;
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 (!addr)
return;
session->snat_enabled = true;
fw_snat = fw_snat_lookup(index);
if (fw_snat) {
fw_snat_ref(session, fw_snat);
return;
}
err = fw_snat_create(session, index, ifname, addr);
if (err < 0) {
DBG("failed to add SNAT rule");
session->snat_enabled = false;
}
g_free(ifname);
}
uint32_t connman_session_firewall_get_fwmark(struct connman_session *session)
{
return session->mark;
}
static void cleanup_routing_table(struct connman_session *session)
{
DBG("");
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;
}
del_default_route(session);
}
static void update_firewall(struct connman_session *session)
{
cleanup_firewall_session(session);
init_firewall_session(session);
}
static void update_routing_table(struct connman_session *session)
{
cleanup_routing_table(session);
init_routing_table(session);
add_default_route(session);
}
static void cleanup_nat_rules(struct connman_session *session)
{
del_nat_rules(session);
}
static void destroy_policy_config(struct connman_session *session)
{
if (!policy) {
g_free(session->policy_config);
return;
}
policy->destroy(session);
}
static void free_session(struct connman_session *session)
{
if (!session)
return;
if (session->notify_watch > 0)
g_dbus_remove_watch(connection, session->notify_watch);
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 void set_active_session(struct connman_session *session, bool enable)
{
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 void cleanup_session(gpointer user_data)
{
struct connman_session *session = user_data;
DBG("remove %s", session->session_path);
cleanup_nat_rules(session);
cleanup_routing_table(session);
cleanup_firewall_session(session);
if (session->active)
set_active_session(session, false);
session_deactivate(session);
update_session_state(session);
g_slist_free(session->user_allowed_bearers);
g_free(session->user_allowed_interface);
free_session(session);
}
struct creation_data {
DBusMessage *pending;
struct connman_session *session;
/* user config */
enum connman_session_type type;
GSList *allowed_bearers;
char *allowed_interface;
bool source_ip_rule;
};
static void cleanup_creation_data(struct creation_data *creation_data)
{
if (!creation_data)
return;
if (creation_data->pending)
dbus_message_unref(creation_data->pending);
g_slist_free(creation_data->allowed_bearers);
g_free(creation_data->allowed_interface);
g_free(creation_data);
}
static int create_policy_config(struct connman_session *session,
connman_session_config_func_t cb,
struct creation_data *creation_data)
{
struct connman_session_config *config;
if (!policy) {
config = connman_session_create_default_config();
if (!config) {
free_session(session);
cleanup_creation_data(creation_data);
return -ENOMEM;
}
return cb(session, config, creation_data, 0);
}
return policy->create(session, cb, creation_data);
}
int connman_session_policy_register(struct connman_session_policy *plugin)
{
if (policy)
return -EINVAL;
if (!plugin->create || !plugin->destroy)
return -EINVAL;
DBG("name %s", plugin->name);
policy = plugin;
return 0;
}
void connman_session_policy_unregister(struct connman_session_policy *plugin)
{
if (plugin != policy)
return;
DBG("name %s", policy->name);
policy = NULL;
}
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]));
}
void connman_session_set_default_config(struct connman_session_config *config)
{
config->id_type = CONNMAN_SESSION_ID_TYPE_UNKNOWN;
g_free(config->id);
config->id = NULL;
config->priority = FALSE;
config->roaming_policy = CONNMAN_SESSION_ROAMING_POLICY_DEFAULT;
config->type = CONNMAN_SESSION_TYPE_ANY;
config->ecall = FALSE;
g_slist_free(config->allowed_bearers);
config->allowed_bearers = NULL;
add_default_bearer_types(&config->allowed_bearers);
}
struct connman_session_config *connman_session_create_default_config(void)
{
struct connman_session_config *config;
config = g_new0(struct connman_session_config, 1);
connman_session_set_default_config(config);
return config;
}
static enum connman_session_type apply_policy_on_type(
enum connman_session_type policy,
enum connman_session_type type)
{
if (type == CONNMAN_SESSION_TYPE_UNKNOWN)
return CONNMAN_SESSION_TYPE_UNKNOWN;
if (policy == CONNMAN_SESSION_TYPE_ANY)
return type;
if (policy == CONNMAN_SESSION_TYPE_LOCAL)
return CONNMAN_SESSION_TYPE_LOCAL;
return CONNMAN_SESSION_TYPE_INTERNET;
}
int connman_session_parse_bearers(const char *token, GSList **list)
{
enum connman_service_type bearer;
int err;
if (g_strcmp0(token, "") == 0)
return 0;
if (g_strcmp0(token, "*") == 0) {
add_default_bearer_types(list);
return 0;
}
err = bearer2service(token, &bearer);
if (err < 0)
return err;
*list = g_slist_append(*list, GINT_TO_POINTER(bearer));
return 0;
}
static int parse_bearers(DBusMessageIter *iter, GSList **list)
{
DBusMessageIter array;
int type, err;
dbus_message_iter_recurse(iter, &array);
*list = NULL;
while ((type = dbus_message_iter_get_arg_type(&array)) !=
DBUS_TYPE_INVALID) {
char *bearer_name = NULL;
if (type != DBUS_TYPE_STRING) {
g_slist_free(*list);
*list = NULL;
return -EINVAL;
}
dbus_message_iter_get_basic(&array, &bearer_name);
err = connman_session_parse_bearers(bearer_name, list);
if (err < 0) {
g_slist_free(*list);
*list = NULL;
return err;
}
dbus_message_iter_next(&array);
}
return 0;
}
static void filter_bearer(GSList *bearers,
enum connman_service_type policy,
GSList **list)
{
enum connman_service_type bearer;
GSList *it;
if (!bearers)
return;
for (it = bearers; it; it = it->next) {
bearer = GPOINTER_TO_INT(it->data);
if (policy != bearer)
continue;
*list = g_slist_append(*list, GINT_TO_POINTER(bearer));
return;
}
}
static void apply_policy_on_bearers(GSList *policy_bearers, GSList *bearers,
GSList **list)
{
enum connman_service_type policy_bearer;
GSList *it;
*list = NULL;
for (it = policy_bearers; it; it = it->next) {
policy_bearer = GPOINTER_TO_INT(it->data);
filter_bearer(bearers, policy_bearer, list);
}
}
static char * apply_policy_on_interface(const char *policy_interface,
const char *user_interface)
{
if (policy_interface)
return g_strdup(policy_interface);
else if (user_interface)
return g_strdup(user_interface);
else
return NULL;
}
const char *connman_session_get_owner(struct connman_session *session)
{
return session->owner;
}
static void append_allowed_bearers(DBusMessageIter *iter, void *user_data)
{
struct session_info *info = user_data;
GSList *list;
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);
if (!name)
name = "";
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&name);
}
}
static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data)
{
struct connman_service *service = user_data;
struct connman_ipconfig *ipconfig_ipv4;
if (!service)
return;
if (!__connman_service_is_connected_state(service,
CONNMAN_IPCONFIG_TYPE_IPV4))
return;
ipconfig_ipv4 = __connman_service_get_ip4config(service);
if (!ipconfig_ipv4)
return;
__connman_ipconfig_append_ipv4(ipconfig_ipv4, iter);
}
static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
{
struct connman_service *service = user_data;
struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6;
if (!service)
return;
if (!__connman_service_is_connected_state(service,
CONNMAN_IPCONFIG_TYPE_IPV6))
return;
ipconfig_ipv4 = __connman_service_get_ip4config(service);
ipconfig_ipv6 = __connman_service_get_ip6config(service);
if (!ipconfig_ipv6)
return;
__connman_ipconfig_append_ipv6(ipconfig_ipv6, iter, ipconfig_ipv4);
}
static void append_notify(DBusMessageIter *dict,
struct connman_session *session)
{
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;
if (session->append_all || info->state != info_last->state) {
const char *state = state2string(info->state);
connman_dbus_dict_append_basic(dict, "State",
DBUS_TYPE_STRING,
&state);
info_last->state = info->state;
}
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);
ifname = connman_inet_ifname(idx);
if (!ifname)
ifname = g_strdup("");
type = connman_service_get_type(service);
bearer = service2bearer(type);
} else {
service = NULL;
name = "";
ifname = g_strdup("");
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);
g_free(ifname);
session->service_last = session->service;
}
if (session->append_all ||
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 ||
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;
}
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;
}
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 bool 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)
return true;
if (info->state != info_last->state)
return true;
if (session->service != session->service_last &&
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 ||
info->config.allowed_interface != info_last->config.allowed_interface ||
info->config.source_ip_rule != info_last->config.source_ip_rule)
return true;
return false;
}
static gboolean session_notify(gpointer user_data)
{
struct connman_session *session = user_data;
DBusMessage *msg;
DBusMessageIter array, dict;
if (!compute_notifiable_changes(session))
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)
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);
g_dbus_send_message(connection, msg);
return FALSE;
}
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);
}
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);
}
int connman_session_config_update(struct connman_session *session)
{
struct session_info *info = session->info;
GSList *allowed_bearers;
char *allowed_interface;
int err;
DBG("session %p", session);
/*
* We update all configuration even though only one entry
* might have changed. We can still optimize this later.
*/
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;
}
session->id_type = session->policy_config->id_type;
}
apply_policy_on_bearers(
session->policy_config->allowed_bearers,
session->user_allowed_bearers,
&allowed_bearers);
allowed_interface = apply_policy_on_interface(
session->policy_config->allowed_interface,
session->user_allowed_interface);
if (session->active)
set_active_session(session, false);
session->active = false;
session_deactivate(session);
g_slist_free(info->config.allowed_bearers);
info->config.allowed_bearers = allowed_bearers;
g_free(info->config.allowed_interface);
info->config.allowed_interface = allowed_interface;
session_activate(session);
info->config.type = apply_policy_on_type(
session->policy_config->type,
info->config.type);
info->config.roaming_policy = session->policy_config->roaming_policy;
info->config.ecall = session->policy_config->ecall;
if (info->config.ecall)
ecall_session = session;
info->config.priority = session->policy_config->priority;
session_notify(session);
return 0;
}
static DBusMessage *connect_session(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct connman_session *session = user_data;
DBG("session %p", session);
if (ecall_session) {
if (ecall_session->ecall && ecall_session != session)
return __connman_error_failed(msg, EBUSY);
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);
}
static DBusMessage *disconnect_session(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct connman_session *session = user_data;
DBG("session %p", session);
if (ecall_session) {
if (ecall_session->ecall && ecall_session != session)
return __connman_error_failed(msg, EBUSY);
session->ecall = false;
}
if (session->active) {
session->active = false;
set_active_session(session, false);
}
session_deactivate(session);
update_session_state(session);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *change_session(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct connman_session *session = user_data;
struct session_info *info = session->info;
DBusMessageIter iter, value;
const char *name;
const char *val;
GSList *allowed_bearers;
int err;
DBG("session %p", session);
if (!dbus_message_iter_init(msg, &iter))
return __connman_error_invalid_arguments(msg);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &name);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_recurse(&iter, &value);
switch (dbus_message_iter_get_arg_type(&value)) {
case DBUS_TYPE_ARRAY:
if (g_str_equal(name, "AllowedBearers")) {
err = parse_bearers(&value, &allowed_bearers);
if (err < 0)
return __connman_error_failed(msg, -err);
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,
session->user_allowed_bearers,
&info->config.allowed_bearers);
session_activate(session);
} else {
goto err;
}
break;
case DBUS_TYPE_STRING:
if (g_str_equal(name, "ConnectionType")) {
dbus_message_iter_get_basic(&value, &val);
info->config.type = apply_policy_on_type(
session->policy_config->type,
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;
}
break;
default:
goto err;
}
session_notify(session);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
err:
return __connman_error_invalid_arguments(msg);
}
static void release_session(gpointer key, gpointer value, gpointer user_data)
{
struct connman_session *session = value;
DBusMessage *message;
DBG("owner %s path %s", session->owner, session->notify_path);
if (session->notify_watch > 0)
g_dbus_remove_watch(connection, session->notify_watch);
g_dbus_unregister_interface(connection, session->session_path,
CONNMAN_SESSION_INTERFACE);
message = dbus_message_new_method_call(session->owner,
session->notify_path,
CONNMAN_NOTIFICATION_INTERFACE,
"Release");
if (!message)
return;
dbus_message_set_no_reply(message, TRUE);
g_dbus_send_message(connection, message);
}
static int session_disconnect(struct connman_session *session)
{
DBG("session %p, %s", session, session->owner);
if (session->notify_watch > 0)
g_dbus_remove_watch(connection, session->notify_watch);
g_dbus_unregister_interface(connection, session->session_path,
CONNMAN_SESSION_INTERFACE);
g_hash_table_remove(session_hash, session->session_path);
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;
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;
session->append_all = true;
cleanup_creation_data(creation_data);
session_activate(session);
return 0;
err:
reply = __connman_error_failed(creation_data->pending, -err);
g_dbus_send_message(connection, reply);
creation_data->pending = NULL;
cleanup_session(session);
cleanup_creation_data(creation_data);
return err;
}
int __connman_session_create(DBusMessage *msg)
{
const char *owner, *notify_path;
char *session_path = NULL;
DBusMessageIter iter, array;
struct connman_session *session = NULL;
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 && ecall_session->ecall) {
/*
* If there is an emergency call already going on,
* ignore session creation attempt
*/
err = -EBUSY;
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);
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry, value;
const char *key, *val;
dbus_message_iter_recurse(&array, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
switch (dbus_message_iter_get_arg_type(&value)) {
case DBUS_TYPE_ARRAY:
if (g_str_equal(key, "AllowedBearers")) {
err = parse_bearers(&value,
&creation_data->allowed_bearers);
if (err < 0)
goto err;
user_allowed_bearers = true;
} else {
err = -EINVAL;
goto err;
}
break;
case DBUS_TYPE_STRING:
if (g_str_equal(key, "ConnectionType")) {
dbus_message_iter_get_basic(&value, &val);
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 {
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) {
err = -EINVAL;
goto err;
}
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) {
g_free(session_path);
session = NULL;
err = -EEXIST;
goto err;
}
session = g_try_new0(struct connman_session, 1);
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) {
err = -ENOMEM;
goto err;
}
session->info_last = g_try_new0(struct session_info, 1);
if (!session->info_last) {
err = -ENOMEM;
goto err;
}
session->owner = g_strdup(owner);
session->notify_path = g_strdup(notify_path);
session->notify_watch =
g_dbus_add_disconnect_watch(connection, session->owner,
owner_disconnect, session, NULL);
err = create_policy_config(session, session_policy_config_cb,
creation_data);
if (err < 0 && err != -EINPROGRESS)
return err;
return -EINPROGRESS;
err:
connman_error("Failed to create session");
free_session(session);
cleanup_creation_data(creation_data);
return err;
}
bool __connman_session_policy_autoconnect(enum connman_service_connect_reason reason)
{
if (!policy || !policy->autoconnect)
return true;
return policy->autoconnect(reason);
}
void connman_session_destroy(struct connman_session *session)
{
DBG("session %p", session);
session_disconnect(session);
}
int __connman_session_destroy(DBusMessage *msg)
{
const char *owner, *session_path;
struct connman_session *session;
owner = dbus_message_get_sender(msg);
DBG("owner %s", owner);
dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &session_path,
DBUS_TYPE_INVALID);
if (!session_path)
return -EINVAL;
session = g_hash_table_lookup(session_hash, session_path);
if (!session)
return -EINVAL;
if (g_strcmp0(owner, session->owner) != 0)
return -EACCES;
connman_session_destroy(session);
return 0;
}
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)
{
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);
}
static bool session_match_service(struct connman_session *session,
struct connman_service *service)
{
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;
}
static bool is_session_connected(struct connman_session *session,
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:
if (session->info->config.type == CONNMAN_SESSION_TYPE_INTERNET)
return false;
/* fall through */
case CONNMAN_SERVICE_STATE_ONLINE:
return true;
}
return false;
}
static void session_activate(struct connman_session *session)
{
GHashTableIter iter;
gpointer key, value;
if (!service_hash)
return;
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;
g_hash_table_iter_init(&iter, service_hash);
while (g_hash_table_iter_next(&iter, &key, &value)) {
struct connman_service_info *info = value;
state = __connman_service_get_state(info->service);
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);
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_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;
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 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;
g_hash_table_iter_init(&iter, session_hash);
while (g_hash_table_iter_next(&iter, &key, &value)) {
struct connman_session *session = value;
bool connected;
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;
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;
}
DBG("session %p remove service %p", session, 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)
{
struct connman_service_info *info;
DBG("service %p state %d", service, state);
info = g_hash_table_lookup(service_hash, service);
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);
g_hash_table_remove(service_hash, service);
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);
}
info->service = service;
handle_service_state_online(service, state, info);
}
}
static void ipconfig_changed(struct connman_service *service,
struct connman_ipconfig *ipconfig)
{
GHashTableIter iter;
gpointer key, value;
struct connman_session *session;
struct session_info *info;
enum connman_ipconfig_type type;
DBG("service %p ipconfig %p", service, ipconfig);
type = __connman_ipconfig_get_config_type(ipconfig);
g_hash_table_iter_init(&iter, session_hash);
while (g_hash_table_iter_next(&iter, &key, &value)) {
session = value;
info = session->info;
if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED)
continue;
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)
ipconfig_ipv6_changed(session);
}
}
}
static struct connman_notifier session_notifier = {
.name = "session",
.service_state_changed = service_state_changed,
.ipconfig_changed = ipconfig_changed,
};
int __connman_session_init(void)
{
int err;
DBG("");
connection = connman_dbus_get_connection();
if (!connection)
return -1;
err = connman_notifier_register(&session_notifier);
if (err < 0) {
dbus_connection_unref(connection);
return err;
}
session_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, cleanup_session);
service_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, cleanup_service);
return 0;
}
void __connman_session_cleanup(void)
{
DBG("");
if (!connection)
return;
connman_notifier_unregister(&session_notifier);
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);
}