blob: fd4e1eb78fe76af6075ad7b16a49a2f54890bd14 [file] [log] [blame] [edit]
/*
*
* Connection Manager
*
* Copyright (C) 2012-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 <string.h>
#include <errno.h>
#include <gdbus.h>
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/device.h>
#include <connman/network.h>
#include <connman/service.h>
#include <connman/inet.h>
#include <connman/dbus.h>
#define DUNDEE_SERVICE "org.ofono.dundee"
#define DUNDEE_MANAGER_INTERFACE DUNDEE_SERVICE ".Manager"
#define DUNDEE_DEVICE_INTERFACE DUNDEE_SERVICE ".Device"
#define DEVICE_ADDED "DeviceAdded"
#define DEVICE_REMOVED "DeviceRemoved"
#define PROPERTY_CHANGED "PropertyChanged"
#define GET_PROPERTIES "GetProperties"
#define SET_PROPERTY "SetProperty"
#define GET_DEVICES "GetDevices"
#define TIMEOUT 60000
static DBusConnection *connection;
static GHashTable *dundee_devices = NULL;
struct dundee_data {
char *path;
char *name;
struct connman_device *device;
struct connman_network *network;
bool active;
int index;
/* IPv4 Settings */
enum connman_ipconfig_method method;
struct connman_ipaddress *address;
char *nameservers;
DBusPendingCall *call;
};
static char *get_ident(const char *path)
{
char *pos;
if (*path != '/')
return NULL;
pos = strrchr(path, '/');
if (!pos)
return NULL;
return pos + 1;
}
static int create_device(struct dundee_data *info)
{
struct connman_device *device;
char *ident;
int err;
DBG("%s", info->path);
ident = g_strdup(get_ident(info->path));
device = connman_device_create("dundee", CONNMAN_DEVICE_TYPE_BLUETOOTH);
if (!device) {
err = -ENOMEM;
goto out;
}
DBG("device %p", device);
connman_device_set_ident(device, ident);
connman_device_set_string(device, "Path", info->path);
connman_device_set_data(device, info);
err = connman_device_register(device);
if (err < 0) {
connman_error("Failed to register DUN device");
connman_device_unref(device);
goto out;
}
info->device = device;
out:
g_free(ident);
return err;
}
static void destroy_device(struct dundee_data *info)
{
connman_device_set_powered(info->device, false);
if (info->call)
dbus_pending_call_cancel(info->call);
if (info->network) {
connman_device_remove_network(info->device, info->network);
connman_network_unref(info->network);
info->network = NULL;
}
connman_device_unregister(info->device);
connman_device_unref(info->device);
info->device = NULL;
}
static void device_destroy(gpointer data)
{
struct dundee_data *info = data;
if (info->device)
destroy_device(info);
g_free(info->path);
g_free(info->name);
g_free(info);
}
static int create_network(struct dundee_data *info)
{
struct connman_network *network;
const char *group;
int err;
DBG("%s", info->path);
network = connman_network_create(info->path,
CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN);
if (!network)
return -ENOMEM;
DBG("network %p", network);
connman_network_set_data(network, info);
connman_network_set_string(network, "Path",
info->path);
connman_network_set_name(network, info->name);
group = get_ident(info->path);
connman_network_set_group(network, group);
err = connman_device_add_network(info->device, network);
if (err < 0) {
connman_network_unref(network);
return err;
}
info->network = network;
return 0;
}
static void set_connected(struct dundee_data *info)
{
struct connman_service *service;
DBG("%s", info->path);
connman_inet_ifup(info->index);
service = connman_service_lookup_from_network(info->network);
if (!service)
return;
connman_service_create_ip4config(service, info->index);
connman_network_set_index(info->network, info->index);
connman_network_set_ipv4_method(info->network,
CONNMAN_IPCONFIG_METHOD_FIXED);
connman_network_set_ipaddress(info->network, info->address);
connman_network_set_nameservers(info->network, info->nameservers);
connman_network_set_connected(info->network, true);
}
static void set_disconnected(struct dundee_data *info)
{
DBG("%s", info->path);
connman_network_set_connected(info->network, false);
connman_inet_ifdown(info->index);
}
static void set_property_reply(DBusPendingCall *call, void *user_data)
{
struct dundee_data *info = user_data;
DBusMessage *reply;
DBusError error;
DBG("%s", info->path);
info->call = NULL;
dbus_error_init(&error);
reply = dbus_pending_call_steal_reply(call);
if (dbus_set_error_from_message(&error, reply)) {
connman_error("Failed to change property: %s %s %s",
info->path, error.name, error.message);
dbus_error_free(&error);
connman_network_set_error(info->network,
CONNMAN_NETWORK_ERROR_ASSOCIATE_FAIL);
}
dbus_message_unref(reply);
dbus_pending_call_unref(call);
}
static int set_property(struct dundee_data *info,
const char *property, int type, void *value)
{
DBusMessage *message;
DBusMessageIter iter;
DBG("%s %s", info->path, property);
message = dbus_message_new_method_call(DUNDEE_SERVICE, info->path,
DUNDEE_DEVICE_INTERFACE, SET_PROPERTY);
if (!message)
return -ENOMEM;
dbus_message_iter_init_append(message, &iter);
connman_dbus_property_append_basic(&iter, property, type, value);
if (!dbus_connection_send_with_reply(connection, message,
&info->call, TIMEOUT)) {
connman_error("Failed to change property: %s %s",
info->path, property);
dbus_message_unref(message);
return -EINVAL;
}
if (!info->call) {
connman_error("D-Bus connection not available");
dbus_message_unref(message);
return -EINVAL;
}
dbus_pending_call_set_notify(info->call, set_property_reply,
info, NULL);
dbus_message_unref(message);
return -EINPROGRESS;
}
static int device_set_active(struct dundee_data *info)
{
dbus_bool_t active = TRUE;
DBG("%s", info->path);
return set_property(info, "Active", DBUS_TYPE_BOOLEAN,
&active);
}
static int device_set_inactive(struct dundee_data *info)
{
dbus_bool_t active = FALSE;
int err;
DBG("%s", info->path);
err = set_property(info, "Active", DBUS_TYPE_BOOLEAN,
&active);
if (err == -EINPROGRESS)
return 0;
return err;
}
static int network_probe(struct connman_network *network)
{
DBG("network %p", network);
return 0;
}
static void network_remove(struct connman_network *network)
{
DBG("network %p", network);
}
static int network_connect(struct connman_network *network)
{
struct dundee_data *info = connman_network_get_data(network);
DBG("network %p", network);
return device_set_active(info);
}
static int network_disconnect(struct connman_network *network, bool user_initiated)
{
struct dundee_data *info = connman_network_get_data(network);
DBG("network %p", network);
return device_set_inactive(info);
}
static struct connman_network_driver network_driver = {
.name = "network",
.type = CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN,
.probe = network_probe,
.remove = network_remove,
.connect = network_connect,
.disconnect = network_disconnect,
};
static int dundee_probe(struct connman_device *device)
{
GHashTableIter iter;
gpointer key, value;
DBG("device %p", device);
if (!dundee_devices)
return -ENOTSUP;
g_hash_table_iter_init(&iter, dundee_devices);
while (g_hash_table_iter_next(&iter, &key, &value)) {
struct dundee_data *info = value;
if (device == info->device)
return 0;
}
return -ENOTSUP;
}
static void dundee_remove(struct connman_device *device)
{
DBG("device %p", device);
}
static int dundee_enable(struct connman_device *device)
{
DBG("device %p", device);
return 0;
}
static int dundee_disable(struct connman_device *device)
{
DBG("device %p", device);
return 0;
}
static struct connman_device_driver dundee_driver = {
.name = "dundee",
.type = CONNMAN_DEVICE_TYPE_BLUETOOTH,
.probe = dundee_probe,
.remove = dundee_remove,
.enable = dundee_enable,
.disable = dundee_disable,
};
static char *extract_nameservers(DBusMessageIter *array)
{
DBusMessageIter entry;
char *nameservers = NULL;
char *tmp;
dbus_message_iter_recurse(array, &entry);
while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
const char *nameserver;
dbus_message_iter_get_basic(&entry, &nameserver);
if (!nameservers) {
nameservers = g_strdup(nameserver);
} else {
tmp = nameservers;
nameservers = g_strdup_printf("%s %s", tmp, nameserver);
g_free(tmp);
}
dbus_message_iter_next(&entry);
}
return nameservers;
}
static void extract_settings(DBusMessageIter *array,
struct dundee_data *info)
{
DBusMessageIter dict;
char *address = NULL, *gateway = NULL;
char *nameservers = NULL;
const char *interface = NULL;
int index = -1;
if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY)
return;
dbus_message_iter_recurse(array, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry, value;
const char *key, *val;
dbus_message_iter_recurse(&dict, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
if (g_str_equal(key, "Interface")) {
dbus_message_iter_get_basic(&value, &interface);
DBG("Interface %s", interface);
index = connman_inet_ifindex(interface);
DBG("index %d", index);
if (index < 0)
break;
} else if (g_str_equal(key, "Address")) {
dbus_message_iter_get_basic(&value, &val);
address = g_strdup(val);
DBG("Address %s", address);
} else if (g_str_equal(key, "DomainNameServers")) {
nameservers = extract_nameservers(&value);
DBG("Nameservers %s", nameservers);
} else if (g_str_equal(key, "Gateway")) {
dbus_message_iter_get_basic(&value, &val);
gateway = g_strdup(val);
DBG("Gateway %s", gateway);
}
dbus_message_iter_next(&dict);
}
if (index < 0)
goto out;
info->address = connman_ipaddress_alloc(CONNMAN_IPCONFIG_TYPE_IPV4);
if (!info->address)
goto out;
info->index = index;
connman_ipaddress_set_ipv4(info->address, address, NULL, gateway);
info->nameservers = nameservers;
out:
if (info->nameservers != nameservers)
g_free(nameservers);
g_free(address);
g_free(gateway);
}
static gboolean device_changed(DBusConnection *conn,
DBusMessage *message,
void *user_data)
{
const char *path = dbus_message_get_path(message);
struct dundee_data *info = NULL;
DBusMessageIter iter, value;
const char *key;
const char *signature = DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING;
if (!dbus_message_has_signature(message, signature)) {
connman_error("dundee signature does not match");
return TRUE;
}
info = g_hash_table_lookup(dundee_devices, path);
if (!info)
return TRUE;
if (!dbus_message_iter_init(message, &iter))
return TRUE;
dbus_message_iter_get_basic(&iter, &key);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &value);
/*
* Dundee guarantees the ordering of Settings and
* Active. Settings will always be send before Active = True.
* That means we don't have to order here.
*/
if (g_str_equal(key, "Active")) {
dbus_bool_t active;
dbus_message_iter_get_basic(&value, &active);
info->active = active;
DBG("%s Active %d", info->path, info->active);
if (info->active)
set_connected(info);
else
set_disconnected(info);
} else if (g_str_equal(key, "Settings")) {
DBG("%s Settings", info->path);
extract_settings(&value, info);
} else if (g_str_equal(key, "Name")) {
char *name;
dbus_message_iter_get_basic(&value, &name);
g_free(info->name);
info->name = g_strdup(name);
DBG("%s Name %s", info->path, info->name);
connman_network_set_name(info->network, info->name);
connman_network_update(info->network);
}
return TRUE;
}
static void add_device(const char *path, DBusMessageIter *properties)
{
struct dundee_data *info;
int err;
info = g_hash_table_lookup(dundee_devices, path);
if (info)
return;
info = g_try_new0(struct dundee_data, 1);
if (!info)
return;
info->path = g_strdup(path);
while (dbus_message_iter_get_arg_type(properties) ==
DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry, value;
const char *key;
dbus_message_iter_recurse(properties, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
if (g_str_equal(key, "Active")) {
dbus_bool_t active;
dbus_message_iter_get_basic(&value, &active);
info->active = active;
DBG("%s Active %d", info->path, info->active);
} else if (g_str_equal(key, "Settings")) {
DBG("%s Settings", info->path);
extract_settings(&value, info);
} else if (g_str_equal(key, "Name")) {
char *name;
dbus_message_iter_get_basic(&value, &name);
info->name = g_strdup(name);
DBG("%s Name %s", info->path, info->name);
}
dbus_message_iter_next(properties);
}
g_hash_table_insert(dundee_devices, g_strdup(path), info);
err = create_device(info);
if (err < 0)
goto out;
err = create_network(info);
if (err < 0) {
destroy_device(info);
goto out;
}
if (info->active)
set_connected(info);
return;
out:
g_hash_table_remove(dundee_devices, path);
}
static gboolean device_added(DBusConnection *conn, DBusMessage *message,
void *user_data)
{
DBusMessageIter iter, properties;
const char *path;
const char *signature = DBUS_TYPE_OBJECT_PATH_AS_STRING
DBUS_TYPE_ARRAY_AS_STRING
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING;
if (!dbus_message_has_signature(message, signature)) {
connman_error("dundee signature does not match");
return TRUE;
}
DBG("");
if (!dbus_message_iter_init(message, &iter))
return TRUE;
dbus_message_iter_get_basic(&iter, &path);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &properties);
add_device(path, &properties);
return TRUE;
}
static void remove_device(DBusConnection *conn, const char *path)
{
DBG("path %s", path);
g_hash_table_remove(dundee_devices, path);
}
static gboolean device_removed(DBusConnection *conn, DBusMessage *message,
void *user_data)
{
const char *path;
const char *signature = DBUS_TYPE_OBJECT_PATH_AS_STRING;
if (!dbus_message_has_signature(message, signature)) {
connman_error("dundee signature does not match");
return TRUE;
}
dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID);
remove_device(conn, path);
return TRUE;
}
static void manager_get_devices_reply(DBusPendingCall *call, void *user_data)
{
DBusMessage *reply;
DBusError error;
DBusMessageIter array, dict;
const char *signature = DBUS_TYPE_ARRAY_AS_STRING
DBUS_STRUCT_BEGIN_CHAR_AS_STRING
DBUS_TYPE_OBJECT_PATH_AS_STRING
DBUS_TYPE_ARRAY_AS_STRING
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING
DBUS_STRUCT_END_CHAR_AS_STRING;
DBG("");
reply = dbus_pending_call_steal_reply(call);
if (!dbus_message_has_signature(reply, signature)) {
connman_error("dundee signature does not match");
goto done;
}
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply)) {
connman_error("%s", error.message);
dbus_error_free(&error);
goto done;
}
if (!dbus_message_iter_init(reply, &array))
goto done;
dbus_message_iter_recurse(&array, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRUCT) {
DBusMessageIter value, properties;
const char *path;
dbus_message_iter_recurse(&dict, &value);
dbus_message_iter_get_basic(&value, &path);
dbus_message_iter_next(&value);
dbus_message_iter_recurse(&value, &properties);
add_device(path, &properties);
dbus_message_iter_next(&dict);
}
done:
dbus_message_unref(reply);
dbus_pending_call_unref(call);
}
static int manager_get_devices(void)
{
DBusMessage *message;
DBusPendingCall *call;
DBG("");
message = dbus_message_new_method_call(DUNDEE_SERVICE, "/",
DUNDEE_MANAGER_INTERFACE, GET_DEVICES);
if (!message)
return -ENOMEM;
if (!dbus_connection_send_with_reply(connection, message,
&call, TIMEOUT)) {
connman_error("Failed to call GetDevices()");
dbus_message_unref(message);
return -EINVAL;
}
if (!call) {
connman_error("D-Bus connection not available");
dbus_message_unref(message);
return -EINVAL;
}
dbus_pending_call_set_notify(call, manager_get_devices_reply,
NULL, NULL);
dbus_message_unref(message);
return -EINPROGRESS;
}
static void dundee_connect(DBusConnection *conn, void *user_data)
{
DBG("connection %p", conn);
dundee_devices = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, device_destroy);
manager_get_devices();
}
static void dundee_disconnect(DBusConnection *conn, void *user_data)
{
DBG("connection %p", conn);
g_hash_table_destroy(dundee_devices);
dundee_devices = NULL;
}
static guint watch;
static guint added_watch;
static guint removed_watch;
static guint device_watch;
static int dundee_init(void)
{
int err;
connection = connman_dbus_get_connection();
if (!connection)
return -EIO;
watch = g_dbus_add_service_watch(connection, DUNDEE_SERVICE,
dundee_connect, dundee_disconnect, NULL, NULL);
added_watch = g_dbus_add_signal_watch(connection, DUNDEE_SERVICE, NULL,
DUNDEE_MANAGER_INTERFACE,
DEVICE_ADDED, device_added,
NULL, NULL);
removed_watch = g_dbus_add_signal_watch(connection, DUNDEE_SERVICE,
NULL, DUNDEE_MANAGER_INTERFACE,
DEVICE_REMOVED, device_removed,
NULL, NULL);
device_watch = g_dbus_add_signal_watch(connection, DUNDEE_SERVICE,
NULL, DUNDEE_DEVICE_INTERFACE,
PROPERTY_CHANGED,
device_changed,
NULL, NULL);
if (watch == 0 || added_watch == 0 || removed_watch == 0 ||
device_watch == 0) {
err = -EIO;
goto remove;
}
err = connman_network_driver_register(&network_driver);
if (err < 0)
goto remove;
err = connman_device_driver_register(&dundee_driver);
if (err < 0) {
connman_network_driver_unregister(&network_driver);
goto remove;
}
return 0;
remove:
g_dbus_remove_watch(connection, watch);
g_dbus_remove_watch(connection, added_watch);
g_dbus_remove_watch(connection, removed_watch);
g_dbus_remove_watch(connection, device_watch);
dbus_connection_unref(connection);
return err;
}
static void dundee_exit(void)
{
g_dbus_remove_watch(connection, watch);
g_dbus_remove_watch(connection, added_watch);
g_dbus_remove_watch(connection, removed_watch);
g_dbus_remove_watch(connection, device_watch);
connman_device_driver_unregister(&dundee_driver);
connman_network_driver_unregister(&network_driver);
dbus_connection_unref(connection);
}
CONNMAN_PLUGIN_DEFINE(dundee, "Dundee plugin", VERSION,
CONNMAN_PLUGIN_PRIORITY_DEFAULT, dundee_init, dundee_exit)