| /* |
| * |
| * Connection Manager |
| * |
| * Copyright (C) 2014 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 <ctype.h> |
| #include <gdbus.h> |
| #include <gdhcp/gdhcp.h> |
| #include <netinet/if_ether.h> |
| |
| #include <connman/agent.h> |
| |
| #include "connman.h" |
| |
| static DBusConnection *connection = NULL; |
| |
| static GHashTable *peers_table = NULL; |
| |
| static struct connman_peer_driver *peer_driver; |
| |
| struct _peers_notify { |
| int id; |
| GHashTable *add; |
| GHashTable *remove; |
| } *peers_notify; |
| |
| struct _peer_service { |
| enum connman_peer_service_type type; |
| unsigned char *data; |
| int length; |
| }; |
| |
| struct connman_peer { |
| int refcount; |
| struct connman_device *device; |
| struct connman_device *sub_device; |
| unsigned char *iface_address[ETH_ALEN]; |
| char *identifier; |
| char *name; |
| char *path; |
| enum connman_peer_state state; |
| struct connman_ipconfig *ipconfig; |
| DBusMessage *pending; |
| bool registered; |
| bool connection_master; |
| struct connman_ippool *ip_pool; |
| GDHCPServer *dhcp_server; |
| uint32_t lease_ip; |
| GSList *services; |
| }; |
| |
| static void settings_changed(struct connman_peer *peer); |
| |
| static void stop_dhcp_server(struct connman_peer *peer) |
| { |
| DBG(""); |
| |
| if (peer->dhcp_server) |
| g_dhcp_server_unref(peer->dhcp_server); |
| |
| peer->dhcp_server = NULL; |
| |
| if (peer->ip_pool) |
| __connman_ippool_unref(peer->ip_pool); |
| peer->ip_pool = NULL; |
| peer->lease_ip = 0; |
| } |
| |
| static void dhcp_server_debug(const char *str, void *data) |
| { |
| connman_info("%s: %s\n", (const char *) data, str); |
| } |
| |
| static void lease_added(unsigned char *mac, uint32_t ip) |
| { |
| GList *list, *start; |
| |
| start = list = g_hash_table_get_values(peers_table); |
| for (; list; list = list->next) { |
| struct connman_peer *temp = list->data; |
| |
| if (!memcmp(temp->iface_address, mac, ETH_ALEN)) { |
| temp->lease_ip = ip; |
| settings_changed(temp); |
| break; |
| } |
| } |
| |
| g_list_free(start); |
| } |
| |
| static gboolean dhcp_server_started(gpointer data) |
| { |
| struct connman_peer *peer = data; |
| |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_READY); |
| connman_peer_unref(peer); |
| |
| return FALSE; |
| } |
| |
| static int start_dhcp_server(struct connman_peer *peer) |
| { |
| const char *start_ip, *end_ip; |
| GDHCPServerError dhcp_error; |
| const char *broadcast; |
| const char *gateway; |
| const char *subnet; |
| int prefixlen; |
| int index; |
| int err; |
| |
| DBG(""); |
| |
| err = -ENOMEM; |
| |
| if (peer->sub_device) |
| index = connman_device_get_index(peer->sub_device); |
| else |
| index = connman_device_get_index(peer->device); |
| |
| peer->ip_pool = __connman_ippool_create(index, 2, 1, NULL, NULL); |
| if (!peer->ip_pool) |
| goto error; |
| |
| gateway = __connman_ippool_get_gateway(peer->ip_pool); |
| subnet = __connman_ippool_get_subnet_mask(peer->ip_pool); |
| broadcast = __connman_ippool_get_broadcast(peer->ip_pool); |
| start_ip = __connman_ippool_get_start_ip(peer->ip_pool); |
| end_ip = __connman_ippool_get_end_ip(peer->ip_pool); |
| |
| prefixlen = connman_ipaddress_calc_netmask_len(subnet); |
| |
| err = __connman_inet_modify_address(RTM_NEWADDR, |
| NLM_F_REPLACE | NLM_F_ACK, index, AF_INET, |
| gateway, NULL, prefixlen, broadcast); |
| if (err < 0) |
| goto error; |
| |
| peer->dhcp_server = g_dhcp_server_new(G_DHCP_IPV4, index, &dhcp_error); |
| if (!peer->dhcp_server) |
| goto error; |
| |
| g_dhcp_server_set_debug(peer->dhcp_server, |
| dhcp_server_debug, "Peer DHCP server"); |
| g_dhcp_server_set_lease_time(peer->dhcp_server, 3600); |
| g_dhcp_server_set_option(peer->dhcp_server, G_DHCP_SUBNET, subnet); |
| g_dhcp_server_set_option(peer->dhcp_server, G_DHCP_ROUTER, gateway); |
| g_dhcp_server_set_option(peer->dhcp_server, G_DHCP_DNS_SERVER, NULL); |
| g_dhcp_server_set_ip_range(peer->dhcp_server, start_ip, end_ip); |
| |
| g_dhcp_server_set_lease_added_cb(peer->dhcp_server, lease_added); |
| |
| err = g_dhcp_server_start(peer->dhcp_server); |
| if (err < 0) |
| goto error; |
| |
| g_timeout_add_seconds(0, dhcp_server_started, connman_peer_ref(peer)); |
| |
| return 0; |
| |
| error: |
| stop_dhcp_server(peer); |
| return err; |
| } |
| |
| static void reply_pending(struct connman_peer *peer, int error) |
| { |
| if (!peer->pending) |
| return; |
| |
| connman_dbus_reply_pending(peer->pending, error, NULL); |
| peer->pending = NULL; |
| } |
| |
| static void peer_free(gpointer data) |
| { |
| struct connman_peer *peer = data; |
| |
| reply_pending(peer, ENOENT); |
| |
| connman_peer_unregister(peer); |
| |
| if (peer->path) { |
| g_free(peer->path); |
| peer->path = NULL; |
| } |
| |
| if (peer->ipconfig) { |
| __connman_ipconfig_set_ops(peer->ipconfig, NULL); |
| __connman_ipconfig_set_data(peer->ipconfig, NULL); |
| __connman_ipconfig_unref(peer->ipconfig); |
| peer->ipconfig = NULL; |
| } |
| |
| stop_dhcp_server(peer); |
| |
| if (peer->device) { |
| connman_device_unref(peer->device); |
| peer->device = NULL; |
| } |
| |
| if (peer->services) |
| connman_peer_reset_services(peer); |
| |
| g_free(peer->identifier); |
| g_free(peer->name); |
| |
| g_free(peer); |
| } |
| |
| static const char *state2string(enum connman_peer_state state) |
| { |
| switch (state) { |
| case CONNMAN_PEER_STATE_UNKNOWN: |
| break; |
| case CONNMAN_PEER_STATE_IDLE: |
| return "idle"; |
| case CONNMAN_PEER_STATE_ASSOCIATION: |
| return "association"; |
| case CONNMAN_PEER_STATE_CONFIGURATION: |
| return "configuration"; |
| case CONNMAN_PEER_STATE_READY: |
| return "ready"; |
| case CONNMAN_PEER_STATE_DISCONNECT: |
| return "disconnect"; |
| case CONNMAN_PEER_STATE_FAILURE: |
| return "failure"; |
| } |
| |
| return NULL; |
| } |
| |
| static bool is_connecting(struct connman_peer *peer) |
| { |
| if (peer->state == CONNMAN_PEER_STATE_ASSOCIATION || |
| peer->state == CONNMAN_PEER_STATE_CONFIGURATION || |
| peer->pending) |
| return true; |
| |
| return false; |
| } |
| |
| static bool is_connected(struct connman_peer *peer) |
| { |
| if (peer->state == CONNMAN_PEER_STATE_READY) |
| return true; |
| |
| return false; |
| } |
| |
| static bool allow_property_changed(struct connman_peer *peer) |
| { |
| if (g_hash_table_lookup_extended(peers_notify->add, peer->path, |
| NULL, NULL)) |
| return false; |
| |
| return true; |
| } |
| |
| static void append_ipv4(DBusMessageIter *iter, void *user_data) |
| { |
| struct connman_peer *peer = user_data; |
| char trans[INET_ADDRSTRLEN+1] = {}; |
| const char *local = ""; |
| const char *remote = ""; |
| char *dhcp = NULL; |
| |
| if (!is_connected(peer)) |
| return; |
| |
| if (peer->connection_master) { |
| struct in_addr addr; |
| |
| addr.s_addr = peer->lease_ip; |
| inet_ntop(AF_INET, &addr, trans, INET_ADDRSTRLEN); |
| |
| local = __connman_ippool_get_gateway(peer->ip_pool); |
| remote = trans; |
| } else if (peer->ipconfig) { |
| local = __connman_ipconfig_get_local(peer->ipconfig); |
| |
| remote = __connman_ipconfig_get_gateway(peer->ipconfig); |
| if (!remote) { |
| remote = dhcp = __connman_dhcp_get_server_address( |
| peer->ipconfig); |
| if (!dhcp) |
| remote = ""; |
| } |
| } |
| |
| connman_dbus_dict_append_basic(iter, "Local", |
| DBUS_TYPE_STRING, &local); |
| connman_dbus_dict_append_basic(iter, "Remote", |
| DBUS_TYPE_STRING, &remote); |
| if (dhcp) |
| g_free(dhcp); |
| } |
| |
| static void append_peer_service(DBusMessageIter *iter, |
| struct _peer_service *service) |
| { |
| DBusMessageIter dict; |
| |
| connman_dbus_dict_open(iter, &dict); |
| |
| switch (service->type) { |
| case CONNMAN_PEER_SERVICE_UNKNOWN: |
| /* Should never happen */ |
| break; |
| case CONNMAN_PEER_SERVICE_WIFI_DISPLAY: |
| connman_dbus_dict_append_fixed_array(&dict, |
| "WiFiDisplayIEs", DBUS_TYPE_BYTE, |
| &service->data, service->length); |
| break; |
| } |
| |
| connman_dbus_dict_close(iter, &dict); |
| } |
| |
| static void append_peer_services(DBusMessageIter *iter, void *user_data) |
| { |
| struct connman_peer *peer = user_data; |
| DBusMessageIter container; |
| GSList *list; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, |
| NULL, &container); |
| |
| if (!peer->services) { |
| DBusMessageIter dict; |
| |
| connman_dbus_dict_open(&container, &dict); |
| connman_dbus_dict_close(&container, &dict); |
| } else { |
| for (list = peer->services; list; list = list->next) |
| append_peer_service(&container, list->data); |
| } |
| |
| dbus_message_iter_close_container(iter, &container); |
| } |
| |
| static void append_properties(DBusMessageIter *iter, struct connman_peer *peer) |
| { |
| const char *state = state2string(peer->state); |
| DBusMessageIter dict; |
| |
| connman_dbus_dict_open(iter, &dict); |
| |
| connman_dbus_dict_append_basic(&dict, "State", |
| DBUS_TYPE_STRING, &state); |
| connman_dbus_dict_append_basic(&dict, "Name", |
| DBUS_TYPE_STRING, &peer->name); |
| connman_dbus_dict_append_dict(&dict, "IPv4", append_ipv4, peer); |
| connman_dbus_dict_append_array(&dict, "Services", |
| DBUS_TYPE_DICT_ENTRY, |
| append_peer_services, peer); |
| connman_dbus_dict_close(iter, &dict); |
| } |
| |
| static void settings_changed(struct connman_peer *peer) |
| { |
| if (!allow_property_changed(peer)) |
| return; |
| |
| connman_dbus_property_changed_dict(peer->path, |
| CONNMAN_PEER_INTERFACE, "IPv4", |
| append_ipv4, peer); |
| } |
| |
| static DBusMessage *get_peer_properties(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct connman_peer *peer = data; |
| DBusMessageIter dict; |
| DBusMessage *reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &dict); |
| append_properties(&dict, peer); |
| |
| return reply; |
| } |
| |
| static void append_peer_struct(gpointer key, gpointer value, |
| gpointer user_data) |
| { |
| DBusMessageIter *array = user_data; |
| struct connman_peer *peer = value; |
| DBusMessageIter entry; |
| |
| dbus_message_iter_open_container(array, DBUS_TYPE_STRUCT, |
| NULL, &entry); |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, |
| &peer->path); |
| append_properties(&entry, peer); |
| dbus_message_iter_close_container(array, &entry); |
| } |
| |
| static void state_changed(struct connman_peer *peer) |
| { |
| const char *state; |
| |
| state = state2string(peer->state); |
| if (!state || !allow_property_changed(peer)) |
| return; |
| |
| connman_dbus_property_changed_basic(peer->path, |
| CONNMAN_PEER_INTERFACE, "State", |
| DBUS_TYPE_STRING, &state); |
| } |
| |
| static void append_existing_and_new_peers(gpointer key, |
| gpointer value, gpointer user_data) |
| { |
| struct connman_peer *peer = value; |
| DBusMessageIter *iter = user_data; |
| DBusMessageIter entry, dict; |
| |
| if (!peer || !peer->registered) |
| return; |
| |
| if (g_hash_table_lookup(peers_notify->add, peer->path)) { |
| DBG("new %s", peer->path); |
| |
| append_peer_struct(key, peer, iter); |
| g_hash_table_remove(peers_notify->add, peer->path); |
| } else if (!g_hash_table_lookup(peers_notify->remove, peer->path)) { |
| DBG("existing %s", peer->path); |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, |
| NULL, &entry); |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, |
| &peer->path); |
| connman_dbus_dict_open(&entry, &dict); |
| connman_dbus_dict_close(&entry, &dict); |
| |
| dbus_message_iter_close_container(iter, &entry); |
| } |
| } |
| |
| static void peer_append_all(DBusMessageIter *iter, void *user_data) |
| { |
| g_hash_table_foreach(peers_table, append_existing_and_new_peers, iter); |
| } |
| |
| static void append_removed(gpointer key, gpointer value, gpointer user_data) |
| { |
| DBusMessageIter *iter = user_data; |
| char *objpath = key; |
| |
| DBG("removed %s", objpath); |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &objpath); |
| } |
| |
| static void peer_append_removed(DBusMessageIter *iter, void *user_data) |
| { |
| g_hash_table_foreach(peers_notify->remove, append_removed, iter); |
| } |
| |
| static gboolean peer_send_changed(gpointer data) |
| { |
| DBusMessage *signal; |
| |
| DBG(""); |
| |
| peers_notify->id = 0; |
| |
| signal = dbus_message_new_signal(CONNMAN_MANAGER_PATH, |
| CONNMAN_MANAGER_INTERFACE, "PeersChanged"); |
| if (!signal) |
| return FALSE; |
| |
| __connman_dbus_append_objpath_dict_array(signal, |
| peer_append_all, NULL); |
| __connman_dbus_append_objpath_array(signal, |
| peer_append_removed, NULL); |
| |
| dbus_connection_send(connection, signal, NULL); |
| dbus_message_unref(signal); |
| |
| g_hash_table_remove_all(peers_notify->remove); |
| g_hash_table_remove_all(peers_notify->add); |
| |
| return FALSE; |
| } |
| |
| static void peer_schedule_changed(void) |
| { |
| if (peers_notify->id != 0) |
| return; |
| |
| peers_notify->id = g_timeout_add(100, peer_send_changed, NULL); |
| } |
| |
| static void peer_added(struct connman_peer *peer) |
| { |
| DBG("peer %p", peer); |
| |
| g_hash_table_remove(peers_notify->remove, peer->path); |
| g_hash_table_replace(peers_notify->add, peer->path, peer); |
| |
| peer_schedule_changed(); |
| } |
| |
| static void peer_removed(struct connman_peer *peer) |
| { |
| DBG("peer %p", peer); |
| |
| g_hash_table_remove(peers_notify->add, peer->path); |
| g_hash_table_replace(peers_notify->remove, g_strdup(peer->path), NULL); |
| |
| peer_schedule_changed(); |
| } |
| |
| static const char *get_dbus_sender(struct connman_peer *peer) |
| { |
| if (!peer->pending) |
| return NULL; |
| |
| return dbus_message_get_sender(peer->pending); |
| } |
| |
| static enum connman_peer_wps_method check_wpspin(struct connman_peer *peer, |
| const char *wpspin) |
| { |
| int len, i; |
| |
| if (!wpspin) |
| return CONNMAN_PEER_WPS_PBC; |
| |
| len = strlen(wpspin); |
| if (len == 0) |
| return CONNMAN_PEER_WPS_PBC; |
| |
| if (len != 8) |
| return CONNMAN_PEER_WPS_UNKNOWN; |
| for (i = 0; i < 8; i++) { |
| if (!isdigit((unsigned char) wpspin[i])) |
| return CONNMAN_PEER_WPS_UNKNOWN; |
| } |
| |
| return CONNMAN_PEER_WPS_PIN; |
| } |
| |
| static void request_authorization_cb(struct connman_peer *peer, |
| bool choice_done, const char *wpspin, |
| const char *error, void *user_data) |
| { |
| enum connman_peer_wps_method wps_method; |
| int err; |
| |
| DBG("RequestInput return, %p", peer); |
| |
| if (error) { |
| if (g_strcmp0(error, |
| "net.connman.Agent.Error.Canceled") == 0 || |
| g_strcmp0(error, |
| "net.connman.Agent.Error.Rejected") == 0) { |
| err = -EINVAL; |
| goto out; |
| } |
| } |
| |
| if (!choice_done || !peer_driver->connect) { |
| err = -EINVAL; |
| goto out; |
| } |
| |
| wps_method = check_wpspin(peer, wpspin); |
| |
| err = peer_driver->connect(peer, wps_method, wpspin); |
| if (err == -EINPROGRESS) |
| return; |
| |
| out: |
| reply_pending(peer, EIO); |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_IDLE); |
| } |
| |
| static int peer_connect(struct connman_peer *peer) |
| { |
| int err = -ENOTSUP; |
| |
| if (peer_driver->connect) |
| err = peer_driver->connect(peer, |
| CONNMAN_PEER_WPS_UNKNOWN, NULL); |
| |
| if (err == -ENOKEY) { |
| err = __connman_agent_request_peer_authorization(peer, |
| request_authorization_cb, true, |
| get_dbus_sender(peer), NULL); |
| } |
| |
| return err; |
| } |
| |
| static int peer_disconnect(struct connman_peer *peer) |
| { |
| int err = -ENOTSUP; |
| |
| connman_agent_cancel(peer); |
| reply_pending(peer, ECONNABORTED); |
| |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_DISCONNECT); |
| |
| if (peer->connection_master) |
| stop_dhcp_server(peer); |
| else |
| __connman_dhcp_stop(peer->ipconfig); |
| |
| if (peer_driver->disconnect) |
| err = peer_driver->disconnect(peer); |
| |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_IDLE); |
| |
| return err; |
| } |
| |
| static DBusMessage *connect_peer(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct connman_peer *peer = user_data; |
| GList *list, *start; |
| int err; |
| |
| DBG("peer %p", peer); |
| |
| if (peer->pending) |
| return __connman_error_in_progress(msg); |
| |
| list = g_hash_table_get_values(peers_table); |
| start = list; |
| for (; list; list = list->next) { |
| struct connman_peer *temp = list->data; |
| |
| if (temp == peer || temp->device != peer->device) |
| continue; |
| |
| if (is_connecting(temp) || is_connected(temp)) { |
| if (peer_disconnect(temp) == -EINPROGRESS) { |
| g_list_free(start); |
| return __connman_error_in_progress(msg); |
| } |
| } |
| } |
| |
| g_list_free(start); |
| |
| peer->pending = dbus_message_ref(msg); |
| |
| err = peer_connect(peer); |
| if (err == -EINPROGRESS) |
| return NULL; |
| |
| if (err < 0) { |
| dbus_message_unref(peer->pending); |
| peer->pending = NULL; |
| |
| return __connman_error_failed(msg, -err); |
| } |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| } |
| |
| static DBusMessage *disconnect_peer(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct connman_peer *peer = user_data; |
| int err; |
| |
| DBG("peer %p", peer); |
| |
| err = peer_disconnect(peer); |
| if (err < 0 && err != -EINPROGRESS) |
| return __connman_error_failed(msg, -err); |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| } |
| |
| struct connman_peer *connman_peer_create(const char *identifier) |
| { |
| struct connman_peer *peer; |
| |
| peer = g_malloc0(sizeof(struct connman_peer)); |
| peer->identifier = g_strdup(identifier); |
| peer->state = CONNMAN_PEER_STATE_IDLE; |
| |
| peer->refcount = 1; |
| |
| return peer; |
| } |
| |
| struct connman_peer *connman_peer_ref_debug(struct connman_peer *peer, |
| const char *file, int line, const char *caller) |
| { |
| DBG("%p ref %d by %s:%d:%s()", peer, peer->refcount + 1, |
| file, line, caller); |
| |
| __sync_fetch_and_add(&peer->refcount, 1); |
| |
| return peer; |
| } |
| |
| void connman_peer_unref_debug(struct connman_peer *peer, |
| const char *file, int line, const char *caller) |
| { |
| DBG("%p ref %d by %s:%d:%s()", peer, peer->refcount - 1, |
| file, line, caller); |
| |
| if (__sync_fetch_and_sub(&peer->refcount, 1) != 1) |
| return; |
| |
| if (!peer->registered && !peer->path) |
| return peer_free(peer); |
| |
| g_hash_table_remove(peers_table, peer->path); |
| } |
| |
| const char *connman_peer_get_identifier(struct connman_peer *peer) |
| { |
| if (!peer) |
| return NULL; |
| |
| return peer->identifier; |
| } |
| |
| void connman_peer_set_name(struct connman_peer *peer, const char *name) |
| { |
| g_free(peer->name); |
| peer->name = g_strdup(name); |
| } |
| |
| void connman_peer_set_iface_address(struct connman_peer *peer, |
| const unsigned char *iface_address) |
| { |
| memset(peer->iface_address, 0, ETH_ALEN); |
| memcpy(peer->iface_address, iface_address, ETH_ALEN); |
| } |
| |
| void connman_peer_set_device(struct connman_peer *peer, |
| struct connman_device *device) |
| { |
| if (!peer || !device) |
| return; |
| |
| peer->device = device; |
| connman_device_ref(device); |
| } |
| |
| struct connman_device *connman_peer_get_device(struct connman_peer *peer) |
| { |
| if (!peer) |
| return NULL; |
| |
| return peer->device; |
| } |
| |
| void connman_peer_set_sub_device(struct connman_peer *peer, |
| struct connman_device *device) |
| { |
| if (!peer || !device || peer->sub_device) |
| return; |
| |
| peer->sub_device = device; |
| } |
| |
| void connman_peer_set_as_master(struct connman_peer *peer, bool master) |
| { |
| if (!peer || !is_connecting(peer)) |
| return; |
| |
| peer->connection_master = master; |
| } |
| |
| static void dhcp_callback(struct connman_ipconfig *ipconfig, |
| struct connman_network *network, |
| bool success, gpointer data) |
| { |
| struct connman_peer *peer = data; |
| int err; |
| |
| if (!success) |
| goto error; |
| |
| DBG("lease acquired for ipconfig %p", ipconfig); |
| |
| err = __connman_ipconfig_address_add(ipconfig); |
| if (err < 0) |
| goto error; |
| |
| return; |
| |
| error: |
| __connman_ipconfig_address_remove(ipconfig); |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_FAILURE); |
| } |
| |
| static int start_dhcp_client(struct connman_peer *peer) |
| { |
| if (peer->sub_device) |
| __connman_ipconfig_set_index(peer->ipconfig, |
| connman_device_get_index(peer->sub_device)); |
| |
| __connman_ipconfig_enable(peer->ipconfig); |
| |
| return __connman_dhcp_start(peer->ipconfig, NULL, dhcp_callback, peer); |
| } |
| |
| static void report_error_cb(void *user_context, bool retry, void *user_data) |
| { |
| struct connman_peer *peer = user_context; |
| |
| if (retry) { |
| int err; |
| err = peer_connect(peer); |
| |
| if (err == 0 || err == -EINPROGRESS) |
| return; |
| } |
| |
| reply_pending(peer, ENOTCONN); |
| |
| peer_disconnect(peer); |
| |
| if (!peer->connection_master) { |
| __connman_dhcp_stop(peer->ipconfig); |
| __connman_ipconfig_disable(peer->ipconfig); |
| } else |
| stop_dhcp_server(peer); |
| |
| peer->connection_master = false; |
| peer->sub_device = NULL; |
| } |
| |
| static int manage_peer_error(struct connman_peer *peer) |
| { |
| int err; |
| |
| err = __connman_agent_report_peer_error(peer, peer->path, |
| "connect-failed", report_error_cb, |
| get_dbus_sender(peer), NULL); |
| if (err != -EINPROGRESS) { |
| report_error_cb(peer, false, NULL); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int connman_peer_set_state(struct connman_peer *peer, |
| enum connman_peer_state new_state) |
| { |
| enum connman_peer_state old_state = peer->state; |
| int err; |
| |
| DBG("peer (%s) old state %d new state %d", peer->name, |
| old_state, new_state); |
| |
| if (old_state == new_state) |
| return -EALREADY; |
| |
| switch (new_state) { |
| case CONNMAN_PEER_STATE_UNKNOWN: |
| return -EINVAL; |
| case CONNMAN_PEER_STATE_IDLE: |
| if (is_connecting(peer) || is_connected(peer)) |
| return peer_disconnect(peer); |
| peer->sub_device = NULL; |
| break; |
| case CONNMAN_PEER_STATE_ASSOCIATION: |
| break; |
| case CONNMAN_PEER_STATE_CONFIGURATION: |
| if (peer->connection_master) |
| err = start_dhcp_server(peer); |
| else |
| err = start_dhcp_client(peer); |
| if (err < 0) |
| return connman_peer_set_state(peer, |
| CONNMAN_PEER_STATE_FAILURE); |
| break; |
| case CONNMAN_PEER_STATE_READY: |
| reply_pending(peer, 0); |
| break; |
| case CONNMAN_PEER_STATE_DISCONNECT: |
| if (peer->connection_master) |
| stop_dhcp_server(peer); |
| else |
| __connman_dhcp_stop(peer->ipconfig); |
| peer->connection_master = false; |
| peer->sub_device = NULL; |
| |
| break; |
| case CONNMAN_PEER_STATE_FAILURE: |
| if (manage_peer_error(peer) == 0) |
| return 0; |
| break; |
| }; |
| |
| peer->state = new_state; |
| state_changed(peer); |
| |
| if (peer->state == CONNMAN_PEER_STATE_READY || |
| peer->state == CONNMAN_PEER_STATE_DISCONNECT) |
| settings_changed(peer); |
| |
| return 0; |
| } |
| |
| int connman_peer_request_connection(struct connman_peer *peer) |
| { |
| return __connman_agent_request_peer_authorization(peer, |
| request_authorization_cb, false, |
| NULL, NULL); |
| } |
| |
| static void peer_service_free(gpointer data) |
| { |
| struct _peer_service *service = data; |
| |
| if (!service) |
| return; |
| |
| g_free(service->data); |
| g_free(service); |
| } |
| |
| void connman_peer_reset_services(struct connman_peer *peer) |
| { |
| if (!peer) |
| return; |
| |
| g_slist_free_full(peer->services, peer_service_free); |
| peer->services = NULL; |
| } |
| |
| void connman_peer_services_changed(struct connman_peer *peer) |
| { |
| if (!peer || !peer->registered || !allow_property_changed(peer)) |
| return; |
| |
| connman_dbus_property_changed_array(peer->path, |
| CONNMAN_PEER_INTERFACE, "Services", |
| DBUS_TYPE_DICT_ENTRY, append_peer_services, peer); |
| } |
| |
| void connman_peer_add_service(struct connman_peer *peer, |
| enum connman_peer_service_type type, |
| const unsigned char *data, int data_length) |
| { |
| struct _peer_service *service; |
| |
| if (!peer || !data || type == CONNMAN_PEER_SERVICE_UNKNOWN) |
| return; |
| |
| service = g_malloc0(sizeof(struct _peer_service)); |
| service->type = type; |
| service->data = g_memdup(data, data_length * sizeof(unsigned char)); |
| service->length = data_length; |
| |
| peer->services = g_slist_prepend(peer->services, service); |
| } |
| |
| static void peer_up(struct connman_ipconfig *ipconfig, const char *ifname) |
| { |
| DBG("%s up", ifname); |
| } |
| |
| static void peer_down(struct connman_ipconfig *ipconfig, const char *ifname) |
| { |
| DBG("%s down", ifname); |
| } |
| |
| static void peer_lower_up(struct connman_ipconfig *ipconfig, |
| const char *ifname) |
| { |
| DBG("%s lower up", ifname); |
| } |
| |
| static void peer_lower_down(struct connman_ipconfig *ipconfig, |
| const char *ifname) |
| { |
| struct connman_peer *peer = __connman_ipconfig_get_data(ipconfig); |
| |
| DBG("%s lower down", ifname); |
| |
| __connman_ipconfig_disable(ipconfig); |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_DISCONNECT); |
| } |
| |
| static void peer_ip_bound(struct connman_ipconfig *ipconfig, |
| const char *ifname) |
| { |
| struct connman_peer *peer = __connman_ipconfig_get_data(ipconfig); |
| |
| DBG("%s ip bound", ifname); |
| |
| if (peer->state == CONNMAN_PEER_STATE_READY) |
| settings_changed(peer); |
| connman_peer_set_state(peer, CONNMAN_PEER_STATE_READY); |
| } |
| |
| static void peer_ip_release(struct connman_ipconfig *ipconfig, |
| const char *ifname) |
| { |
| struct connman_peer *peer = __connman_ipconfig_get_data(ipconfig); |
| |
| DBG("%s ip release", ifname); |
| |
| if (peer->state == CONNMAN_PEER_STATE_READY) |
| settings_changed(peer); |
| } |
| |
| static const struct connman_ipconfig_ops peer_ip_ops = { |
| .up = peer_up, |
| .down = peer_down, |
| .lower_up = peer_lower_up, |
| .lower_down = peer_lower_down, |
| .ip_bound = peer_ip_bound, |
| .ip_release = peer_ip_release, |
| .route_set = NULL, |
| .route_unset = NULL, |
| }; |
| |
| static struct connman_ipconfig *create_ipconfig(int index, void *user_data) |
| { |
| struct connman_ipconfig *ipconfig; |
| |
| ipconfig = __connman_ipconfig_create(index, |
| CONNMAN_IPCONFIG_TYPE_IPV4); |
| if (!ipconfig) |
| return NULL; |
| |
| __connman_ipconfig_set_method(ipconfig, CONNMAN_IPCONFIG_METHOD_DHCP); |
| __connman_ipconfig_set_data(ipconfig, user_data); |
| __connman_ipconfig_set_ops(ipconfig, &peer_ip_ops); |
| |
| return ipconfig; |
| } |
| |
| static const GDBusMethodTable peer_methods[] = { |
| { GDBUS_METHOD("GetProperties", |
| NULL, GDBUS_ARGS({ "properties", "a{sv}" }), |
| get_peer_properties) }, |
| { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, connect_peer) }, |
| { GDBUS_METHOD("Disconnect", NULL, NULL, disconnect_peer) }, |
| { }, |
| }; |
| |
| static const GDBusSignalTable peer_signals[] = { |
| { GDBUS_SIGNAL("PropertyChanged", |
| GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, |
| { }, |
| }; |
| |
| static char *get_peer_path(struct connman_device *device, |
| const char *identifier) |
| { |
| return g_strdup_printf("%s/peer/peer_%s_%s", CONNMAN_PATH, |
| connman_device_get_ident(device), identifier); |
| } |
| |
| int connman_peer_register(struct connman_peer *peer) |
| { |
| int index; |
| |
| DBG("peer %p", peer); |
| |
| if (peer->path && peer->registered) |
| return -EALREADY; |
| |
| index = connman_device_get_index(peer->device); |
| peer->ipconfig = create_ipconfig(index, peer); |
| if (!peer->ipconfig) |
| return -ENOMEM; |
| |
| peer->path = get_peer_path(peer->device, peer->identifier); |
| DBG("path %s", peer->path); |
| |
| g_hash_table_insert(peers_table, peer->path, peer); |
| |
| g_dbus_register_interface(connection, peer->path, |
| CONNMAN_PEER_INTERFACE, |
| peer_methods, peer_signals, |
| NULL, peer, NULL); |
| peer->registered = true; |
| peer_added(peer); |
| |
| return 0; |
| } |
| |
| void connman_peer_unregister(struct connman_peer *peer) |
| { |
| DBG("peer %p", peer); |
| |
| if (!peer->path || !peer->registered) |
| return; |
| |
| connman_agent_cancel(peer); |
| reply_pending(peer, EIO); |
| |
| g_dbus_unregister_interface(connection, peer->path, |
| CONNMAN_PEER_INTERFACE); |
| peer->registered = false; |
| peer_removed(peer); |
| } |
| |
| struct connman_peer *connman_peer_get(struct connman_device *device, |
| const char *identifier) |
| { |
| char *ident = get_peer_path(device, identifier); |
| struct connman_peer *peer; |
| |
| peer = g_hash_table_lookup(peers_table, ident); |
| g_free(ident); |
| |
| return peer; |
| } |
| |
| int connman_peer_driver_register(struct connman_peer_driver *driver) |
| { |
| if (peer_driver && peer_driver != driver) |
| return -EINVAL; |
| |
| peer_driver = driver; |
| |
| __connman_peer_service_set_driver(driver); |
| |
| return 0; |
| } |
| |
| void connman_peer_driver_unregister(struct connman_peer_driver *driver) |
| { |
| if (peer_driver != driver) |
| return; |
| |
| peer_driver = NULL; |
| |
| __connman_peer_service_set_driver(NULL); |
| } |
| |
| void __connman_peer_list_struct(DBusMessageIter *array) |
| { |
| g_hash_table_foreach(peers_table, append_peer_struct, array); |
| } |
| |
| const char *__connman_peer_get_path(struct connman_peer *peer) |
| { |
| if (!peer || !peer->registered) |
| return NULL; |
| |
| return peer->path; |
| } |
| |
| int __connman_peer_init(void) |
| { |
| DBG(""); |
| |
| connection = connman_dbus_get_connection(); |
| |
| peers_table = g_hash_table_new_full(g_str_hash, g_str_equal, |
| NULL, peer_free); |
| |
| peers_notify = g_new0(struct _peers_notify, 1); |
| peers_notify->add = g_hash_table_new(g_str_hash, g_str_equal); |
| peers_notify->remove = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, NULL); |
| return 0; |
| } |
| |
| void __connman_peer_cleanup(void) |
| { |
| DBG(""); |
| |
| g_hash_table_destroy(peers_notify->remove); |
| g_hash_table_destroy(peers_notify->add); |
| g_free(peers_notify); |
| |
| g_hash_table_destroy(peers_table); |
| peers_table = NULL; |
| dbus_connection_unref(connection); |
| connection = NULL; |
| } |