blob: 164981d08582f909760d554b513b6ba06c1874a2 [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2009 Intel Corporation. All rights reserved.
*
* 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 <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/route.h>
#include <gdbus.h>
#include "connman.h"
#define _DBG_CONNECTION(fmt, arg...) DBG(DBG_CONNECTION, fmt, ## arg)
#define ACTIVE_METRIC 0
#define INACTIVE_METRIC 10
#define INVALID_METRIC -1
struct gateway_data {
struct connman_element element; /* CONNECTION element */
char *gateway; /* gateway address */
unsigned int order; /* sorted priority order */
gboolean active; /* TRUE if system told us installed */
short metric; /* metric used for this gateway */
};
static GSList *gateway_list = NULL;
static connman_bool_t checkorder(const struct gateway_data *data,
const char *where)
{
if (data->order == CONNMAN_SERVICE_ORDER_MAX) {
connman_error("%s: bad order, gw %s index %d active %d",
where, data->gateway, data->element.index, data->active);
return FALSE;
} else
return TRUE;
}
static struct gateway_data *find_gateway(int index, const char *gateway)
{
GSList *list;
if (gateway == NULL) /* TODO(sleffler) needed? */
return NULL;
for (list = gateway_list; list; list = list->next) {
struct gateway_data *data = list->data;
if (data->element.index == index &&
g_strcmp0(data->gateway, gateway) == 0)
return data;
}
return NULL;
}
static void update_order(void)
{
GSList *list;
for (list = gateway_list; list != NULL; list = list->next) {
struct gateway_data *data = list->data;
struct connman_service *service;
service = __connman_element_get_service(&data->element);
data->order = __connman_service_get_order(service);
(void) checkorder(data, __func__);
}
}
static void add_gateway(struct gateway_data *data)
{
/* TODO(sleffler) check return value */
gateway_list = g_slist_append(gateway_list, data);
update_order();
}
static void connection_newroute(void *user_data, int index, int scope,
const char *dst, const char *gateway)
{
struct gateway_data *data;
_DBG_CONNECTION("index %d scope %d dst %s gateway %s",
index, scope, dst, gateway);
if (scope != RT_SCOPE_UNIVERSE || g_strcmp0(dst, "0.0.0.0") != 0)
return;
data = find_gateway(index, gateway);
if (data != NULL)
data->active = TRUE;
}
static void set_gateway_and_metric(struct gateway_data *data, short metric)
{
struct connman_element *element = &data->element;
short old_metric = data->metric;
_DBG_CONNECTION("gateway %s metric %d", data->gateway, metric);
/* TODO(sleffler) should we ignore this error? (do elsewhere) */
data->metric = metric;
if (connman_inet_set_gateway(element->index,
inet_addr(data->gateway),
data->metric) < 0) {
/* NB: connman_inet_set_gateway logs a msg */
return;
}
/* Remove the gateway with the old metric - errors ignored */
if (old_metric != data->metric && old_metric != INVALID_METRIC)
connman_inet_del_gateway(element->index,
inet_addr(data->gateway),
old_metric);
}
static void set_default_gateway(struct gateway_data *data)
{
struct connman_element *element = &data->element;
struct connman_service *service;
set_gateway_and_metric(data, ACTIVE_METRIC);
/*
* Mark associated service as the ``active'' one; this causes,
* for example, dns proxy requests to go through servers
* associated with the gateway.
*/
service = __connman_element_get_service(element);
if (service != NULL) {
/*
* TODO(sleffler): sometimes the service lookup fails
* because the corresponding network has no group;
* don't know why.
*/
__connman_service_indicate_active(service, TRUE);
}
}
static struct gateway_data *pick_default_gateway(void)
{
struct gateway_data *found = NULL;
unsigned int order = CONNMAN_SERVICE_ORDER_MAX;
GSList *list;
for (list = gateway_list; list; list = list->next) {
struct gateway_data *data = list->data;
if (checkorder(data, __func__) == FALSE)
continue;
if (found == NULL || data->order < order) {
found = data;
order = data->order;
}
}
return found;
}
static void notify_gateway_removed(struct gateway_data *data)
{
struct connman_element *element;
struct connman_service *service;
element = &data->element;
service = __connman_element_get_service(element);
if (service != NULL)
__connman_service_indicate_active(service, FALSE);
}
static void remove_gateway(struct gateway_data *data)
{
_DBG_CONNECTION("gateway %s", data->gateway);
if (data->active == TRUE) {
/* known to be in the routing tables; remove it */
connman_inet_del_gateway(data->element.index,
inet_addr(data->gateway),
data->metric);
data->active = FALSE;
}
gateway_list = g_slist_remove(gateway_list, data);
update_order();
notify_gateway_removed(data);
}
static void connection_delroute(void *user_data, int index, int scope,
const char *dst, const char *gateway)
{
struct gateway_data *data;
_DBG_CONNECTION("index %d scope %d dst %s gateway %s",
index, scope, dst, gateway);
if (scope != RT_SCOPE_UNIVERSE || g_strcmp0(dst, "0.0.0.0") != 0)
return;
data = find_gateway(index, gateway);
if (data != NULL) {
data->active = FALSE;
notify_gateway_removed(data);
}
data = pick_default_gateway();
if (data != NULL) {
connman_info("Replace default gateway with %s", data->gateway);
set_default_gateway(data);
}
}
static struct connman_rtnl connection_rtnl = {
RTNL_DEFINE("connection", CONNMAN_RTNL_PRIORITY_DEFAULT),
.newroute = connection_newroute,
.delroute = connection_delroute,
};
static struct gateway_data *find_active_gateway(void)
{
GSList *list;
_DBG_CONNECTION("");
for (list = gateway_list; list; list = list->next) {
struct gateway_data *data = list->data;
if (checkorder(data, __func__) == FALSE)
continue;
if (data->metric == ACTIVE_METRIC)
return data;
}
return NULL;
}
static void gateway_destruct(struct connman_element *element)
{
struct gateway_data *data = (struct gateway_data *) element;
_DBG_CONNECTION("index %d gateway %s", element->index, data->gateway);
g_free(data->gateway);
}
/*
* Create a connection element and register it as a child of the parent.
*/
struct connman_element *__connman_connection_create(
struct connman_ipconfig *ipconfig, struct connman_element *parent)
{
struct gateway_data *data;
struct connman_element *connection;
data = g_try_new0(struct gateway_data, 1);
if (data == NULL) {
connman_error("%s: no memory for connection element", __func__);
return NULL;
}
data->active = FALSE;
data->metric = INVALID_METRIC;
connection = &data->element;
__connman_element_initialize(connection);
connection->type = CONNMAN_ELEMENT_TYPE_CONNECTION;
connection->index = connman_ipconfig_get_index(ipconfig);
connection->devname = g_strdup(connman_ipconfig_get_ifname(ipconfig));
connection->destruct = gateway_destruct;
if (connman_element_register(connection, parent) < 0) {
connman_element_unref(connection);
return NULL;
} else
return connection;
}
static DBusConnection *connection;
static void emit_state_change(DBusConnection *conn, const char *state)
{
DBusMessage *signal;
connman_dbus_send_property_changed_variant(CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "State", DBUS_TYPE_STRING, &state);
signal = dbus_message_new_signal(CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "StateChanged");
if (signal != NULL) {
DBusMessageIter entry;
dbus_message_iter_init_append(signal, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
&state);
g_dbus_send_message(conn, signal);
}
}
connman_bool_t __connman_connection_is_online(void)
{
return (__connman_element_count(NULL,
CONNMAN_ELEMENT_TYPE_CONNECTION) > 0);
}
/*
* Called when a CONNECTION element is registered. We know the
* element is installed as a direct child of an ipconfig element
* (see connman_ipconfig_bind) so grab the parent and use it to
* find gateway information.
*/
static int connection_probe(struct connman_element *element)
{
struct connman_service *service = NULL;
const char *gateway = NULL;
struct gateway_data *active_gateway = NULL;
struct gateway_data *new_gateway = NULL;
_DBG_CONNECTION("element %p name %s parent %p index %d",
element, element->name, element->parent, element->index);
if (element->parent == NULL)
return -ENODEV;
#if 0
/* TODO(sleffler) could check for all possible types */
if (element->parent->type != CONNMAN_ELEMENT_TYPE_IPV4)
return -ENODEV;
#endif
gateway = __connman_ipconfig_get_gateway(element->index);
_DBG_CONNECTION("gateway %s", gateway);
/* locate the associated service and mark it READY+enabled */
service = __connman_element_get_service(element);
__connman_service_indicate_state(service, CONNMAN_SERVICE_STATE_READY);
connman_element_set_enabled(element, TRUE);
if (gateway == NULL) /* NB: unlikely but possible */
goto out;
new_gateway = (struct gateway_data *) element;
new_gateway->gateway = g_strdup(gateway);
if (new_gateway->gateway == NULL) {
connman_error("%s: no memory for gateway string %s",
__func__, gateway);
goto out;
}
new_gateway->metric = INVALID_METRIC;
/*
* Add the gateway and potentially update the system-wide default.
*/
active_gateway = find_active_gateway();
add_gateway(new_gateway);
if (active_gateway == NULL) {
connman_info("No previous default gateway, use %s",
new_gateway->gateway);
set_default_gateway(new_gateway);
} else if (new_gateway->order < active_gateway->order) {
connman_info("Change default gateway, %s (%d) metric %d"
" to %s (%d) metric %d",
active_gateway->gateway, active_gateway->order,
active_gateway->metric,
new_gateway->gateway, new_gateway->order,
new_gateway->metric);
/* NB: add first so there's always a default route */
set_gateway_and_metric(new_gateway, ACTIVE_METRIC);
/* Update metric on old active gateway */
set_gateway_and_metric(active_gateway, INACTIVE_METRIC);
} else {
connman_info("Adding inactive gateway for %s (%d) metric %d",
new_gateway->gateway, new_gateway->order,
INACTIVE_METRIC);
set_gateway_and_metric(new_gateway, INACTIVE_METRIC);
}
out:
if (__connman_element_count(NULL, CONNMAN_ELEMENT_TYPE_CONNECTION) == 1)
emit_state_change(connection, "online");
return 0;
}
/*
* Called when a CONNECTION element is removed from the hierarchy.
*/
static void connection_remove(struct connman_element *element)
{
struct connman_service *service;
struct gateway_data *data, *default_gateway;
gboolean wasactive;
_DBG_CONNECTION("element %p name %s", element, element->name);
service = __connman_element_get_service(element);
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_DISCONNECT);
connman_element_set_enabled(element, FALSE);
if (__connman_element_count(NULL, CONNMAN_ELEMENT_TYPE_CONNECTION) == 0)
emit_state_change(connection, "offline");
data = (struct gateway_data *) element;
wasactive = data->active; /* record before state is clobbered */
remove_gateway(data);
if (wasactive) {
/* pick new active/default gateway as we just removed it */
default_gateway = pick_default_gateway();
if (default_gateway != NULL) {
connman_info("New default gateway %s (%d) metric %d",
default_gateway->gateway,
default_gateway->order,
default_gateway->metric);
set_default_gateway(default_gateway);
}
}
}
/*
* Called when a CONNECTION element is updated (e.g. when roaming).
*/
static void connection_update(struct connman_element *element)
{
const char *gateway = __connman_ipconfig_get_gateway(element->index);
struct gateway_data *data = (struct gateway_data *) element;
struct connman_service *service;
_DBG_CONNECTION("element %p index %d gateway %s metric %d", element,
element->index, gateway, data->metric);
if (gateway == NULL) { /* NB: can happen during shutdown */
connman_info("Update default gateway, %s to None",
data->gateway);
} else if (g_strcmp0(data->gateway, gateway) != 0) {
connman_info("Update default gateway, %s to %s",
data->gateway, gateway);
/* NB: add first so there's always a default route */
connman_inet_set_gateway(element->index,
inet_addr(gateway), data->metric);
connman_inet_del_gateway(element->index,
inet_addr(data->gateway), data->metric);
g_free(data->gateway);
data->gateway = g_strdup(gateway);
} else if (data->active == TRUE) {
/* TODO(sleffler) should not be needed */
connman_inet_set_gateway(element->index, inet_addr(gateway),
data->metric);
}
/* locate the associated service and mark it READY+enabled */
service = __connman_element_get_service(element);
__connman_service_indicate_state(service, CONNMAN_SERVICE_STATE_READY);
}
static struct connman_driver connection_driver = {
.name = "connection",
.type = CONNMAN_ELEMENT_TYPE_CONNECTION,
.priority = CONNMAN_DRIVER_PRIORITY_LOW,
.probe = connection_probe,
.remove = connection_remove,
.update = connection_update,
};
void __connman_connection_resort(void)
{
struct gateway_data *new_gateway;
struct gateway_data *active_gateway;
active_gateway = find_active_gateway();
update_order();
new_gateway = pick_default_gateway();
if (new_gateway != NULL && new_gateway != active_gateway) {
/* NB: add first so there's always a default route */
set_default_gateway(new_gateway);
/* Update metric on old active gateway */
if (active_gateway != NULL)
set_gateway_and_metric(active_gateway, INACTIVE_METRIC);
}
}
int __connman_connection_init(void)
{
_DBG_CONNECTION("");
connection = connman_dbus_get_connection();
if (connection == NULL) {
connman_error("No dbus connection");
return -EIO;
}
if (connman_rtnl_register(&connection_rtnl) < 0) {
connman_error("Failed to setup RTNL gateway driver");
/* TODO(sleffler) continue? */
}
return connman_driver_register(&connection_driver);
}
void __connman_connection_cleanup(void)
{
_DBG_CONNECTION("");
connman_driver_unregister(&connection_driver);
connman_rtnl_unregister(&connection_rtnl);
dbus_connection_unref(connection);
/* NB: storage is reclaimed with elements */
while (gateway_list != NULL)
remove_gateway(gateway_list->data);
}