blob: f38ebee121ebfd10137d6480e0888699a8cf9957 [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 <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <gdbus.h>
#include <connman/assert.h>
#include <connman/inet.h>
#include "connman.h"
#define _DBG_DEVICE(fmt, arg...) DBG(DBG_DEVICE, fmt, ## arg)
static DBusConnection *connection = NULL;
struct connman_device {
struct connman_element element; /* base class */
enum connman_device_type type;
enum connman_device_mode mode;
connman_bool_t registered;
connman_bool_t offlinemode;
connman_bool_t blocked;
connman_bool_t powered;
connman_bool_t powered_pending;
connman_bool_t powered_persistent;
connman_bool_t carrier;
connman_bool_t scanning;
connman_bool_t connected;
connman_bool_t reconnect;
struct connman_rtnl rtnl; /* RTNL watch for dev rename */
connman_uint16_t scan_interval; /* long scan interval (secs) */
connman_uint16_t scan_short; /* short scan interval (secs) */
int signal_threshold; /* short/long threshold (dBm) */
char *scan_method; /* background scan method */
char *name;
char *node;
char *address; /* local address */
char *bssid; /* connected address */
char *interface;
char *control;
char *ident;
int phyindex;
uint32_t configmask; /* bitmask of ip configs */
guint scan_timeout;
struct {
enum connman_device_cellular_family family;
char *carrier;
char *esn;
char *meid;
char *imei;
char *imsi;
char *mdn;
char *min;
char *model_id;
char *manufacturer;
char *firmware_revision;
char *firmware_image;
char *hardware_revision;
char *unlock_required;
char *selected_network;
guint prl_version;
guint unlock_retries;
connman_bool_t lock_enabled;
connman_bool_t roaming_allowed;
connman_bool_t scanning_supported;
GPtrArray *found_networks;
GPtrArray *apn_list;
struct connman_network_operator home_provider;
connman_bool_t provider_requires_roaming;
} cellular;
struct connman_device_driver *driver;
void *driver_data;
struct connman_network *network; /* current connected network */
GHashTable *networks; /* associated networks */
DBusMessage *pending;
guint timeout;
/* Address and path of an external DBus object backing this device. */
char *external_dbus_connection;
char *external_dbus_service;
char *external_dbus_object;
/* count of times the reverse path filter has been disabled */
int rp_filter_count;
};
static const char *kStringDictArraySig =
DBUS_TYPE_ARRAY_AS_STRING
DBUS_TYPE_ARRAY_AS_STRING
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING;
#define DEFAULT_SCAN_METHOD NULL /* Leave it up to the device plugin */
#define DEFAULT_SCAN_INTERVAL 180
#define DEFAULT_SCAN_SHORT 30
#define DEFAULT_SIGNAL_THRESHOLD -50
#define SCAN_INTERVAL_UNSET ((connman_uint16_t)-1)
#define SIGNAL_THRESHOLD_UNSET 9999
static void device_newlink(void *user_data, int index, unsigned short type,
const char *ifname, unsigned flags, int change);
/*
* Scan timer handling.
*/
/*
* Timer callback to do a bgscan request. We assume bgscan operations
* while connected are handled elsewhere (e.g. wpa_supplicant for wifi,
* bluez for bluetooth). If a device needs explicit scan requests made
* while connected the device can schedule it's own timer and use the
* connman_device_scan request below.
*/
static gboolean device_scan(gpointer user_data)
{
struct connman_device *device = user_data;
_DBG_DEVICE("device %p", device);
if (device->driver == NULL) {
device->scan_timeout = 0;
return FALSE;
}
if (device->connected == FALSE) {
/* TODO(sleffler) can driver->scan ever be NULL? */
if (device->driver->scan)
device->driver->scan(device);
} else {
/*
* NB: this can happen during suspend/resume so just drop
* the request; the timer should be cleared shortly.
*/
connman_warn("%s: scanning while connected", device->name);
}
return TRUE;
}
/*
* Stop and disable bgscan work.
*/
static void bgscan_disable(struct connman_device *device)
{
if (device->scan_timeout != 0) {
g_source_remove(device->scan_timeout);
device->scan_timeout = 0;
}
}
/*
* Enable bgscan work using the current settings.
*/
static void bgscan_enable(struct connman_device *device)
{
int scan_interval = connman_device_bgscan_get_long(device);
bgscan_disable(device);
if (scan_interval > 0) {
guint interval = scan_interval;
device->scan_timeout = g_timeout_add_seconds(interval,
device_scan, device);
}
}
static const char *type2description(enum connman_device_type type)
{
switch (type) {
case CONNMAN_DEVICE_TYPE_UNKNOWN:
case CONNMAN_DEVICE_TYPE_VENDOR:
break;
case CONNMAN_DEVICE_TYPE_ETHERNET:
return "Ethernet";
case CONNMAN_DEVICE_TYPE_WIFI:
return "Wireless";
case CONNMAN_DEVICE_TYPE_WIMAX:
return "WiMAX";
case CONNMAN_DEVICE_TYPE_BLUETOOTH:
return "Bluetooth";
case CONNMAN_DEVICE_TYPE_GPS:
return "GPS";
case CONNMAN_DEVICE_TYPE_CELLULAR:
return "Cellular";
}
return NULL;
}
static const char *type2string(enum connman_device_type type)
{
switch (type) {
case CONNMAN_DEVICE_TYPE_UNKNOWN:
case CONNMAN_DEVICE_TYPE_VENDOR:
break;
case CONNMAN_DEVICE_TYPE_ETHERNET:
return "ethernet";
case CONNMAN_DEVICE_TYPE_WIFI:
return "wifi";
case CONNMAN_DEVICE_TYPE_WIMAX:
return "wimax";
case CONNMAN_DEVICE_TYPE_BLUETOOTH:
return "bluetooth";
case CONNMAN_DEVICE_TYPE_GPS:
return "gps";
case CONNMAN_DEVICE_TYPE_CELLULAR:
return "cellular";
}
return NULL;
}
static const char *family2string(enum connman_device_cellular_family family)
{
switch (family) {
case CONNMAN_DEVICE_CELLULAR_FAMILY_CDMA:
return "CDMA";
case CONNMAN_DEVICE_CELLULAR_FAMILY_GSM:
return "GSM";
}
return NULL;
}
enum connman_service_type __connman_device_get_service_type(
struct connman_device *device)
{
enum connman_device_type type = connman_device_get_type(device);
switch (type) {
case CONNMAN_DEVICE_TYPE_UNKNOWN:
case CONNMAN_DEVICE_TYPE_VENDOR:
case CONNMAN_DEVICE_TYPE_GPS:
break;
case CONNMAN_DEVICE_TYPE_ETHERNET:
return CONNMAN_SERVICE_TYPE_ETHERNET;
case CONNMAN_DEVICE_TYPE_WIFI:
return CONNMAN_SERVICE_TYPE_WIFI;
case CONNMAN_DEVICE_TYPE_WIMAX:
return CONNMAN_SERVICE_TYPE_WIMAX;
case CONNMAN_DEVICE_TYPE_BLUETOOTH:
return CONNMAN_SERVICE_TYPE_BLUETOOTH;
case CONNMAN_DEVICE_TYPE_CELLULAR:
return CONNMAN_SERVICE_TYPE_CELLULAR;
}
return CONNMAN_SERVICE_TYPE_UNKNOWN;
}
struct connman_ipconfig *connman_device_add_ipconfig(
struct connman_device *device, enum connman_ipconfig_type type)
{
struct connman_ipconfig *ipconfig;
gchar name[32];
int ix;
/* XXX not unique if config's deleted */
ix = ffs(~(device->configmask));
if (ix == 0) {
/* XXX should not happen */
connman_error("%s: too many ipconfigs for %s", __func__,
device->name);
return NULL;
}
g_snprintf(name, sizeof(name), "%s_%d", device->ident, ix-1);
ipconfig = connman_ipconfig_create(type, name, device,
device->element.index, TRUE);
if (ipconfig == NULL) {
connman_error("%s: failed to create %s ipconfig for %s",
__func__, type2string(type), device->name);
return NULL;
}
/* ipconfig's are children of the device */
/* TODO(sleffler) check return value */
connman_ipconfig_register(ipconfig, &device->element);
device->configmask |= 1<<(ix-1);
__connman_profile_save_device(device);
return ipconfig;
}
/**
* set_connected
* @device: connman device that is now (dis)connected
* @connected: is device connected or disconnected
*
* Returns: %0 on success
*/
static int set_connected(struct connman_device *device,
connman_bool_t connected)
{
struct connman_service *service =
__connman_service_lookup_from_device(device);
enum connman_service_type type =
__connman_device_get_service_type(device);
if (connected == TRUE) {
connman_bool_t was_previously_connected;
device->connected = TRUE;
bgscan_disable(device);
/* kick off L3 configuration */
if (device->configmask == 0) {
/*
* Auto-add DHCP if no ipconfig records are set up
* and the device is backed by a network interface.
*
* TODO(sleffler) this is policy, perhaps add a
* config knob to disable
*/
connman_device_add_ipconfig(device,
CONNMAN_IPCONFIG_TYPE_DHCP);
}
was_previously_connected =
__connman_service_get_connected(service);
connman_ipconfig_request(device);
/*
* We manually ena/clear ipv6 routes and IP addresses because
* we rely on the kernel to install them and they are only
* cleared if the interface is marked down--which does not
* happen in flimflam.
*/
connman_inet_enable_ipv6(connman_device_get_index(device));
/*
* Some ipconfig types immediately mark the service as
* connected. We can detect this by checking whether
* the service state has changed from not-connected
* to connected since before we called
* connman_ipconfig_request above. If the the service
* was connected as a result of conman_ipconfig_request,
* do NOT mark the service back in the "configuring" state.
*/
if (!(was_previously_connected == FALSE &&
__connman_service_get_connected(service) == TRUE)) {
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_CONFIGURATION);
}
__connman_notifier_connect(type);
} else {
/* tear down L3 configuration */
connman_inet_disable_ipv6(connman_device_get_index(device));
connman_ipconfig_release(device);
device->connected = FALSE;
__connman_notifier_disconnect(type);
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_IDLE);
bgscan_enable(device);
}
return 0;
}
static int set_carrier(struct connman_device *device, connman_bool_t carrier)
{
if (carrier == TRUE)
__connman_profile_add_device(device);
else
__connman_profile_remove_device(device);
return set_connected(device, carrier);
}
static connman_bool_t powered_changed(struct connman_device *device)
{
return connman_dbus_send_property_changed_variant(device->element.path,
CONNMAN_DEVICE_INTERFACE, "Powered",
DBUS_TYPE_BOOLEAN, &device->powered);
}
static int set_powered(struct connman_device *device, connman_bool_t powered)
{
struct connman_device_driver *driver = device->driver;
enum connman_service_type type;
int err;
_DBG_DEVICE("device %p powered %d (currently %d/%d)",
device, powered,
device->powered, device->powered_pending);
if (device->powered_pending == powered)
return -EALREADY;
if (driver == NULL)
return -EINVAL;
type = __connman_device_get_service_type(device);
if (powered == TRUE) {
if (driver->enable != NULL) {
device->powered_pending = powered;
err = driver->enable(device);
if (err == 0)
__connman_notifier_enable(type);
} else
err = -EINVAL;
} else {
device->powered_pending = powered;
device->reconnect = FALSE;
bgscan_disable(device);
g_hash_table_remove_all(device->networks);
set_carrier(device, FALSE);
if (driver->disable != NULL) {
err = driver->disable(device);
if (err == 0)
__connman_notifier_disable(type);
} else
err = -EINVAL;
}
if (err == 0) {
device->powered = powered;
if (device->registered == TRUE)
powered_changed(device);
} else if (err == -EINPROGRESS) {
/* Not really an error, do not roll back powered_pending */
} else {
/* Roll back powered_pending. No-op in -EINVAL case
* because powered_pending is already !powered */
device->powered_pending = !powered;
}
return err;
}
/*
* Append object paths for each network associated with a device.
*/
static void append_network_path(gpointer key, gpointer value,
gpointer user_data)
{
struct connman_element *element = value;
DBusMessageIter *iter = user_data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
&element->path);
}
static void append_networks(DBusMessageIter *dict, void *arg)
{
struct connman_device *device = arg;
g_hash_table_foreach(device->networks, append_network_path, dict);
}
/*
* Append object paths for each ipconfig associated with a device.
*/
static void append_ipconfig_path(struct connman_ipconfig *ipconfig, void *arg)
{
const char *str = connman_ipconfig_get_path(ipconfig);
DBusMessageIter *iter = arg;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str);
}
static void append_ipconfig(DBusMessageIter *dict, void *arg)
{
struct connman_device *device = arg;
__connman_ipconfig_foreach(device, append_ipconfig_path, dict);
}
static void append_unlock_status(DBusMessageIter *dict, void *arg)
{
struct connman_device *device = arg;
if (device->cellular.unlock_required) {
connman_dbus_dict_append_basic(dict, "LockType",
DBUS_TYPE_STRING,
&device->cellular.unlock_required);
connman_dbus_dict_append_basic(dict, "RetriesLeft",
DBUS_TYPE_UINT32,
&device->cellular.unlock_retries);
connman_dbus_dict_append_basic(dict, "LockEnabled",
DBUS_TYPE_BOOLEAN,
&device->cellular.lock_enabled);
}
}
static void append_string_dict_item(gpointer key, gpointer value, gpointer data)
{
DBusMessageIter *iter = data;
connman_dbus_dict_append_string(iter, key, value);
}
static void append_string_dict_array(DBusMessageIter *iter, gpointer data)
{
DBusMessageIter array, dict;
int i;
GPtrArray *arrayptr = (GPtrArray *)data;
const char *array_sig = kStringDictArraySig
+ strlen(DBUS_TYPE_ARRAY_AS_STRING);
const char *dict_sig = array_sig
+ strlen(DBUS_TYPE_ARRAY_AS_STRING);
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
array_sig, &array);
for (i = 0; i < arrayptr->len; i++) {
GHashTable *props = g_ptr_array_index(arrayptr, i);
dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
dict_sig, &dict);
g_hash_table_foreach(props, append_string_dict_item, &dict);
dbus_message_iter_close_container(&array, &dict);
}
dbus_message_iter_close_container(iter, &array);
}
static void append_operator(DBusMessageIter *iter, void *arg)
{
struct connman_network_operator *op =
(struct connman_network_operator *)arg;
if (op->name != NULL)
connman_dbus_dict_append_string(iter, "name", op->name);
if (op->country != NULL)
connman_dbus_dict_append_string(iter, "country", op->country);
if (op->code != NULL)
connman_dbus_dict_append_string(iter, "code", op->code);
}
/*
* Count of the times the reverse path filter has been disabled on all
* devices. This is used to control the "all" reverse path filter.
*/
static int rp_filter_all_count;
static void rp_filter_set(const char *interface, connman_bool_t enabled)
{
/* gcc 4.6.0 still warns on (void) write(...), hence unused */
int fd, cnt __attribute__ ((unused));
char filename[PATH_MAX];
const char *str_value = (enabled == TRUE) ? "1" : "0";
snprintf(filename, sizeof(filename),
"/proc/sys/net/ipv4/conf/%s/rp_filter", interface);
fd = open(filename, O_WRONLY);
if (fd == -1)
return;
cnt = write(fd, str_value, strlen(str_value));
close(fd);
}
/**
* connman_device_rp_filter_disable:
* @device: device structure
*
* Disable the reverse path filter for this device. Calls to disable
* are counted, and only the first call changes the setting.
*
* If this is the first device to disable the reverse path filter,
* disable the reverse path filter globally also.
*/
void connman_device_rp_filter_disable(struct connman_device *device)
{
if (device->rp_filter_count == 0)
rp_filter_set(device->interface, FALSE);
device->rp_filter_count++;
if (rp_filter_all_count == 0)
rp_filter_set("all", FALSE);
rp_filter_all_count++;
}
/**
* connman_device_rp_filter_disable:
* @device: device structure
*
* Enable the reverse path filter for this device. Calls to disable
* are counted, and only when the count gets to zero is the filter
* enabled.
*
* If all devices have enabled the reverse path filter, then globally
* enable it.
*/
void connman_device_rp_filter_enable(struct connman_device *device)
{
device->rp_filter_count--;
if (device->rp_filter_count == 0)
rp_filter_set(device->interface, TRUE);
rp_filter_all_count--;
if (rp_filter_all_count == 0)
rp_filter_set("all", TRUE);
}
static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
DBusMessage *reply;
DBusMessageIter array, dict;
const char *str;
int val;
_DBG_DEVICE("conn %p", conn);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_PUBLIC) < 0)
return __connman_error_permission_denied(msg);
reply = dbus_message_new_method_return(msg);
if (reply == NULL)
return NULL;
dbus_message_iter_init_append(reply, &array);
dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
if (device->name != NULL)
connman_dbus_dict_append_variant(&dict, "Name",
DBUS_TYPE_STRING, &device->name);
str = type2string(device->type);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "Type",
DBUS_TYPE_STRING, &str);
if (device->address != NULL)
connman_dbus_dict_append_variant(&dict, "Address",
DBUS_TYPE_STRING, &device->address);
if (device->interface != NULL)
connman_dbus_dict_append_variant(&dict, "Interface",
DBUS_TYPE_STRING, &device->interface);
connman_dbus_dict_append_variant(&dict, "Powered",
DBUS_TYPE_BOOLEAN, &device->powered);
if (device->driver && device->driver->scan)
connman_dbus_dict_append_variant(&dict, "Scanning",
DBUS_TYPE_BOOLEAN, &device->scanning);
connman_dbus_dict_append_variant(&dict, "Reconnect",
DBUS_TYPE_BOOLEAN, &device->reconnect);
connman_dbus_dict_append_variant_array(&dict, "IPConfigs",
DBUS_TYPE_OBJECT_PATH, append_ipconfig, device);
if (device->type == CONNMAN_DEVICE_TYPE_CELLULAR) {
str = family2string(device->cellular.family);
if (str != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.Family",
DBUS_TYPE_STRING, &str);
if (device->cellular.carrier != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.Carrier",
DBUS_TYPE_STRING,
&device->cellular.carrier);
if (device->cellular.imsi != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.IMSI",
DBUS_TYPE_STRING,
&device->cellular.imsi);
if (device->cellular.imei != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.IMEI",
DBUS_TYPE_STRING,
&device->cellular.imei);
if (device->cellular.meid != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.MEID",
DBUS_TYPE_STRING,
&device->cellular.meid);
if (device->cellular.esn != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.ESN",
DBUS_TYPE_STRING,
&device->cellular.esn);
if (device->cellular.mdn != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.MDN",
DBUS_TYPE_STRING,
&device->cellular.mdn);
if (device->cellular.min != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.MIN",
DBUS_TYPE_STRING,
&device->cellular.min);
if (device->cellular.model_id != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.ModelID",
DBUS_TYPE_STRING,
&device->cellular.model_id);
if (device->cellular.manufacturer != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.Manufacturer",
DBUS_TYPE_STRING,
&device->cellular.manufacturer);
if (device->cellular.firmware_revision != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.FirmwareRevision",
DBUS_TYPE_STRING,
&device->cellular.firmware_revision);
if (device->cellular.firmware_image != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.FirmwareImageName",
DBUS_TYPE_STRING,
&device->cellular.firmware_image);
if (device->cellular.hardware_revision != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.HardwareRevision",
DBUS_TYPE_STRING,
&device->cellular.hardware_revision);
if (device->cellular.home_provider.name != NULL)
connman_dbus_dict_append_string_dict(
&dict, "Cellular.HomeProvider",
append_operator,
&device->cellular.home_provider);
connman_dbus_dict_append_variant(
&dict, "Cellular.PRLVersion",
DBUS_TYPE_UINT16,
&device->cellular.prl_version);
connman_dbus_dict_append_dict(&dict, "Cellular.SIMLockStatus",
append_unlock_status, device);
if (device->cellular.selected_network != NULL)
connman_dbus_dict_append_variant(
&dict, "Cellular.SelectedNetwork",
DBUS_TYPE_STRING,
&device->cellular.selected_network);
if (device->cellular.found_networks != NULL)
connman_dbus_dict_append_variant_container(&dict,
"Cellular.FoundNetworks",
kStringDictArraySig,
append_string_dict_array,
device->cellular.found_networks);
if (device->cellular.apn_list != NULL)
connman_dbus_dict_append_variant_container(&dict,
"Cellular.APNList",
kStringDictArraySig,
append_string_dict_array,
device->cellular.apn_list);
connman_dbus_dict_append_variant(&dict, "Cellular.AllowRoaming",
DBUS_TYPE_BOOLEAN,
&device->cellular.roaming_allowed);
connman_dbus_dict_append_variant(
&dict,
"Cellular.SupportNetworkScan",
DBUS_TYPE_BOOLEAN,
&device->cellular.scanning_supported);
connman_dbus_dict_append_variant(&dict, "Cellular.ProviderRequiresRoaming",
DBUS_TYPE_BOOLEAN,
&device->cellular.provider_requires_roaming);
}
if (device->external_dbus_connection != NULL)
connman_dbus_dict_append_variant(&dict, "DBus.Connection",
DBUS_TYPE_STRING, &device->external_dbus_connection);
if (device->external_dbus_service != NULL)
connman_dbus_dict_append_variant(&dict, "DBus.Service",
DBUS_TYPE_STRING, &device->external_dbus_service);
if (device->external_dbus_object != NULL)
connman_dbus_dict_append_basic(&dict, "DBus.Object",
DBUS_TYPE_OBJECT_PATH, &device->external_dbus_object);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
break;
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
val = connman_device_bgscan_get_long(device);
if (val > 0)
connman_dbus_dict_append_variant(&dict, "ScanInterval",
DBUS_TYPE_UINT16, &val);
str = connman_device_bgscan_get_method(device);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "BgscanMethod",
DBUS_TYPE_STRING, &str);
val = connman_device_bgscan_get_short(device);
if (val > 0)
connman_dbus_dict_append_variant(&dict,
"BgscanShortInterval", DBUS_TYPE_UINT16, &val);
val = connman_device_bgscan_get_signal_threshold(device);
if (val != 0)
connman_dbus_dict_append_variant(&dict,
"BgscanSignalThreshold", DBUS_TYPE_INT32, &val);
connman_dbus_dict_append_variant_array(&dict, "Networks",
DBUS_TYPE_OBJECT_PATH, append_networks, device);
break;
}
dbus_message_iter_close_container(&array, &dict);
return reply;
}
static gboolean powered_timeout(gpointer user_data)
{
struct connman_device *device = user_data;
_DBG_DEVICE("device %p", device);
device->timeout = 0;
if (device->pending != NULL) {
DBusMessage *reply;
reply = __connman_error_operation_timeout(device->pending);
if (reply != NULL)
g_dbus_send_message(connection, reply);
dbus_message_unref(device->pending);
device->pending = NULL;
}
return FALSE;
}
static int check_bgscan_parameter(struct connman_device *device,
DBusMessageIter *value, int type)
{
if (device->mode == CONNMAN_DEVICE_MODE_UNKNOWN ||
device->mode == CONNMAN_DEVICE_MODE_TRANSPORT_IP)
return 0;
return (dbus_message_iter_get_arg_type(value) == type);
}
static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
DBusMessageIter iter, value;
const char *name;
_DBG_DEVICE("conn %p", conn);
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &name);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &value);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (g_str_equal(name, "Powered") == TRUE) {
connman_bool_t powered;
int type, err;
type = dbus_message_iter_get_arg_type(&value);
if (type != DBUS_TYPE_BOOLEAN)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &powered);
device->powered_persistent = powered;
__connman_profile_save_device(device);
if (device->powered == powered)
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
if (device->pending != NULL)
return __connman_error_in_progress(msg);
err = set_powered(device, powered);
if (err < 0) {
if (err != -EINPROGRESS)
return __connman_error_failed(msg, -err);
device->pending = dbus_message_ref(msg);
device->timeout = g_timeout_add_seconds(15,
powered_timeout, device);
return NULL;
}
} else if (g_str_equal(name, "ScanInterval") == TRUE) {
connman_uint16_t interval;
if (!check_bgscan_parameter(device, &value, DBUS_TYPE_UINT16))
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &interval);
if (device->scan_interval != interval) {
device->scan_interval = interval;
__connman_profile_save_device(device);
connman_element_update(&device->element);
if (device->scan_timeout != 0)
bgscan_enable(device);
}
} else if (g_str_equal(name, "BgscanMethod") == TRUE) {
const char *method;
if (!check_bgscan_parameter(device, &value, DBUS_TYPE_STRING))
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &method);
if (g_strcmp0(method, device->scan_method) != 0) {
g_free(device->scan_method);
device->scan_method = g_strdup(method);
__connman_profile_save_device(device);
connman_element_update(&device->element);
}
} else if (g_str_equal(name, "BgscanShortInterval") == TRUE) {
connman_uint16_t interval;
if (!check_bgscan_parameter(device, &value, DBUS_TYPE_UINT16))
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &interval);
if (device->scan_short != interval) {
device->scan_short = interval;
__connman_profile_save_device(device);
connman_element_update(&device->element);
}
} else if (g_str_equal(name, "BgscanSignalThreshold") == TRUE) {
int threshold;
if (!check_bgscan_parameter(device, &value, DBUS_TYPE_INT32))
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &threshold);
if (device->signal_threshold != threshold) {
device->signal_threshold = threshold;
__connman_profile_save_device(device);
connman_element_update(&device->element);
}
} else if (g_str_equal(name, "Cellular.AllowRoaming") &&
device->type == CONNMAN_DEVICE_TYPE_CELLULAR) {
int type;
dbus_bool_t allow;
type = dbus_message_iter_get_arg_type(&value);
if (type != DBUS_TYPE_BOOLEAN)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &allow);
if (allow != device->cellular.roaming_allowed) {
device->cellular.roaming_allowed = allow;
__connman_profile_save_device(device);
connman_dbus_send_property_changed_variant(device->element.path,
CONNMAN_DEVICE_INTERFACE,
"Cellular.AllowRoaming",
DBUS_TYPE_BOOLEAN, &allow);
}
if (!connman_device_roaming_allowed(device) &&
device->network != NULL) {
struct connman_service *service =
connman_service_lookup_from_network(device->network);
if (service != NULL)
__connman_service_disconnect_if_roaming(service);
}
} else
return __connman_error_invalid_property(msg);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
DBusMessageIter iter;
const char *name;
_DBG_DEVICE("conn %p", conn);
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &name);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (g_str_equal(name, "ScanInterval") == TRUE) {
device->scan_interval = SCAN_INTERVAL_UNSET;
__connman_profile_save_device(device);
connman_element_update(&device->element);
if (device->scan_timeout != 0)
bgscan_enable(device);
} else if (g_str_equal(name, "BgscanMethod") == TRUE) {
g_free(device->scan_method);
device->scan_method = NULL;
__connman_profile_save_device(device);
connman_element_update(&device->element);
} else if (g_str_equal(name, "BgscanShortInterval") == TRUE) {
device->scan_short = SCAN_INTERVAL_UNSET;
__connman_profile_save_device(device);
connman_element_update(&device->element);
} else if (g_str_equal(name, "BgscanSignalThreshold") == TRUE) {
device->signal_threshold = SIGNAL_THRESHOLD_UNSET;
__connman_profile_save_device(device);
connman_element_update(&device->element);
} else
return __connman_error_invalid_property(msg);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *propose_scan(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
int err;
_DBG_DEVICE("conn %p", conn);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
return __connman_error_not_supported(msg);
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
break;
}
err = connman_device_scan(device);
if (err < 0)
return __connman_error_failed(msg, -err);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusMessage *add_ipconfig(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
struct connman_ipconfig *ipconfig;
enum connman_ipconfig_type type;
const char *str;
_DBG_DEVICE("conn %p", conn);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &str,
DBUS_TYPE_INVALID) == FALSE)
return NULL;
if (__connman_ipconfig_parse_type(str, &type) == FALSE)
return __connman_error_invalid_arguments(msg);
ipconfig = connman_device_add_ipconfig(device, type);
if (ipconfig == NULL)
return __connman_error_invalid_arguments(msg); /* XXX */
str = connman_ipconfig_get_path(ipconfig);
return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &str,
DBUS_TYPE_INVALID);
}
static const char *error_to_dbus_error_name(enum connman_element_error error)
{
const char *errname;
if (error == CONNMAN_ELEMENT_ERROR_PIN_REQUIRED)
errname = CONNMAN_ERROR_INTERFACE ".PinRequired";
else if (error == CONNMAN_ELEMENT_ERROR_PIN_BLOCKED)
errname = CONNMAN_ERROR_INTERFACE ".PinBlocked";
else if (error == CONNMAN_ELEMENT_ERROR_INCORRECT_PIN)
errname = CONNMAN_ERROR_INTERFACE ".IncorrectPin";
else
errname = CONNMAN_ERROR_INTERFACE ".InternalError";
return errname;
}
static void pin_method_callback(enum connman_element_error error,
struct connman_dbus_method_callback *cb,
void *data)
{
DBusError dberror;
_DBG_DEVICE("%s error: %d", connman_dbus_callback_method_name(cb), error);
dbus_error_init(&dberror);
if (error != CONNMAN_ELEMENT_ERROR_NO_ERROR) {
dbus_set_error_const(&dberror,
error_to_dbus_error_name(error),
"PIN error");
}
connman_dbus_callback_send_reply(cb, &dberror);
dbus_error_free(&dberror);
connman_dbus_free_callback(cb);
}
static DBusMessage *require_pin(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
struct connman_dbus_method_callback *callback;
const char *pin;
connman_bool_t require;
int err;
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &pin,
DBUS_TYPE_BOOLEAN, &require,
DBUS_TYPE_INVALID) == FALSE)
return __connman_error_invalid_arguments(msg);
if (device->driver == NULL || device->driver->require_pin == NULL)
return __connman_error_not_supported(msg);
callback = connman_dbus_callback_new(pin_method_callback, msg, NULL);
err = device->driver->require_pin(device, pin, require, callback);
if (err < 0) {
connman_dbus_free_callback(callback);
return __connman_error_failed(msg, -err);
}
return NULL;
}
static DBusMessage *enter_pin(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
struct connman_dbus_method_callback *callback;
const char *pin;
int err;
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &pin,
DBUS_TYPE_INVALID) == FALSE)
return __connman_error_invalid_arguments(msg);
if (device->driver == NULL || device->driver->enter_pin == NULL)
return __connman_error_not_supported(msg);
callback = connman_dbus_callback_new(pin_method_callback, msg, NULL);
err = device->driver->enter_pin(device, pin, callback);
if (err < 0) {
connman_dbus_free_callback(callback);
return __connman_error_failed(msg, -err);
}
return NULL;
}
static DBusMessage *unblock_pin(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
struct connman_dbus_method_callback *callback;
const char *unblock_code;
const char *pin;
int err;
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &unblock_code,
DBUS_TYPE_STRING, &pin,
DBUS_TYPE_INVALID) == FALSE)
return __connman_error_invalid_arguments(msg);
if (device->driver == NULL || device->driver->unblock_pin == NULL)
return __connman_error_not_supported(msg);
callback = connman_dbus_callback_new(pin_method_callback, msg, NULL);
err = device->driver->unblock_pin(device, unblock_code, pin, callback);
if (err < 0) {
connman_dbus_free_callback(callback);
return __connman_error_failed(msg, -err);
}
return NULL;
}
static DBusMessage *change_pin(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
struct connman_dbus_method_callback *callback;
const char *old_pin, *new_pin;
int err;
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
if (dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &old_pin,
DBUS_TYPE_STRING, &new_pin,
DBUS_TYPE_INVALID) == FALSE)
return __connman_error_invalid_arguments(msg);
if (device->driver == NULL || device->driver->change_pin == NULL)
return __connman_error_not_supported(msg);
callback = connman_dbus_callback_new(pin_method_callback, msg, NULL);
err = device->driver->change_pin(device, old_pin, new_pin, callback);
if (err < 0) {
connman_dbus_free_callback(callback);
return __connman_error_failed(msg, -err);
}
return NULL;
}
static void registration_callback(enum connman_element_error error,
struct connman_dbus_method_callback *cb,
void *data)
{
struct connman_device *device = data;
DBusError dberror;
_DBG_DEVICE("%s error: %d", connman_dbus_callback_method_name(cb), error);
dbus_error_init(&dberror);
if (error != CONNMAN_ELEMENT_ERROR_NO_ERROR) {
dbus_set_error_const(&dberror,
CONNMAN_ERROR_INTERFACE ".RegistrationFailed",
"Registration Failed");
g_free(device->cellular.selected_network);
device->cellular.selected_network = NULL;
} else if (device->cellular.selected_network != NULL) {
__connman_profile_save_device(device);
}
connman_device_unref(device);
connman_dbus_callback_send_reply(cb, &dberror);
dbus_error_free(&dberror);
connman_dbus_free_callback(cb);
}
static DBusMessage *register_on_network(DBusConnection *conn,
DBusMessage *msg,
void *data)
{
struct connman_device *device = data;
struct connman_dbus_method_callback *callback;
const char *network_id;
int err;
if (dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &network_id,
DBUS_TYPE_INVALID) == FALSE)
return __connman_error_invalid_arguments(msg);
if (device->driver == NULL || device->driver->register_on_network == NULL)
return __connman_error_not_supported(msg);
callback = connman_dbus_callback_new(registration_callback, msg,
connman_device_ref(device));
err = device->driver->register_on_network(device, network_id, callback);
if (err < 0) {
connman_dbus_free_callback(callback);
return __connman_error_failed(msg, -err);
}
/* Clear any previously saved selected network */
connman_device_clear_selected_network(device);
/* Selected network will be persisted if registration succeeds */
if (strlen(network_id) != 0)
device->cellular.selected_network = g_strdup(network_id);
return NULL;
}
static GDBusMethodTable device_methods[] = {
{ "GetProperties", "", "a{sv}", get_properties },
{ "SetProperty", "sv", "", set_property,
G_DBUS_METHOD_FLAG_ASYNC },
{ "ClearProperty", "s", "", clear_property },
{ "ProposeScan", "", "", propose_scan },
{ "AddIPConfig", "s", "o", add_ipconfig },
{ "Register", "s", "", register_on_network,
G_DBUS_METHOD_FLAG_ASYNC },
{ "RequirePin", "sb", "", require_pin,
G_DBUS_METHOD_FLAG_ASYNC },
{ "EnterPin", "s", "", enter_pin,
G_DBUS_METHOD_FLAG_ASYNC },
{ "UnblockPin", "ss", "", unblock_pin,
G_DBUS_METHOD_FLAG_ASYNC },
{ "ChangePin", "ss", "", change_pin,
G_DBUS_METHOD_FLAG_ASYNC },
{ },
};
static GDBusSignalTable device_signals[] = {
{ "PropertyChanged", "sv" },
{ },
};
/* NB: needed to match type signature */
static void register_ipconfig(struct connman_ipconfig *ipconfig, void *arg)
{
connman_ipconfig_register(ipconfig, arg);
}
static void append_devices(DBusMessageIter *iter, void *arg)
{
__connman_element_list(NULL, CONNMAN_ELEMENT_TYPE_DEVICE, iter);
}
static void emit_devices_signal(void)
{
connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "Device",
DBUS_TYPE_OBJECT_PATH, append_devices, NULL);
}
static int register_interface(struct connman_element *element)
{
struct connman_device *device = element->device;
_DBG_DEVICE("element %p name %s", element, element->name);
if (g_dbus_register_interface(connection, element->path,
CONNMAN_DEVICE_INTERFACE,
device_methods, device_signals,
NULL, device, NULL) == FALSE) {
connman_error("Failed to register %s device", element->path);
return -EIO;
}
device->registered = TRUE;
emit_devices_signal();
/* register ipconfig records loaded from the profile */
__connman_ipconfig_foreach(device, register_ipconfig, element);
return 0;
}
static void unregister_interface(struct connman_element *element)
{
struct connman_device *device = element->device;
_DBG_DEVICE("element %p name %s", element, element->name);
device->registered = FALSE;
emit_devices_signal();
g_dbus_unregister_interface(connection, element->path,
CONNMAN_DEVICE_INTERFACE);
}
static int setup_device(struct connman_device *device,
struct connman_device_driver *driver)
{
enum connman_service_type type;
int err;
_DBG_DEVICE("device %p driver %p", device, driver);
device->driver = driver;
err = register_interface(&device->element);
if (err < 0) {
if (device->driver->remove)
device->driver->remove(device);
device->driver = NULL;
return err;
}
type = __connman_device_get_service_type(device);
__connman_notifier_register(type);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
break;
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
if (device->carrier == TRUE)
__connman_profile_add_device(device);
break;
}
if (device->offlinemode == FALSE && device->powered_persistent == TRUE)
__connman_device_enable(device);
return 0;
}
static void probe_driver(struct connman_element *element, gpointer user_data)
{
struct connman_device_driver *driver = user_data;
_DBG_DEVICE("element %p name %s", element, element->name);
if (element->device == NULL)
return;
if (element->device->driver != NULL)
return;
if (driver->type != element->device->type)
return;
if (driver->probe(element->device) == 0)
setup_device(element->device, driver);
}
/*
* Extract the index from the ipconfig name.
*/
static int parse_ipconfig_index(const char *tag)
{
const char *cp = strrchr(tag, '_');
return (cp == NULL ? -1 : atoi(cp+1));
}
/*
* Reclaim state for ipconfig records bound to a device.
* We clear the entry from the bitmask and delete the
* in-memory copy of the ipconfig record.
*/
static void remove_ipconfig(struct connman_ipconfig *ipconfig, void *arg)
{
struct connman_device *device = arg;
struct connman_service *service =
__connman_service_lookup_from_device(device);
connman_bool_t was_connected;
int ix;
_DBG_DEVICE("device %p ipconfig %p configmask %d", device, ipconfig,
device->configmask);
ix = parse_ipconfig_index(connman_ipconfig_get_name(ipconfig));
CONNMAN_ASSERT(0 <= ix && ix < 32);
device->configmask &= ~(1<<ix);
was_connected = __connman_service_get_connecting_or_connected(service);
connman_ipconfig_delete(ipconfig);
_DBG_DEVICE("was_connected: %d now_connected: %d configmask: %d",
was_connected,
__connman_service_get_connecting_or_connected(service),
device->configmask);
if (device->connected == TRUE && was_connected == TRUE &&
__connman_service_get_connecting_or_connected(service) == FALSE &&
device->configmask != 0) {
/*
* Removing this ipconfig caused the service to disconnect
* but the underlying device is still connected and other
* ipconfigs exist. Restart the configuration process so
* the service is connected with the new ipconfig parameters.
*/
_DBG_DEVICE("reconnecting %p", device);
set_connected(device, TRUE);
}
}
/*
* Public version of remove_ipconfig.
*/
void __connman_device_remove_ipconfig(struct connman_device *device,
struct connman_ipconfig *ipconfig)
{
remove_ipconfig(ipconfig, device);
}
static void remove_device(struct connman_device *device)
{
enum connman_service_type type;
_DBG_DEVICE("device %p", device);
__connman_device_disable(device);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
break;
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
__connman_profile_remove_device(device);
break;
}
__connman_ipconfig_foreach(device, remove_ipconfig, device);
type = __connman_device_get_service_type(device);
__connman_notifier_unregister(type);
unregister_interface(&device->element);
/* NB: callers all verify device->driver is not NULL */
if (device->driver->remove != NULL)
device->driver->remove(device);
device->driver = NULL;
}
static void remove_driver(struct connman_element *element, gpointer user_data)
{
struct connman_device_driver *driver = user_data;
_DBG_DEVICE("element %p name %s", element, element->name);
if (element->device != NULL && element->device->driver == driver)
remove_device(element->device);
}
connman_bool_t __connman_device_has_driver(struct connman_device *device)
{
return (device == NULL || device->driver == NULL) ?
FALSE : device->registered;
}
static GSList *driver_list = NULL;
static gint compare_priority(gconstpointer a, gconstpointer b)
{
const struct connman_device_driver *driver1 = a;
const struct connman_device_driver *driver2 = b;
return driver2->priority - driver1->priority;
}
/**
* connman_device_driver_register:
* @driver: device driver definition
*
* Register a new device driver
*
* Returns: %0 on success
*/
int connman_device_driver_register(struct connman_device_driver *driver)
{
_DBG_DEVICE("driver %p name %s", driver, driver->name);
/* TODO(sleffler) check return value */
driver_list = g_slist_insert_sorted(driver_list, driver,
compare_priority);
__connman_element_foreach(NULL, CONNMAN_ELEMENT_TYPE_DEVICE,
probe_driver, driver);
return 0;
}
/**
* connman_device_driver_unregister:
* @driver: device driver definition
*
* Remove a previously registered device driver
*/
void connman_device_driver_unregister(struct connman_device_driver *driver)
{
_DBG_DEVICE("driver %p name %s", driver, driver->name);
driver_list = g_slist_remove(driver_list, driver);
__connman_element_foreach(NULL, CONNMAN_ELEMENT_TYPE_DEVICE,
remove_driver, driver);
}
static void unregister_network(gpointer data)
{
struct connman_network *network = data;
_DBG_DEVICE("network %p", network);
connman_element_unregister((struct connman_element *) network);
connman_network_unref(network);
}
static void device_destruct(struct connman_element *element)
{
struct connman_device *device = element->device;
_DBG_DEVICE("element %p name %s", element, element->name);
if (device->timeout > 0) {
g_source_remove(device->timeout);
device->timeout = 0;
}
if (device->pending != NULL) {
dbus_message_unref(device->pending);
device->pending = NULL;
}
/* NB: tear down rtnl monitoring */
connman_device_set_index(device, -1);
g_free(device->ident);
g_free(device->node);
g_free(device->name);
g_free(device->address);
g_free(device->control);
g_free(device->interface);
g_free(device->cellular.carrier);
g_free(device->cellular.meid);
g_free(device->cellular.imei);
g_free(device->cellular.imsi);
g_free(device->cellular.esn);
g_free(device->cellular.mdn);
g_free(device->cellular.min);
g_free(device->cellular.manufacturer);
g_free(device->cellular.model_id);
g_free(device->cellular.hardware_revision);
g_free(device->cellular.home_provider.code);
g_free(device->cellular.home_provider.name);
g_free(device->cellular.home_provider.country);
g_free(device->cellular.firmware_revision);
g_free(device->cellular.firmware_image);
g_free(device->cellular.unlock_required);
g_free(device->cellular.selected_network);
g_free(device->external_dbus_connection);
g_free(device->external_dbus_service);
g_free(device->external_dbus_object);
g_hash_table_destroy(device->networks);
if (device->cellular.found_networks != NULL)
g_ptr_array_free(device->cellular.found_networks, TRUE);
if (device->cellular.apn_list != NULL)
g_ptr_array_free(device->cellular.apn_list, TRUE);
device->networks = NULL;
}
/**
* connman_device_create:
* @node: device node name (for example an address)
* @type: device type
*
* Allocate a new device of given #type and name it #node.
*
* Returns: a newly-allocated #connman_device structure
*/
struct connman_device * connman_device_create(const char *node,
enum connman_device_type type)
{
struct connman_device *device;
const char *str;
_DBG_DEVICE("node %s type %d", node, type);
device = g_try_new0(struct connman_device, 1);
if (device == NULL) {
connman_error("%s: cannot allocate device", __func__);
return NULL;
}
device->networks = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, unregister_network);
if (device->networks == NULL) {
connman_error("%s: cannot allocate hash table", __func__);
g_free(device);
return NULL;
}
_DBG_DEVICE("device %p", device);
__connman_element_initialize(&device->element);
device->element.name = g_strdup(node);
device->element.type = CONNMAN_ELEMENT_TYPE_DEVICE;
device->element.device = device;
device->element.destruct = device_destruct;
str = type2string(type);
if (str != NULL)
connman_element_set_string(&device->element,
CONNMAN_PROPERTY_ID_TYPE, str);
device->type = type;
device->name = g_strdup(type2description(device->type));
device->mode = CONNMAN_DEVICE_MODE_UNKNOWN;
device->powered_persistent = TRUE;
device->phyindex = -1;
/* setup RTNL device rename support */
RTNL_INIT(&device->rtnl,
device->name,
CONNMAN_RTNL_PRIORITY_DEFAULT,
CONNMAN_RTNL_DEVICE_ANY, /* NB: filled in later */
device);
device->rtnl.newlink = device_newlink;
switch (type) {
case CONNMAN_DEVICE_TYPE_WIFI:
device->scan_method = NULL;
device->scan_interval = SCAN_INTERVAL_UNSET;
device->scan_short = SCAN_INTERVAL_UNSET;
device->signal_threshold = SIGNAL_THRESHOLD_UNSET;
break;
default:
device->scan_interval = 0;
break;
}
return device;
}
/**
* connman_device_ref:
* @device: device structure
*
* Increase reference counter of device
*/
struct connman_device *connman_device_ref(struct connman_device *device)
{
connman_element_ref(&device->element);
return device;
}
/**
* connman_device_unref:
* @device: device structure
*
* Decrease reference counter of device
*/
void connman_device_unref(struct connman_device *device)
{
connman_element_unref(&device->element);
}
const char *__connman_device_get_type(struct connman_device *device)
{
return type2string(device->type);
}
/**
* connman_device_get_type:
* @device: device structure
*
* Get type of device
*/
enum connman_device_type connman_device_get_type(struct connman_device *device)
{
return device->type;
}
/**
* connman_device_get_name:
* @device: device structure
*
* Get unique name of device
*/
const char *connman_device_get_name(struct connman_device *device)
{
return device->element.name;
}
/**
* connman_device_get_path:
* @device: device structure
*
* Get path name of device
*/
const char *connman_device_get_path(struct connman_device *device)
{
return device->element.path;
}
static void device_newlink(void *user_data, int index, unsigned short type,
const char *ifname, unsigned flags, int change)
{
struct connman_device *device = user_data;
if (g_strcmp0(device->interface, ifname) != 0) {
_DBG_DEVICE("device %p ifname %s -> %s", device,
device->interface, ifname);
connman_device_set_interface(device, ifname, device->control);
}
}
/**
* connman_device_set_index:
* @device: device structure
* @index: index number
*
* Set index number of device
*/
void connman_device_set_index(struct connman_device *device, int index)
{
if (index == device->element.index)
return;
/*
* Setup/teardown the RTNL newlink monitor callback
* used to track device renaming.
*/
if (device->element.index == -1) {
device->rtnl.index = index;
connman_rtnl_register(&device->rtnl);
}
device->element.index = index;
if (index == -1) {
connman_rtnl_unregister(&device->rtnl);
device->rtnl.index = CONNMAN_RTNL_DEVICE_ANY;
}
}
/**
* connman_device_get_index:
* @device: device structure
*
* Get index number of device
*/
int connman_device_get_index(struct connman_device *device)
{
return device->element.index;
}
int __connman_device_get_phyindex(struct connman_device *device)
{
return device->phyindex;
}
void __connman_device_set_phyindex(struct connman_device *device,
int phyindex)
{
device->phyindex = phyindex;
}
/**
* connman_device_get_interface:
* @device: device structure
*
* Get interface name for device
*/
const char *connman_device_get_interface(struct connman_device *device)
{
return device->interface;
}
/**
* connman_device_set_interface:
* @device: device structure
* @interface: interface name
* @control: control interface
*
* Set interface name of device
*/
void connman_device_set_interface(struct connman_device *device,
const char *interface, const char *control)
{
g_free(device->element.devname);
device->element.devname = g_strdup(interface);
g_free(device->interface);
device->interface = g_strdup(interface);
g_free(device->control);
device->control = g_strdup(control);
if (device->name == NULL) {
const char *str = type2description(device->type);
if (str != NULL && device->interface != NULL)
device->name = g_strdup_printf("%s (%s)", str,
device->interface);
}
}
const char *connman_device_get_control(struct connman_device *device)
{
return device->control;
}
/**
* connman_device_set_ident:
* @device: device structure
* @ident: unique identifier
*
* Set unique identifier of device
*/
void connman_device_set_ident(struct connman_device *device, const char *ident)
{
g_free(device->ident);
device->ident = g_strdup(ident);
}
const char *__connman_device_get_ident(struct connman_device *device)
{
return device->ident;
}
/**
* connman_device_set_mode:
* @device: device structure
* @mode: network mode
*
* Change network mode of device
*/
void connman_device_set_mode(struct connman_device *device,
enum connman_device_mode mode)
{
device->mode = mode;
}
/**
* connman_device_get_mode:
* @device: device structure
*
* Get network mode of device
*/
enum connman_device_mode connman_device_get_mode(struct connman_device *device)
{
return device->mode;
}
/**
* connman_device_get_selected_network:
* @device: device structure
*
* Get the network ID of the cellular network, if any, on which
* the user requested manual registration.
*/
const char *connman_device_get_selected_network(struct connman_device *device)
{
return device->cellular.selected_network;
}
/**
* connman_device_clear_selected_network:
* @device: device structure
*
* Forget any previously selected cellular network ID, thus causing
* the next network reigstration attempt to be automatic (i.e., on
* the home network).
*/
void connman_device_clear_selected_network(struct connman_device *device)
{
g_free(device->cellular.selected_network);
device->cellular.selected_network = NULL;
__connman_profile_save_device(device);
}
/**
* connman_device_set_powered:
* @device: device structure
* @powered: powered state
*
* Change power state of device
*/
int connman_device_set_powered(struct connman_device *device,
connman_bool_t powered)
{
enum connman_service_type type;
_DBG_DEVICE("driver %p powered %d", device, powered);
if (device->timeout > 0) {
g_source_remove(device->timeout);
device->timeout = 0;
}
if (device->pending != NULL) {
g_dbus_send_reply(connection, device->pending,
DBUS_TYPE_INVALID);
dbus_message_unref(device->pending);
device->pending = NULL;
}
bgscan_disable(device);
if (device->powered == powered)
return -EALREADY;
device->powered = powered;
device->powered_pending = powered;
type = __connman_device_get_service_type(device);
if (device->powered == TRUE)
__connman_notifier_enable(type);
else
__connman_notifier_disable(type);
if (device->registered == FALSE)
return 0;
powered_changed(device);
if (device->powered == TRUE &&
device->type != CONNMAN_DEVICE_TYPE_CELLULAR) {
connman_device_scan(device);
bgscan_enable(device);
}
return 0;
}
void connman_device_set_powered_failed(struct connman_device *device,
enum connman_element_error error)
{
_DBG_DEVICE("device %s error %d", device->ident, error);
if (device->timeout > 0) {
g_source_remove(device->timeout);
device->timeout = 0;
}
if (device->pending != NULL) {
const char *errname = error_to_dbus_error_name(error);
const char *msg = device->powered_pending ?
"Enable Failure" : "Disable Failure";
DBusMessage *reply;
reply = g_dbus_create_error(device->pending, errname, msg);
g_dbus_send_message(connection, reply);
dbus_message_unref(device->pending);
device->pending = NULL;
}
device->powered_pending = device->powered;
}
int __connman_device_set_blocked(struct connman_device *device,
connman_bool_t blocked)
{
connman_bool_t powered;
_DBG_DEVICE("device %p blocked %d", device, blocked);
device->blocked = blocked;
if (device->offlinemode == TRUE)
return 0;
if (blocked == FALSE)
powered = device->powered_persistent;
else
powered = FALSE;
return set_powered(device, powered);
}
/**
* connman_device_set_carrier:
* @device: device structure
* @carrier: carrier state
*
* Change carrier state of device (only for device without scanning)
*/
int connman_device_set_carrier(struct connman_device *device,
connman_bool_t carrier)
{
_DBG_DEVICE("device %p carrier %d", device, carrier);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
return -EINVAL;
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
break;
}
if (device->carrier == carrier)
return -EALREADY;
device->carrier = carrier;
return set_carrier(device, device->carrier);
}
/**
* connman_device_bgscan_get_short:
* @device: device structure
*
* Return interval (in seconds) for urgent bgscan's done when we believe
* we want to roam (e.g. the signal level drops below the threshold).
*/
int connman_device_bgscan_get_short(const struct connman_device *device)
{
if (device->scan_short == SCAN_INTERVAL_UNSET)
return DEFAULT_SCAN_SHORT;
return device->scan_short;
}
/**
* connman_device_bgscan_get_long:
* @device: device structure
*
* Return interval (in seconds) for slow bgscan's done when we our
* connection is satisfactory (e.g. the signal level is above the threshold).
*/
int connman_device_bgscan_get_long(const struct connman_device *device)
{
if (device->scan_interval == SCAN_INTERVAL_UNSET)
return DEFAULT_SCAN_INTERVAL;
return device->scan_interval;
}
/**
* connman_device_bgscan_get_method:
* @device: device structure
*
* Return background scan method (string) to use. This function can return
* NULL which means "use default". It is up to the plugin to interpret this
* string and determine what to do if it does not recognize the string.
*/
const char *connman_device_bgscan_get_method(const struct
connman_device *device)
{
if (device->scan_method == NULL)
return DEFAULT_SCAN_METHOD;
return device->scan_method;
}
/**
* connman_device_bgscan_get_signal_threshold:
* @device: device structure
*
* Return signal level threshold (in dBm) at which we proactively scan
* for a new network.
*/
int connman_device_bgscan_get_signal_threshold(const struct connman_device *device)
{
if (device->signal_threshold == SIGNAL_THRESHOLD_UNSET)
return DEFAULT_SIGNAL_THRESHOLD;
return device->signal_threshold;
}
/**
* connman_device_scan:
* @device: device structure
*
* Kick off a scan for the specified device. The device must have
* a driver that supports scan requests and must be pwoered up.
*/
int connman_device_scan(struct connman_device *device)
{
if (device->driver == NULL || device->driver->scan == NULL)
return -EOPNOTSUPP;
if (device->powered == FALSE)
return -ENOLINK;
/*
* If bgscan is active push the next scan forward so
* we don't fire multiple scans too quickly.
*/
if (device->scan_timeout != 0)
bgscan_enable(device);
return device->driver->scan(device);
}
void connman_device_auto_connect(struct connman_device *device)
{
__connman_service_device_auto_connect(device);
}
int __connman_device_enable(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
if (device->driver == NULL || device->driver->enable == NULL)
return -EOPNOTSUPP;
return set_powered(device, TRUE);
}
int __connman_device_enable_persistent(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
device->powered_persistent = TRUE;
__connman_profile_save_device(device);
return __connman_device_enable(device);
}
int __connman_device_disable(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
if (device->driver == NULL || device->driver->disable == NULL)
return -EOPNOTSUPP;
if (device->powered == FALSE)
return -ENOLINK;
return set_powered(device, FALSE);
}
int __connman_device_disable_persistent(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
device->powered_persistent = FALSE;
__connman_profile_save_device(device);
return __connman_device_disable(device);
}
int __connman_device_connect(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
if (device->connected == TRUE)
return -EALREADY;
if (device->driver != NULL && device->driver->connect != NULL)
device->driver->connect(device);
/* TODO(sleffler) else connman_device_set_connected(device, TRUE)? */
return 0;
}
int __connman_device_disconnect(struct connman_device *device)
{
GHashTableIter iter;
gpointer key, value;
_DBG_DEVICE("device %p connected %d", device, device->connected);
if (device->connected == FALSE)
return -EALREADY;
g_hash_table_iter_init(&iter, device->networks);
while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
struct connman_network *network = value;
__connman_network_disconnect(network);
}
if (device->driver != NULL && device->driver->disconnect != NULL)
device->driver->disconnect(device);
else
connman_device_set_connected(device, FALSE);
return 0;
}
/**
* connman_device_set_scanning_state:
* @device: device structure
* @scanning: scanning state
*
* Change scanning state of device
*/
int connman_device_set_scanning_state(struct connman_device *device,
connman_bool_t scanning)
{
_DBG_DEVICE("device %p scanning %d", device, scanning);
if (device->driver == NULL || device->driver->scan == NULL)
return -EINVAL;
if (device->scanning == scanning)
return -EALREADY;
device->scanning = scanning;
/* NB: connman_dbus_send_property_changed logs msg on failure */
(void) connman_dbus_send_property_changed_variant(device->element.path,
CONNMAN_DEVICE_INTERFACE, "Scanning", DBUS_TYPE_BOOLEAN, &scanning);
return 0;
}
static void mark_network_unavailable(gpointer key, gpointer value,
gpointer user_data)
{
struct connman_network *network = value;
if (connman_network_get_in_use(network) == FALSE)
connman_network_set_available(network, FALSE);
}
void __connman_device_mark_networks_unavailable(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
g_hash_table_foreach(device->networks, mark_network_unavailable, NULL);
}
static gboolean remove_unavailable_network(gpointer key, gpointer value,
gpointer user_data)
{
struct connman_network *network = value;
return (connman_network_get_in_use(network) == FALSE &&
connman_network_get_available(network) == FALSE);
}
void __connman_device_cleanup_networks(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
g_hash_table_foreach_remove(device->networks,
remove_unavailable_network, NULL);
}
/**
* connman_device_flush_networks:
* @device: device structure
*
* Remove all !connected networks.
*/
void connman_device_flush_networks(struct connman_device *device)
{
__connman_device_mark_networks_unavailable(device);
__connman_device_cleanup_networks(device);
}
/**
* connman_device_set_scanning:
* @device: device structure
* @scanning: scanning state (TRUE = started, FALSE = completed)
*
* Change scanning state of device and manage associated
* networks for devices that do not provide incremental
* state updates. Specifically, when a scan is started
* all networks are marked "unavailable" and when the scan
* completes any unavailable networks are cleared.
*
* When a scan completes and the device is not connected
* the autoconnect process is scheduled.
*/
int connman_device_set_scanning(struct connman_device *device,
connman_bool_t scanning)
{
int err;
_DBG_DEVICE("device %p scanning %d", device, scanning);
err = connman_device_set_scanning_state(device, scanning);
if (err != 0)
return err;
if (scanning == TRUE) {
__connman_device_mark_networks_unavailable(device);
} else {
__connman_device_cleanup_networks(device);
if (device->connected == FALSE)
__connman_service_device_auto_connect(device);
}
return 0;
}
/**
* connman_device_set_connected:
* @device: device structure
* @connected: connected state
*
* Change connected state of device
*/
int
connman_device_set_connected(struct connman_device *device,
connman_bool_t connected)
{
_DBG_DEVICE("device %p carrier %d connected %d -> %d",
device, device->carrier, device->connected, connected);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
if (device->connected == connected)
return -EALREADY;
return set_connected(device, connected);
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
/* TODO(sleffler) device->connected == connected? */
if (device->carrier == FALSE)
return -ENOTCONN;
return set_connected(device, connected);
default:
return -EINVAL;
}
}
/**
* connman_device_set_string:
* @device: device structure
* @key: unique identifier
* @value: string value
*
* Set string value for specific key
*/
int connman_device_set_string(struct connman_device *device, const char *key,
const char *value)
{
_DBG_DEVICE("device %p key %s value %s", device, key,
connman_log_get_masked_value(key, value));
if (g_str_equal(key, "Address") == TRUE) {
g_free(device->address);
device->address = g_strdup(value);
} else if (g_str_equal(key, "Name") == TRUE) {
g_free(device->name);
device->name = g_strdup(value);
} else if (g_str_equal(key, "Node") == TRUE) {
g_free(device->node);
device->node = g_strdup(value);
} else if (g_str_equal(key, "Cellular.Carrier") == TRUE) {
g_free(device->cellular.carrier);
device->cellular.carrier = g_strdup(value);
} else if (g_str_equal(key, "Cellular.MEID") == TRUE) {
g_free(device->cellular.meid);
device->cellular.meid = g_strdup(value);
} else if (g_str_equal(key, "Cellular.IMEI") == TRUE) {
g_free(device->cellular.imei);
device->cellular.imei = g_strdup(value);
} else if (g_str_equal(key, "Cellular.IMSI") == TRUE) {
g_free(device->cellular.imsi);
device->cellular.imsi = g_strdup(value);
} else if (g_str_equal(key, "Cellular.ESN") == TRUE) {
g_free(device->cellular.esn);
device->cellular.esn = g_strdup(value);
} else if (g_str_equal(key, "Cellular.MDN") == TRUE) {
g_free(device->cellular.mdn);
device->cellular.mdn = g_strdup(value);
} else if (g_str_equal(key, "Cellular.MIN") == TRUE) {
g_free(device->cellular.min);
device->cellular.min = g_strdup(value);
} else if (g_str_equal(key, "Cellular.ModelID") == TRUE) {
g_free(device->cellular.model_id);
device->cellular.model_id = g_strdup(value);
} else if (g_str_equal(key, "Cellular.Manufacturer") == TRUE) {
g_free(device->cellular.manufacturer);
device->cellular.manufacturer = g_strdup(value);
} else if (g_str_equal(key, "Cellular.FirmwareRevision") == TRUE) {
g_free(device->cellular.firmware_revision);
device->cellular.firmware_revision = g_strdup(value);
} else if (g_str_equal(key, "Cellular.FirmwareImageName") == TRUE) {
g_free(device->cellular.firmware_image);
device->cellular.firmware_image = g_strdup(value);
} else if (g_str_equal(key, "Cellular.HardwareRevision") == TRUE) {
g_free(device->cellular.hardware_revision);
device->cellular.hardware_revision = g_strdup(value);
} else if (g_str_equal(key, "DBus.Connection") == TRUE) {
g_free(device->external_dbus_connection);
device->external_dbus_connection = g_strdup(value);
} else if (g_str_equal(key, "DBus.Service") == TRUE) {
g_free(device->external_dbus_service);
device->external_dbus_service = g_strdup(value);
} else if (g_str_equal(key, "DBus.Object") == TRUE) {
g_free(device->external_dbus_object);
device->external_dbus_object = g_strdup(value);
}
return connman_element_set_string(&device->element, key, value);
}
/**
* connman_device_get_string:
* @device: device structure
* @key: unique identifier
*
* Get string value for specific key
*/
const char *connman_device_get_string(struct connman_device *device,
const char *key)
{
_DBG_DEVICE("device %p key %s", device, key);
if (g_str_equal(key, "Address") == TRUE)
return device->address;
else if (g_str_equal(key, "Name") == TRUE)
return device->name;
else if (g_str_equal(key, "Node") == TRUE)
return device->node;
else if (g_str_equal(key, "Cellular.Carrier") == TRUE)
return device->cellular.carrier;
else if (g_str_equal(key, "Cellular.MEID") == TRUE)
return device->cellular.meid;
else if (g_str_equal(key, "Cellular.IMEI") == TRUE)
return device->cellular.imei;
else if (g_str_equal(key, "Cellular.IMSI") == TRUE)
return device->cellular.imsi;
else if (g_str_equal(key, "Cellular.ESN") == TRUE)
return device->cellular.esn;
else if (g_str_equal(key, "Cellular.MDN") == TRUE)
return device->cellular.mdn;
else if (g_str_equal(key, "Cellular.MIN") == TRUE)
return device->cellular.min;
else if (g_str_equal(key, "Cellular.ModelID") == TRUE)
return device->cellular.model_id;
else if (g_str_equal(key, "Cellular.Manufacturer") == TRUE)
return device->cellular.manufacturer;
else if (g_str_equal(key, "Cellular.FirmwareRevision") == TRUE)
return device->cellular.firmware_revision;
else if (g_str_equal(key, "Cellular.FirmwareImageName") == TRUE)
return device->cellular.firmware_image;
else if (g_str_equal(key, "Cellular.HardwareRevision") == TRUE)
return device->cellular.hardware_revision;
else if (g_str_equal(key, "DBus.Connection") == TRUE)
return device->external_dbus_connection;
else if (g_str_equal(key, "DBus.Service") == TRUE)
return device->external_dbus_service;
else if (g_str_equal(key, "DBus.Object") == TRUE)
return device->external_dbus_object;
return connman_element_get_string(&device->element, key);
}
void connman_device_set_cellular_family(struct connman_device *device,
enum connman_device_cellular_family family)
{
device->cellular.family = family;
}
void connman_device_set_prl_version(struct connman_device *device,
guint prl_version)
{
device->cellular.prl_version = prl_version;
}
void connman_device_set_unlock_properties(struct connman_device *device,
const char *unlock_required,
guint unlock_retries,
gboolean lock_enabled)
{
g_free(device->cellular.unlock_required);
device->cellular.unlock_required = g_strdup(unlock_required);
device->cellular.unlock_retries = unlock_retries;
device->cellular.lock_enabled = lock_enabled;
connman_dbus_send_property_changed_dict(device->element.path,
CONNMAN_DEVICE_INTERFACE,
"Cellular.SIMLockStatus",
append_unlock_status,
device);
}
void connman_device_set_found_networks(struct connman_device *device,
GPtrArray *networks)
{
if (device->cellular.found_networks != NULL)
g_ptr_array_free(device->cellular.found_networks, TRUE);
device->cellular.found_networks = networks;
connman_dbus_send_property_changed_container(device->element.path,
CONNMAN_DEVICE_INTERFACE,
"Cellular.FoundNetworks",
kStringDictArraySig,
append_string_dict_array,
device->cellular.found_networks);
}
void connman_device_set_apn_list(struct connman_device *device,
GPtrArray *apn_list)
{
if (device->cellular.apn_list != NULL)
g_ptr_array_free(device->cellular.apn_list, TRUE);
device->cellular.apn_list = apn_list;
connman_dbus_send_property_changed_container(device->element.path,
CONNMAN_DEVICE_INTERFACE,
"Cellular.APNList",
kStringDictArraySig,
append_string_dict_array,
device->cellular.apn_list);
}
void connman_device_set_provider_requires_roaming(struct connman_device *device,
connman_bool_t requires_roaming)
{
device->cellular.provider_requires_roaming = requires_roaming;
}
static void set_offlinemode(struct connman_element *element, gpointer user_data)
{
struct connman_device *device = element->device;
connman_bool_t offlinemode = GPOINTER_TO_UINT(user_data);
connman_bool_t powered;
_DBG_DEVICE("element %p name %s", element, element->name);
if (device == NULL)
return;
device->offlinemode = offlinemode;
powered = (offlinemode == TRUE) ? FALSE : TRUE;
if (device->powered == powered)
return;
if (device->powered_persistent == FALSE)
powered = FALSE;
set_powered(device, powered);
}
int __connman_device_set_offlinemode(connman_bool_t offlinemode)
{
_DBG_DEVICE("offlinmode %d", offlinemode);
__connman_element_foreach(NULL, CONNMAN_ELEMENT_TYPE_DEVICE,
set_offlinemode, GUINT_TO_POINTER(offlinemode));
__connman_notifier_offlinemode(offlinemode);
return 0;
}
/**
* __connman_device_set_operator:
* @device: device structure
* @info: operator structure
*
* Set operator information structure for the service
*/
void connman_device_set_home_provider(struct connman_device *device,
struct connman_network_operator *op)
{
g_free(device->cellular.home_provider.name);
device->cellular.home_provider.name = g_strdup(op->name);
g_free(device->cellular.home_provider.code);
device->cellular.home_provider.code = g_strdup(op->code);
g_free(device->cellular.home_provider.country);
device->cellular.home_provider.country = g_strdup(op->country);
}
struct connman_network_operator *connman_device_get_home_provider(
struct connman_device *device)
{
if (device->cellular.home_provider.name == NULL)
return NULL;
return &device->cellular.home_provider;
}
connman_bool_t connman_device_roaming_allowed(struct connman_device *device)
{
return device->cellular.roaming_allowed ||
device->cellular.provider_requires_roaming;
}
void connman_device_set_scanning_supported(struct connman_device *device,
connman_bool_t supported)
{
device->cellular.scanning_supported = supported;
}
/**
* connman_device_add_network:
* @device: device structure
* @network: network structure
*
* Add new network to the device
*/
int connman_device_add_network(struct connman_device *device,
struct connman_network *network)
{
const char *identifier = connman_network_get_identifier(network);
int err;
_DBG_DEVICE("device %p network %p", device, network);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
return -EINVAL;
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
break;
}
__connman_network_set_device(network, device);
err = connman_element_register((struct connman_element *) network,
&device->element);
if (err < 0) {
__connman_network_set_device(network, NULL);
return err;
}
g_hash_table_insert(device->networks, g_strdup(identifier), network);
return 0;
}
/**
* connman_device_get_network:
* @device: device structure
* @identifier: network identifier
*
* Get network for given identifier
*/
struct connman_network *connman_device_get_network(
struct connman_device *device, const char *identifier)
{
_DBG_DEVICE("device %p identifier %s", device, identifier);
return g_hash_table_lookup(device->networks, identifier);
}
/**
* connman_device_remove_network:
* @device: device structure
* @identifier: network identifier
*
* Remove network for given identifier
*/
int connman_device_remove_network(struct connman_device *device,
const char *identifier)
{
_DBG_DEVICE("device %p identifier %s", device, identifier);
/* NB: this implicitly calls unregister_network */
g_hash_table_remove(device->networks, identifier);
return 0;
}
struct connman_network *__connman_device_get_current_network(
struct connman_device *device)
{
return device->network;
}
void __connman_device_set_current_network(struct connman_device *device,
struct connman_network *network)
{
const char *bssid = NULL;
_DBG_DEVICE("device %p network %p existing %p", device,
network, device->network);
if (network != NULL)
bssid = connman_network_get_string(network, "Address");
if (device->network == network) {
if (network == NULL)
return;
/*
* NB: roaming for old supplicant clobbers the bssid of the
* current network so we must compare addresses.
*/
if (g_strcmp0(device->bssid, bssid) != 0) {
connman_info("%s: ROAMING %s -> %s", device->interface,
device->bssid, bssid);
g_free(device->bssid);
device->bssid = g_strdup(bssid);
/* NB: this will kick off L3 re-configuration */
set_connected(device, TRUE);
} else {
/*
* Same BSSID; this is what happens when we
* re-associate (e.g. due to idle timeout).
*/
connman_ipconfig_renew(device);
}
return;
}
if (network != NULL && device->network != NULL) {
/*
* Roaming with new supplicant plugin; we get a proper
* network handle instead of clobbering the bssid of the
* same network object. Same tasks as above, just more
* straightforward
*/
connman_info("%s: ROAMING %s -> %s", device->interface,
device->bssid, bssid);
connman_network_unref(device->network);
device->network = connman_network_ref(network);
g_free(device->bssid);
device->bssid = g_strdup(bssid);
set_connected(device, TRUE);
} else {
/*
* Not roaming; either clear state or setup fresh.
*/
if (device->network != NULL)
connman_network_unref(device->network);
if (network != NULL) {
device->network = connman_network_ref(network);
g_free(device->bssid);
device->bssid = g_strdup(bssid);
connman_device_set_connected(device, TRUE);
} else {
g_free(device->bssid);
device->bssid = NULL;
connman_device_set_connected(device, FALSE);
/*
* Don't null out the network until after the
* preceding call to connman_device_set_connected.
* The latter results in the ipconfig being released,
* which may remove the default gateway. When that
* happens, we want to notify the service associated
* with the device, but we will be unable to find it
* if the link between device and network has been
* broken.
*/
device->network = NULL;
}
}
}
void __connman_device_set_reconnect(struct connman_device *device,
connman_bool_t onoff)
{
device->reconnect = onoff;
}
connman_bool_t __connman_device_get_reconnect(struct connman_device *device)
{
return device->reconnect;
}
/**
* connman_device_register:
* @device: device structure
*
* Register device with the system
*/
int connman_device_register(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
__connman_profile_load_device(device);
device->offlinemode = __connman_profile_get_offlinemode();
return connman_element_register(&device->element, NULL);
}
/**
* connman_device_unregister:
* @device: device structure
*
* Unregister device with the system
*/
void connman_device_unregister(struct connman_device *device)
{
_DBG_DEVICE("device %p", device);
__connman_profile_save_device(device);
connman_element_unregister(&device->element);
}
/**
* connman_device_get_data:
* @device: device structure
*
* Get private device data pointer
*/
void *connman_device_get_data(struct connman_device *device)
{
return device->driver_data;
}
/**
* connman_device_set_data:
* @device: device structure
* @data: data pointer
*
* Set private device data pointer
*/
void connman_device_set_data(struct connman_device *device, void *data)
{
device->driver_data = data;
}
static gboolean match_driver(const struct connman_device *device,
const struct connman_device_driver *driver)
{
return (device->type == driver->type ||
driver->type == CONNMAN_DEVICE_TYPE_UNKNOWN);
}
static int device_probe(struct connman_element *element)
{
struct connman_device *device = element->device;
GSList *list;
_DBG_DEVICE("element %p name %s", element, element->name);
if (device == NULL)
return -ENODEV;
if (device->driver != NULL)
return -EALREADY;
for (list = driver_list; list; list = list->next) {
struct connman_device_driver *driver = list->data;
if (match_driver(device, driver) == FALSE)
continue;
_DBG_DEVICE("driver %p name %s", driver, driver->name);
if (driver->probe(device) == 0)
return setup_device(device, driver);
}
return -ENODEV;
}
static void device_remove(struct connman_element *element)
{
struct connman_device *device = element->device;
_DBG_DEVICE("element %p name %s", element, element->name);
if (device != NULL && device->driver != NULL)
remove_device(device);
}
static struct connman_driver device_driver = {
.name = "device",
.type = CONNMAN_ELEMENT_TYPE_DEVICE,
.priority = CONNMAN_DRIVER_PRIORITY_LOW,
.probe = device_probe,
.remove = device_remove,
};
/*
* Create any ipconfig records for a device.
*/
static connman_bool_t device_load_ipconfigs(struct connman_device *device,
gsize nconfigs, gchar **configs)
{
int i;
for (i = 0; i < nconfigs; i++) {
enum connman_ipconfig_type type;
struct connman_ipconfig *ipconfig;
char *str;
int ix;
/* Syntax: <unique-tag>:<method>, e.g. "ipconfig_xxx_1:dhcp" */
str = strchr(configs[i], ':');
if (str == NULL) {
connman_error("%s: no Method for %s", __func__,
configs[i]);
return FALSE;
}
*str++ = '\0';
if (!__connman_ipconfig_parse_type(str, &type)) {
connman_error("%s: unknown Method %s", __func__, str);
return FALSE;
}
ix = parse_ipconfig_index(configs[i]);
if (!(0 <= ix && ix < 32)) {
connman_error("%s: cannot parse index from ipconfig "
"name %s", __func__, configs[i]);
return FALSE;
}
if ((device->configmask & (1<<ix)) != 0) {
/* NB: just ignore for backwards compatibility */
connman_warn("%s: ignore duplicate ipconfig record "
"with name %s", __func__, configs[i]);
continue;
}
ipconfig = connman_ipconfig_create(type, configs[i], device,
device->element.index, FALSE);
if (ipconfig == NULL) {
connman_error("%s: invalid ipconfig type %s (%d)",
__func__, str, type);
return FALSE;
}
device->configmask |= 1<<ix;
}
return TRUE;
}
static int device_load(struct connman_device *device, GKeyFile *keyfile)
{
GError *error = NULL;
gchar *identifier;
gchar *method;
gchar *selected_network;
connman_bool_t powered;
connman_bool_t roaming_allowed;
int val;
gchar **configs;
gsize nconfigs;
int err = 0;
_DBG_DEVICE("device %p", device);
identifier = g_strdup_printf("device_%s", device->element.name);
if (identifier == NULL) {
err = -ENOMEM;
goto done;
}
if (g_key_file_has_group(keyfile, identifier) == FALSE) {
err = -ESRCH;
goto done;
}
powered = g_key_file_get_boolean(keyfile, identifier,
"Powered", &error);
if (error == NULL)
device->powered_persistent = powered;
g_clear_error(&error);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
break;
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
val = g_key_file_get_integer(keyfile, identifier,
"ScanInterval", &error);
if (error == NULL && val > 0)
device->scan_interval = val;
g_clear_error(&error);
method = g_key_file_get_string(keyfile, identifier,
"BgscanMethod", &error);
if (error == NULL && method != NULL) {
g_free(device->scan_method);
device->scan_method = method;
}
g_clear_error(&error);
val = g_key_file_get_integer(keyfile, identifier,
"BgscanShortInterval", &error);
if (error == NULL && val > 0)
device->scan_short = val;
g_clear_error(&error);
val = g_key_file_get_integer(keyfile, identifier,
"BgscanSignalThreshold", &error);
if (error == NULL)
device->signal_threshold = val;
g_clear_error(&error);
roaming_allowed = g_key_file_get_boolean(keyfile,
identifier, "Cellular.AllowRoaming", &error);
if (error == NULL)
device->cellular.roaming_allowed = roaming_allowed;
else
device->cellular.roaming_allowed = FALSE;
g_clear_error(&error);
selected_network = g_key_file_get_string(keyfile, identifier,
"Cellular.SelectedNetwork", &error);
if (error == NULL) {
g_free(device->cellular.selected_network);
device->cellular.selected_network = selected_network;
} else {
g_free(selected_network);
}
g_clear_error(&error);
break;
}
configs = g_key_file_get_string_list(keyfile, identifier, "IPConfigs",
&nconfigs, &error);
if (error == NULL && configs != NULL)
device_load_ipconfigs(device, nconfigs, configs);
g_strfreev(configs);
g_clear_error(&error);
done:
g_free(identifier);
return err;
}
static int device_save(struct connman_device *device, GKeyFile *keyfile)
{
gchar *identifier;
const char const *paths[32];
int npaths;
int i;
_DBG_DEVICE("device %p", device);
identifier = g_strdup_printf("device_%s", device->element.name);
if (identifier == NULL)
goto done;
g_key_file_set_boolean(keyfile, identifier,
"Powered", device->powered_persistent);
switch (device->mode) {
case CONNMAN_DEVICE_MODE_UNKNOWN:
case CONNMAN_DEVICE_MODE_TRANSPORT_IP:
break;
case CONNMAN_DEVICE_MODE_NETWORK_SINGLE:
case CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE:
if (device->scan_interval != SCAN_INTERVAL_UNSET)
g_key_file_set_integer(keyfile, identifier,
"ScanInterval", device->scan_interval);
else
g_key_file_remove_key(keyfile, identifier,
"ScanInterval", NULL);
if (device->scan_method != NULL)
g_key_file_set_string(keyfile, identifier,
"BgscanMethod", device->scan_method);
else
g_key_file_remove_key(keyfile, identifier,
"BgscanMethod", NULL);
if (device->scan_short != SCAN_INTERVAL_UNSET)
g_key_file_set_integer(keyfile, identifier,
"BgscanShortInterval", device->scan_short);
else
g_key_file_remove_key(keyfile, identifier,
"BgscanShortInterval", NULL);
if (device->signal_threshold != SIGNAL_THRESHOLD_UNSET)
g_key_file_set_integer(keyfile, identifier,
"BgscanSignalThreshold", device->signal_threshold);
else
g_key_file_remove_key(keyfile, identifier,
"BgscanSignalThreshold", NULL);
if (device->type == CONNMAN_DEVICE_TYPE_CELLULAR) {
g_key_file_set_boolean(keyfile, identifier,
"Cellular.AllowRoaming",
device->cellular.roaming_allowed);
if (device->cellular.selected_network != NULL)
g_key_file_set_string(keyfile, identifier,
"Cellular.SelectedNetwork",
device->cellular.selected_network);
else
g_key_file_remove_key(keyfile, identifier,
"Cellular.SelectedNetwork", NULL);
}
break;
}
__connman_ipconfig_get_keys(device, 32, paths, &npaths);
g_key_file_set_string_list(keyfile, identifier, "IPConfigs",
paths, npaths);
for (i = 0; i < npaths; i++)
g_free((gpointer)paths[i]);
done:
g_free(identifier);
return 0;
}
static struct connman_storage device_storage = {
.name = "device",
.priority = CONNMAN_STORAGE_PRIORITY_LOW,
.device_load = device_load,
.device_save = device_save,
};
int __connman_device_init(void)
{
/* TODO(sleffler) check return values */
connection = connman_dbus_get_connection();
if (connman_storage_register(&device_storage) < 0)
connman_error("%s: failed to register storage", __func__);
return connman_driver_register(&device_driver);
}
void __connman_device_cleanup(void)
{
connman_driver_unregister(&device_driver);
connman_storage_unregister(&device_storage);
dbus_connection_unref(connection);
}