From d04bfa0350781ebfb8cbb2e64fabdfb2f36cd302 Mon Sep 17 00:00:00 2001 From: Nishant Chaprana Date: Fri, 10 Aug 2018 16:41:37 +0530 Subject: [connman] Added Tizen Wi-Fi Mesh Change-Id: Iec2ec99fb7d4fc6d9c0539e5a9e7c903e1969580 Signed-off-by: Nishant Chaprana --- src/mesh.c | 1660 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1660 insertions(+) create mode 100644 src/mesh.c (limited to 'src/mesh.c') diff --git a/src/mesh.c b/src/mesh.c new file mode 100644 index 00000000..b9029343 --- /dev/null +++ b/src/mesh.c @@ -0,0 +1,1660 @@ +/* + * + * Connection Manager + * + * + * Copyright (C) 2017 Samsung Electronics Co., Ltd. + * + * 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 +#endif + +#include +#include + +#include +#include "connman.h" +#include +#include +#include +#include +#include +#include +#include "mesh-netlink.h" + +static DBusConnection *connection; + +static GHashTable *mesh_table; +static GHashTable *connected_peer_table; +static GHashTable *disconnected_peer_table; + +static struct connman_mesh_driver *mesh_driver; +static struct connman_mesh_eth_driver *mesh_eth_driver; + +char *mesh_ifname; +char *bridge_interface; +static unsigned int mesh_autoconnect_timeout; +static bool is_mesh_if_created; +bool eth_if_bridged; +mesh_nl80211_global *nl80211_global; + +struct connman_mesh { + int refcount; + char *identifier; + char *name; + char *path; + char *address; + char *interface_addr; + enum connman_mesh_security security; + char *passphrase; + enum connman_mesh_state state; + enum connman_mesh_peer_type peer_type; + enum connman_mesh_peer_disconnect_reason disconnect_reason; + uint16_t frequency; + uint8_t strength; + bool registered; + bool favorite; + DBusMessage *pending; + int index; + int br_index; + uint16_t ieee80211w; + struct connman_ipconfig *ipconfig; +}; + +struct connman_mesh_connected_peer { + char *peer_address; +}; + +struct connman_mesh_disconnected_peer { + char *peer_address; + enum connman_mesh_peer_disconnect_reason disconnect_reason; +}; + +struct connman_mesh_change_peer_data { + DBusMessage *pending; + char *peer_address; + enum connman_mesh_peer_status status; +}; + +static void mesh_dhcp_callback(struct connman_ipconfig *ipconfig, + struct connman_network *network, bool success, gpointer data); + +static void mesh_free(gpointer data) +{ + struct connman_mesh *mesh = data; + + connman_mesh_unregister(mesh); + + g_free(mesh->path); + + if (mesh->state == CONNMAN_MESH_STATE_CONFIGURATION || + mesh->state == CONNMAN_MESH_STATE_READY) + __connman_dhcp_stop(mesh->ipconfig); + + if (mesh->ipconfig) { + __connman_ipconfig_set_ops(mesh->ipconfig, NULL); + __connman_ipconfig_set_data(mesh->ipconfig, NULL); + __connman_ipconfig_unref(mesh->ipconfig); + mesh->ipconfig = NULL; + } + g_free(mesh->identifier); + g_free(mesh->name); + g_free(mesh->passphrase); + g_free(mesh->interface_addr); + g_free(mesh->address); + g_free(mesh); +} + +static void mesh_connected_peer_free(gpointer data) +{ + struct connman_mesh_connected_peer *peer = data; + + g_free(peer->peer_address); + g_free(peer); +} + +static void mesh_disconnected_peer_free(gpointer data) +{ + struct connman_mesh_disconnected_peer *peer = data; + + g_free(peer->peer_address); + g_free(peer); +} + +static void __mesh_load_and_create_network(char *mesh_id) +{ + GKeyFile *keyfile; + GString *str; + struct connman_mesh *connman_mesh; + gchar *name, *passphrase, *peer_type; + char *identifier, *group, *address; + const char *sec_type, *mesh_ifname; + int freq, i; + + keyfile = connman_storage_load_service(mesh_id); + if (!keyfile) { + DBG("Mesh profile doesn't exist"); + return; + } + + peer_type = g_key_file_get_string(keyfile, mesh_id, "PeerType", NULL); + if (g_strcmp0(peer_type, "created")) { + DBG("Mesh Profile was not created"); + goto done; + } + + name = g_key_file_get_string(keyfile, mesh_id, "Name", NULL); + if (!name) { + DBG("Failed to get Mesh Profile Name"); + goto done; + } + + passphrase = g_key_file_get_string(keyfile, mesh_id, "Passphrase", NULL); + if (passphrase) + sec_type = "sae"; + else + sec_type = "none"; + + freq = g_key_file_get_integer(keyfile, mesh_id, "Frequency", NULL); + + mesh_ifname = connman_mesh_get_interface_name(); + + str = g_string_sized_new((strlen(name) * 2) + 24); + + for (i = 0; name[i]; i++) + g_string_append_printf(str, "%02x", name[i]); + + g_string_append_printf(str, "_mesh"); + + if (g_strcmp0(sec_type, "none") == 0) + g_string_append_printf(str, "_none"); + else if (g_strcmp0(sec_type, "sae") == 0) + g_string_append_printf(str, "_sae"); + + group = g_string_free(str, FALSE); + + identifier = connman_inet_ifaddr(mesh_ifname); + address = connman_inet_ifname2addr(mesh_ifname); + + connman_mesh = connman_mesh_create(identifier, group); + connman_mesh_set_name(connman_mesh, name); + connman_mesh_set_address(connman_mesh, address); + connman_mesh_set_security(connman_mesh, sec_type); + connman_mesh_set_frequency(connman_mesh, freq); + connman_mesh_set_index(connman_mesh, connman_inet_ifindex(mesh_ifname)); + connman_mesh_set_peer_type(connman_mesh, CONNMAN_MESH_PEER_TYPE_CREATED); + + connman_mesh_register(connman_mesh); + g_free(group); + g_free(identifier); + g_free(address); +done: + g_key_file_free(keyfile); +} + +static bool is_connected(struct connman_mesh *mesh) +{ + if (mesh->state == CONNMAN_MESH_STATE_READY) + return true; + + return false; +} + +static void mesh_peer_dhcp_refresh(gpointer key, gpointer value, + gpointer user_data) +{ + struct connman_mesh *mesh = value; + + DBG("mesh %p state %d", mesh, mesh->state); + + if (is_connected(mesh)) + __connman_mesh_dhcp_start(mesh->ipconfig, mesh_dhcp_callback, mesh); +} + +int connman_inet_set_stp(int stp) +{ + int sk, err = 0; + struct ifreq ifr; + unsigned long args[4]; + + if (!bridge_interface) + return -EINVAL; + + sk = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = -errno; + goto out; + } + + args[0] = BRCTL_SET_BRIDGE_STP_STATE; + args[1] = stp; + args[2] = args[3] = 0; + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, bridge_interface, sizeof(ifr.ifr_name) - 1); + ifr.ifr_data = (char *)args; + + if (ioctl(sk, SIOCDEVPRIVATE, &ifr) < 0) + err = -errno; + + close(sk); + +out: + if (err < 0) + DBG("Set STP Failed error %s", strerror(-err)); + + return err; +} + +int __connman_mesh_set_stp_gate_announce(bool gate_announce, int hwmp_rootmode, + int stp) +{ + int err; + + if (!mesh_ifname) + return -EINVAL; + + err = connman_inet_set_stp(stp); + if (err < 0) + return err; + + err = __connman_mesh_netlink_set_gate_announce(nl80211_global, + connman_inet_ifindex(mesh_ifname), gate_announce, + hwmp_rootmode); + + return err; +} + +void __connman_mesh_add_ethernet_to_bridge(void) +{ + if (is_mesh_if_created) { + DBG(""); + mesh_eth_driver->add_to_bridge(bridge_interface); + eth_if_bridged = true; + g_hash_table_foreach(mesh_table, mesh_peer_dhcp_refresh, NULL); + connman_inet_set_stp(1); + __connman_mesh_netlink_set_gate_announce(nl80211_global, + connman_inet_ifindex(mesh_ifname), true, + MESH_HWMP_ROOTMODE_RANN); + } +} + +void __connman_mesh_remove_ethernet_from_bridge(void) +{ + if (eth_if_bridged) { + DBG(""); + mesh_eth_driver->remove_from_bridge(bridge_interface); + eth_if_bridged = false; + g_hash_table_foreach(mesh_table, mesh_peer_dhcp_refresh, NULL); + connman_inet_set_stp(0); + __connman_mesh_netlink_set_gate_announce(nl80211_global, + connman_inet_ifindex(mesh_ifname), false, + MESH_HWMP_ROOTMODE_NO_ROOT); + } +} + +int connman_mesh_notify_interface_create(bool success) +{ + int ret; + int index; + const char *error = NULL; + DIR *dir; + struct dirent *d; + + if (!success) { + error = "Operation Failed"; + goto done; + } + + if (!bridge_interface) { + DBG("Don't create bridge interface"); + goto done; + } + + DBG("Creating bridge [%s]", bridge_interface); + + /* Create bridge interface */ + ret = __connman_bridge_create(bridge_interface); + if (0 != ret) { + DBG("Failed to create bridge [%s] : [%s]", bridge_interface, + strerror(-ret)); + error = "Bridge Creation"; + success = false; + goto done; + } + + /* Get Mesh Interface Index */ + index = connman_inet_ifindex(mesh_ifname); + if (index < 0) { + DBG("Failed to get interface index for %s", mesh_ifname); + error = "Operation Failed"; + success = false; + goto done; + } + + /* Add mesh interface into bridge */ + ret = connman_inet_add_to_bridge(index, bridge_interface); + if (0 != ret) { + DBG("Failed to add interface[%s] into bridge[%s]", mesh_ifname, + bridge_interface); + error = "Add Mesh into bridge"; + success = false; + goto done; + } + + if (__connman_technology_get_connected(CONNMAN_SERVICE_TYPE_ETHERNET)) { + mesh_eth_driver->add_to_bridge(bridge_interface); + eth_if_bridged = true; + } + + index = connman_inet_ifindex(bridge_interface); + if (index < 0) { + DBG("Failed to get interface index for %s", bridge_interface); + error = "Operation Failed"; + success = false; + goto done; + } + + /* Make bridge interface UP */ + ret = connman_inet_ifup(index); + if (0 != ret) { + DBG("Failed to change bridge interface state"); + error = "Make bridge interface UP"; + success = false; + } + +done: + if (success) { + is_mesh_if_created = true; + + /* Load previously created mesh profiles */ + dir = opendir(STORAGEDIR); + if (!dir) { + DBG("Failed to open %s directory", STORAGEDIR); + __connman_technology_mesh_interface_create_finished( + CONNMAN_SERVICE_TYPE_MESH, success, error); + return 0; + } + + while ((d = readdir(dir))) { + if (g_str_has_prefix(d->d_name, "mesh_")) { + DBG("%s is a mesh profile", d->d_name); + __mesh_load_and_create_network(d->d_name); + __connman_mesh_auto_connect(); + } + } + + closedir(dir); + + } else { + if (eth_if_bridged) + mesh_eth_driver->remove_from_bridge(bridge_interface); + + __connman_bridge_disable(bridge_interface); + + __connman_bridge_remove(bridge_interface); + + mesh_driver->remove_interface(mesh_ifname); + } + __connman_technology_mesh_interface_create_finished( + CONNMAN_SERVICE_TYPE_MESH, success, error); + return 0; +} + +int __connman_mesh_add_virtual_interface(const char *ifname, + const char *parent_ifname, const char *bridge_ifname) +{ + int ret; + + if (!ifname || !parent_ifname) + return -EINVAL; + + ret = mesh_driver->add_interface(ifname, parent_ifname); + if (ret != -EINPROGRESS) { + DBG("Failed to add virtual mesh interface"); + return ret; + } + + mesh_ifname = g_strdup(ifname); + bridge_interface = g_strdup(bridge_ifname); + DBG("Success adding virtual mesh interface"); + return 0; +} + +int connman_mesh_notify_interface_remove(bool success) +{ + struct connman_device *device; + int index; + if (success) { + g_free(mesh_ifname); + mesh_ifname = NULL; + g_hash_table_remove_all(mesh_table); + is_mesh_if_created = false; + + if (eth_if_bridged) { + if (bridge_interface) + mesh_eth_driver->remove_from_bridge(bridge_interface); + + device = __connman_device_find_device( + CONNMAN_SERVICE_TYPE_ETHERNET); + if (device) { + index = connman_device_get_index(device); + connman_inet_ifup(index); + } + eth_if_bridged = false; + } + + if (bridge_interface) { + __connman_bridge_disable(bridge_interface); + if (__connman_bridge_remove(bridge_interface)) + DBG("Failed to remove bridge [%s]", bridge_interface); + + g_free(bridge_interface); + bridge_interface = NULL; + } + } + + __connman_technology_mesh_interface_remove_finished( + CONNMAN_SERVICE_TYPE_MESH, success); + return 0; +} + +int __connman_mesh_remove_virtual_interface(const char *ifname) +{ + int ret; + int index; + + if (!ifname) + return -EINVAL; + + if (bridge_interface) { + index = connman_inet_ifindex(mesh_ifname); + if (index < 0) { + DBG("Failed to get interface index for %s", mesh_ifname); + return -EINVAL; + } + + ret = connman_inet_remove_from_bridge(index, bridge_interface); + if (0 != ret) { + DBG("Failed to remove interface[%s] freom bridge[%s]", mesh_ifname, + bridge_interface); + return -EINVAL; + } + + if (eth_if_bridged) + mesh_eth_driver->remove_from_bridge(bridge_interface); + + __connman_bridge_disable(bridge_interface); + + ret = __connman_bridge_remove(bridge_interface); + if (0 != ret) { + DBG("Failed to remove bridge [%s]", bridge_interface); + return -EINVAL; + } + + g_free(bridge_interface); + bridge_interface = NULL; + } + + ret = mesh_driver->remove_interface(ifname); + if (ret != -EINPROGRESS) { + DBG("Failed to remove virtual mesh interface"); + return ret; + } + + DBG("Success removing virtual mesh interface"); + return 0; +} + +const char *connman_mesh_get_interface_name(void) +{ + return mesh_ifname; +} + +bool connman_mesh_is_interface_created(void) +{ + DBG("Mesh interface is %screated", is_mesh_if_created ? "" : "not "); + return is_mesh_if_created; +} + +struct connman_mesh *connman_mesh_create(const char *interface_addr, + const char *identifier) +{ + struct connman_mesh *mesh; + + mesh = g_malloc0(sizeof(struct connman_mesh)); + mesh->identifier = g_strdup_printf("mesh_%s_%s", interface_addr, + identifier); + mesh->interface_addr = g_strdup(interface_addr); + mesh->state = CONNMAN_MESH_STATE_IDLE; + + mesh->refcount = 1; + + return mesh; +} + +void connman_mesh_set_name(struct connman_mesh *mesh, const char *name) +{ + g_free(mesh->name); + mesh->name = g_strdup(name); +} + +const char *connman_mesh_get_name(struct connman_mesh *mesh) +{ + return mesh->name; +} + +void connman_mesh_set_passphrase(struct connman_mesh *mesh, + const char *passphrase) +{ + g_free(mesh->passphrase); + mesh->passphrase = g_strdup(passphrase); +} + +const char *connman_mesh_get_passphrase(struct connman_mesh *mesh) +{ + return mesh->passphrase; +} + +void connman_mesh_set_address(struct connman_mesh *mesh, const char *address) +{ + g_free(mesh->address); + mesh->address = g_strdup(address); +} + +void connman_mesh_set_security(struct connman_mesh *mesh, const char *security) +{ + if (!g_strcmp0(security, "none")) + mesh->security = CONNMAN_MESH_SECURITY_NONE; + else if (!g_strcmp0(security, "sae")) + mesh->security = CONNMAN_MESH_SECURITY_SAE; + else + mesh->security = CONNMAN_MESH_SECURITY_UNKNOWN; +} + +static const char *security2string(enum connman_mesh_security security) +{ + switch (security) { + case CONNMAN_MESH_SECURITY_UNKNOWN: + break; + case CONNMAN_MESH_SECURITY_NONE: + return "none"; + case CONNMAN_MESH_SECURITY_SAE: + return "sae"; + } + + return NULL; +} + +const char *connman_mesh_get_security(struct connman_mesh *mesh) +{ + return security2string(mesh->security); +} + +void connman_mesh_set_frequency(struct connman_mesh *mesh, uint16_t frequency) +{ + mesh->frequency = frequency; +} + +uint16_t connman_mesh_get_frequency(struct connman_mesh *mesh) +{ + return mesh->frequency; +} + +void connman_mesh_set_ieee80211w(struct connman_mesh *mesh, uint16_t ieee80211w) +{ + mesh->ieee80211w = ieee80211w; +} + +uint16_t connman_mesh_get_ieee80211w(struct connman_mesh *mesh) +{ + return mesh->ieee80211w; +} + +void connman_mesh_set_index(struct connman_mesh *mesh, int index) +{ + mesh->index = index; + + if (bridge_interface) + mesh->br_index = connman_inet_ifindex(bridge_interface); +} + +void connman_mesh_set_strength(struct connman_mesh *mesh, uint8_t strength) +{ + mesh->strength = strength; +} + +static const char *peertype2string(enum connman_mesh_peer_type type) +{ + switch (type) { + case CONNMAN_MESH_PEER_TYPE_CREATED: + return "created"; + case CONNMAN_MESH_PEER_TYPE_DISCOVERED: + return "discovered"; + } + + return NULL; +} + +void connman_mesh_set_peer_type(struct connman_mesh *mesh, + enum connman_mesh_peer_type type) +{ + mesh->peer_type = type; +} + +static const char *state2string(enum connman_mesh_state state) +{ + switch (state) { + case CONNMAN_MESH_STATE_UNKNOWN: + break; + case CONNMAN_MESH_STATE_IDLE: + return "idle"; + case CONNMAN_MESH_STATE_ASSOCIATION: + return "association"; + case CONNMAN_MESH_STATE_CONFIGURATION: + return "configuration"; + case CONNMAN_MESH_STATE_READY: + return "ready"; + case CONNMAN_MESH_STATE_DISCONNECT: + return "disconnect"; + case CONNMAN_MESH_STATE_FAILURE: + return "failure"; + } + + return NULL; +} + +static enum connman_mesh_peer_disconnect_reason convert_to_disconnect_reason( + int reason) +{ + switch (reason) { + case 3: + return CONNMAN_MESH_DEAUTH_LEAVING; + case 52: + return CONNMAN_MESH_PEERING_CANCELLED; + case 53: + return CONNMAN_MESH_MAX_PEERS; + case 54: + return CONNMAN_MESH_CONFIG_POLICY_VIOLATION; + case 55: + return CONNMAN_MESH_CLOSE_RCVD; + case 56: + return CONNMAN_MESH_MAX_RETRIES; + case 57: + return CONNMAN_MESH_CONFIRM_TIMEOUT; + case 58: + return CONNMAN_MESH_INVALID_GTK; + case 59: + return CONNMAN_MESH_INCONSISTENT_PARAMS; + case 60: + return CONNMAN_MESH_INVALID_SECURITY_CAP; + } + + return CONNMAN_MESH_REASON_UNKNOWN; +} + +void connman_mesh_peer_set_disconnect_reason(struct connman_mesh *mesh, + int disconnect_reason) +{ + mesh->disconnect_reason = convert_to_disconnect_reason(disconnect_reason); +} + +static bool is_connecting(struct connman_mesh *mesh) +{ + if (mesh->state == CONNMAN_MESH_STATE_ASSOCIATION || + mesh->state == CONNMAN_MESH_STATE_CONFIGURATION) + return true; + + return false; +} + +static int mesh_load(struct connman_mesh *mesh) +{ + GKeyFile *keyfile; + bool favorite; + GError *error = NULL; + gchar *str; + + keyfile = connman_storage_load_service(mesh->identifier); + if (!keyfile) { + DBG("Mesh profile is new"); + return -EIO; + } + + favorite = g_key_file_get_boolean(keyfile, + mesh->identifier, "Favorite", &error); + + if (!error) + mesh->favorite = favorite; + + g_clear_error(&error); + + str = g_key_file_get_string(keyfile, mesh->identifier, "Passphrase", NULL); + + if (str) { + g_free(mesh->passphrase); + mesh->passphrase = str; + } + + return 0; +} + +static int mesh_save(struct connman_mesh *mesh) +{ + GKeyFile *keyfile; + + keyfile = __connman_storage_open_service(mesh->identifier); + if (!keyfile) + return -EIO; + + g_key_file_set_string(keyfile, mesh->identifier, "Name", mesh->name); + g_key_file_set_integer(keyfile, mesh->identifier, "Frequency", + mesh->frequency); + g_key_file_set_boolean(keyfile, mesh->identifier, "Favorite", + mesh->favorite); + + if (mesh->passphrase) + g_key_file_set_string(keyfile, mesh->identifier, "Passphrase", + mesh->passphrase); + + g_key_file_set_string(keyfile, mesh->identifier, "PeerType", + peertype2string(mesh->peer_type)); + + __connman_storage_save_service(keyfile, mesh->identifier); + + g_key_file_free(keyfile); + + return 0; +} + +static void reply_pending(struct connman_mesh *mesh, int error) +{ + if (!mesh->pending) + return; + + connman_dbus_reply_pending(mesh->pending, error, NULL); + mesh->pending = NULL; +} + +static void state_changed(struct connman_mesh *mesh) +{ + const char *state; + + state = state2string(mesh->state); + if (!state) + return; + + connman_dbus_property_changed_basic(mesh->path, + CONNMAN_MESH_INTERFACE, "State", + DBUS_TYPE_STRING, &state); +} + +static void mesh_dhcp_callback(struct connman_ipconfig *ipconfig, + struct connman_network *network, bool success, gpointer data) +{ + struct connman_mesh *mesh = data; + int err; + + if (!success) + goto error; + + err = __connman_ipconfig_address_add(ipconfig); + if (err < 0) + goto error; + + return; + +error: + connman_mesh_peer_set_state(mesh, CONNMAN_MESH_STATE_FAILURE); +} + +static int mesh_start_dhcp_client(struct connman_mesh *mesh) +{ + DBG(""); + + __connman_ipconfig_enable(mesh->ipconfig); + + return __connman_mesh_dhcp_start(mesh->ipconfig, mesh_dhcp_callback, mesh); +} + +static void mesh_remove_connected_peer(gpointer key, gpointer value, + gpointer user_data) +{ + struct connman_mesh_connected_peer *peer = value; + + DBG("Remove Peer %s", peer->peer_address); + g_hash_table_remove(connected_peer_table, key); +} + +static void mesh_remove_disconnected_peer(gpointer key, gpointer value, + gpointer user_data) +{ + struct connman_mesh_disconnected_peer *peer = value; + + DBG("Remove Peer %s", peer->peer_address); + g_hash_table_remove(disconnected_peer_table, key); +} + +int connman_mesh_peer_set_state(struct connman_mesh *mesh, + enum connman_mesh_state new_state) +{ + enum connman_mesh_state old_state = mesh->state; + + DBG("mesh peer %s old state %s new state %s", mesh->name, + state2string(old_state), state2string(new_state)); + + if (old_state == new_state) + return -EALREADY; + + switch (new_state) { + case CONNMAN_MESH_STATE_UNKNOWN: + return -EINVAL; + case CONNMAN_MESH_STATE_IDLE: + case CONNMAN_MESH_STATE_ASSOCIATION: + break; + case CONNMAN_MESH_STATE_CONFIGURATION: + /* Start Link Local IP Address */ + mesh_start_dhcp_client(mesh); + break; + case CONNMAN_MESH_STATE_READY: + reply_pending(mesh, 0); + mesh->favorite = true; + __connman_notifier_connect(CONNMAN_SERVICE_TYPE_MESH); + + /* Set Gate Announce option */ + if (eth_if_bridged) { + connman_inet_set_stp(1); + __connman_mesh_netlink_set_gate_announce(nl80211_global, + connman_inet_ifindex(mesh_ifname), true, + MESH_HWMP_ROOTMODE_RANN); + } + + mesh_save(mesh); + break; + case CONNMAN_MESH_STATE_DISCONNECT: + __connman_dhcp_stop(mesh->ipconfig); + g_hash_table_foreach(connected_peer_table, mesh_remove_connected_peer, + NULL); + g_hash_table_foreach(disconnected_peer_table, + mesh_remove_disconnected_peer, NULL); + __connman_notifier_disconnect(CONNMAN_SERVICE_TYPE_MESH); + break; + case CONNMAN_MESH_STATE_FAILURE: + reply_pending(mesh, ECONNABORTED); + break; + } + + mesh->state = new_state; + state_changed(mesh); + + return 0; +} + +bool connman_mesh_peer_is_connected_state(struct connman_mesh *mesh) +{ + switch (mesh->state) { + case CONNMAN_MESH_STATE_UNKNOWN: + case CONNMAN_MESH_STATE_IDLE: + case CONNMAN_MESH_STATE_ASSOCIATION: + case CONNMAN_MESH_STATE_CONFIGURATION: + case CONNMAN_MESH_STATE_DISCONNECT: + case CONNMAN_MESH_STATE_FAILURE: + break; + case CONNMAN_MESH_STATE_READY: + return true; + } + + return false; +} + +struct connman_mesh *connman_get_connected_mesh_from_name(char *name) +{ + GList *list, *start; + + list = g_hash_table_get_values(mesh_table); + start = list; + for (; list; list = list->next) { + struct connman_mesh *mesh = list->data; + + if (!g_strcmp0(mesh->name, name) && + mesh->state == CONNMAN_MESH_STATE_READY) { + g_list_free(start); + return mesh; + } + } + + g_list_free(start); + + return NULL; +} + +struct connman_mesh *connman_get_connecting_mesh_from_name(char *name) +{ + GList *list, *start; + + list = g_hash_table_get_values(mesh_table); + start = list; + for (; list; list = list->next) { + struct connman_mesh *mesh = list->data; + + if (!g_strcmp0(mesh->name, name) && is_connecting(mesh)) { + g_list_free(start); + return mesh; + } + } + + g_list_free(start); + + return NULL; +} + +static void mesh_append_ethernet(DBusMessageIter *iter, void *user_data) +{ + struct connman_mesh *mesh = user_data; + + if (mesh->ipconfig) + __connman_ipconfig_append_ethernet(mesh->ipconfig, iter); +} + +static void mesh_append_ipv4(DBusMessageIter *iter, void *user_data) +{ + struct connman_mesh *mesh = user_data; + + if (!is_connected(mesh)) + return; + + if (mesh->ipconfig) + __connman_ipconfig_append_ipv4(mesh->ipconfig, iter); +} + +static void mesh_append_ipv4config(DBusMessageIter *iter, void *user_data) +{ + struct connman_mesh *mesh = user_data; + + if (mesh->ipconfig) + __connman_ipconfig_append_ipv4config(mesh->ipconfig, iter); +} + +static void append_properties(DBusMessageIter *iter, struct connman_mesh *mesh) +{ + const char *state = state2string(mesh->state); + const char *security = security2string(mesh->security); + const char *peer_type = peertype2string(mesh->peer_type); + const char *type = "mesh"; + DBusMessageIter dict; + + connman_dbus_dict_open(iter, &dict); + + connman_dbus_dict_append_basic(&dict, "Type", DBUS_TYPE_STRING, &type); + connman_dbus_dict_append_basic(&dict, "Name", + DBUS_TYPE_STRING, &mesh->name); + connman_dbus_dict_append_basic(&dict, "BSSID", + DBUS_TYPE_STRING, &mesh->address); + connman_dbus_dict_append_basic(&dict, "State", DBUS_TYPE_STRING, &state); + if (security) + connman_dbus_dict_append_basic(&dict, "Security", + DBUS_TYPE_STRING, &security); + connman_dbus_dict_append_basic(&dict, "Frequency", + DBUS_TYPE_UINT16, &mesh->frequency); + connman_dbus_dict_append_basic(&dict, "Favorite", + DBUS_TYPE_BOOLEAN, &mesh->favorite); + connman_dbus_dict_append_basic(&dict, "Strength", + DBUS_TYPE_BYTE, &mesh->strength); + connman_dbus_dict_append_basic(&dict, "PeerType", + DBUS_TYPE_STRING, &peer_type); + connman_dbus_dict_append_basic(&dict, "DisconnectReason", + DBUS_TYPE_INT32, &mesh->disconnect_reason); + + connman_dbus_dict_append_dict(&dict, "Ethernet", mesh_append_ethernet, + mesh); + + connman_dbus_dict_append_dict(&dict, "IPv4", mesh_append_ipv4, mesh); + + connman_dbus_dict_append_dict(&dict, "IPv4.Configuration", + mesh_append_ipv4config, mesh); + + connman_dbus_dict_close(iter, &dict); +} + +static void append_mesh_peer_struct(gpointer key, gpointer value, + gpointer user_data) +{ + DBusMessageIter *array = user_data; + struct connman_mesh *mesh = value; + DBusMessageIter entry; + + DBG("Mesh Peer path %s", mesh->path); + dbus_message_iter_open_container(array, DBUS_TYPE_STRUCT, + NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, + &mesh->path); + append_properties(&entry, mesh); + dbus_message_iter_close_container(array, &entry); +} + +void __connman_mesh_peer_list_struct(DBusMessageIter *array) +{ + g_hash_table_foreach(mesh_table, append_mesh_peer_struct, array); +} + +static DBusMessage *get_mesh_peer_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct connman_mesh *mesh = 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, mesh); + + return reply; +} + +static void append_mesh_disconnected_peer_struct(gpointer key, gpointer value, + gpointer user_data) +{ + DBusMessageIter *array = user_data; + struct connman_mesh_disconnected_peer *peer = value; + DBusMessageIter entry; + DBusMessageIter dict; + + dbus_message_iter_open_container(array, DBUS_TYPE_STRUCT, + NULL, &entry); + + connman_dbus_dict_open(&entry, &dict); + + connman_dbus_dict_append_basic(&dict, "PeerAddress", + DBUS_TYPE_STRING, &peer->peer_address); + + connman_dbus_dict_append_basic(&dict, "DisconnectReason", + DBUS_TYPE_INT32, &peer->disconnect_reason); + + connman_dbus_dict_close(&entry, &dict); + dbus_message_iter_close_container(array, &entry); +} + +void __connman_mesh_disconnected_peer_list_struct(DBusMessageIter *array) +{ + g_hash_table_foreach(disconnected_peer_table, + append_mesh_disconnected_peer_struct, array); +} + +static void append_mesh_connected_peer_struct(gpointer key, gpointer value, + gpointer user_data) +{ + DBusMessageIter *array = user_data; + struct connman_mesh_connected_peer *peer = value; + DBusMessageIter entry; + DBusMessageIter dict; + + dbus_message_iter_open_container(array, DBUS_TYPE_STRUCT, + NULL, &entry); + + connman_dbus_dict_open(&entry, &dict); + + connman_dbus_dict_append_basic(&dict, "PeerAddress", + DBUS_TYPE_STRING, &peer->peer_address); + + connman_dbus_dict_close(&entry, &dict); + dbus_message_iter_close_container(array, &entry); +} + +void __connman_mesh_connected_peer_list_struct(DBusMessageIter *array) +{ + g_hash_table_foreach(connected_peer_table, + append_mesh_connected_peer_struct, array); +} + +int connman_mesh_add_connected_peer(const char *peer_address) +{ + struct connman_mesh_connected_peer *peer; + struct connman_mesh_connected_peer *temp_peer; + struct connman_mesh_disconnected_peer *disconn_peer; + + temp_peer = g_hash_table_lookup(connected_peer_table, peer_address); + + if (temp_peer) { + DBG("Mesh Peer %s is already connected", peer_address); + return 0; + } + + peer = g_malloc0(sizeof(struct connman_mesh_connected_peer)); + peer->peer_address = g_strdup(peer_address); + DBG("Peer %s", peer->peer_address); + + g_hash_table_insert(connected_peer_table, peer->peer_address, peer); + + /* Remove from disconnected Peer Table */ + disconn_peer = g_hash_table_lookup(disconnected_peer_table, peer_address); + if (!disconn_peer) { + DBG("Peer %s was never disconnected", peer_address); + goto done; + } + + g_hash_table_remove(disconnected_peer_table, peer_address); +done: + return 0; +} + +int connman_mesh_remove_connected_peer(const char *peer_address, int reason) +{ + struct connman_mesh_connected_peer *peer; + struct connman_mesh_disconnected_peer *disconn_peer; + + peer = g_hash_table_lookup(connected_peer_table, peer_address); + + if (!peer) { + DBG("Peer %s not connected", peer_address); + return 0; + } + + g_hash_table_remove(connected_peer_table, peer_address); + + /* Add to Disconnected Peer Table */ + disconn_peer = g_malloc0(sizeof(struct connman_mesh_disconnected_peer)); + disconn_peer->peer_address = g_strdup(peer_address); + disconn_peer->disconnect_reason = convert_to_disconnect_reason(reason); + + g_hash_table_insert(disconnected_peer_table, disconn_peer->peer_address, + disconn_peer); + + DBG("Mesh Peer %s removed due to reason %d", peer_address, reason); + return 0; +} + +static void __mesh_change_peer_status_cb(int result, void *user_data) +{ + struct connman_mesh_change_peer_data *data = user_data; + + DBG("Status %d Peer Address %s result %d", data->status, data->peer_address, + result); + + connman_dbus_reply_pending(data->pending, -result, NULL); + + data->pending = NULL; + g_free(data->peer_address); + g_free(data); +} + +int __connman_mesh_change_peer_status(DBusMessage *msg, + const char *peer_address, + enum connman_mesh_peer_status status) +{ + struct connman_mesh_connected_peer *conn_peer; + struct connman_mesh_disconnected_peer *disconn_peer; + int err = -ENOTSUP; + struct connman_mesh_change_peer_data *data; + + switch (status) { + case CONNMAN_MESH_PEER_ADD: + conn_peer = g_hash_table_lookup(connected_peer_table, peer_address); + + if (conn_peer) { + DBG("Peer %s already connected", peer_address); + return -EEXIST; + } + + break; + + case CONNMAN_MESH_PEER_REMOVE: + disconn_peer = g_hash_table_lookup(disconnected_peer_table, + peer_address); + + if (disconn_peer) { + DBG("Peer %s already disconnected", peer_address); + return -EEXIST; + } + + break; + + default: + DBG("Invalid Status type"); + return err; + } + + if (mesh_driver->change_peer_status) { + data = g_try_malloc0(sizeof(struct connman_mesh_disconnected_peer)); + if (data == NULL) { + DBG("Memory allocation failed"); + return -ENOMEM; + } + + data->pending = dbus_message_ref(msg); + data->peer_address = g_strdup(peer_address); + data->status = status; + + err = mesh_driver->change_peer_status(peer_address, status, + __mesh_change_peer_status_cb, data); + + if (err < 0) { + dbus_message_unref(data->pending); + g_free(data->peer_address); + g_free(data); + } + } + + return err; +} + +static int mesh_peer_connect(struct connman_mesh *mesh) +{ + int err = -ENOTSUP; + if (mesh_driver->connect) + err = mesh_driver->connect(mesh); + + /* Reset Disconnect Reason */ + mesh->disconnect_reason = CONNMAN_MESH_REASON_UNKNOWN; + return err; +} + +static DBusMessage *connect_mesh_peer(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct connman_mesh *mesh = user_data; + int err; + + DBG("mesh %p", mesh); + + if (mesh->state == CONNMAN_MESH_STATE_READY) { + DBG("mesh %s already connected", mesh->name); + return __connman_error_already_exists(msg); + } + + if (mesh->pending) + return __connman_error_in_progress(msg); + + mesh->pending = dbus_message_ref(msg); + + err = mesh_peer_connect(mesh); + if (err == -EINPROGRESS) + return NULL; + + if (err < 0) { + dbus_message_unref(mesh->pending); + mesh->pending = NULL; + return __connman_error_failed(msg, -err); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void auto_connect_mesh_peer(gpointer key, gpointer value, + gpointer user_data) +{ + bool *conn_started = user_data; + struct connman_mesh *mesh = value; + int err; + + if (*conn_started) + return; + + if (!mesh->favorite || mesh->state != CONNMAN_MESH_STATE_IDLE) + return; + + err = mesh_peer_connect(mesh); + if (err == -EINPROGRESS) + *conn_started = 1; +} + +static gboolean run_mesh_auto_connect(gpointer data) +{ + bool conn_started; + + mesh_autoconnect_timeout = 0; + DBG(""); + + conn_started = false; + g_hash_table_foreach(mesh_table, auto_connect_mesh_peer, &conn_started); + return FALSE; +} + +void __connman_mesh_auto_connect(void) +{ + DBG(""); + + if (mesh_autoconnect_timeout != 0) + return; + + mesh_autoconnect_timeout = g_idle_add(run_mesh_auto_connect, NULL); +} + +static void mesh_peer_up(struct connman_ipconfig *ipconfig, const char *ifname) +{ + DBG("%s up", ifname); +} + +static void mesh_peer_down(struct connman_ipconfig *ipconfig, + const char *ifname) +{ + DBG("%s down", ifname); +} + +static void mesh_peer_lower_up(struct connman_ipconfig *ipconfig, + const char *ifname) +{ + DBG("%s lower up", ifname); +} + +static void mesh_peer_lower_down(struct connman_ipconfig *ipconfig, + const char *ifname) +{ + DBG("%s lower down", ifname); +} + +static void mesh_peer_ip_bound(struct connman_ipconfig *ipconfig, + const char *ifname) +{ + struct connman_mesh *mesh = __connman_ipconfig_get_data(ipconfig); + + DBG("%s ip bound", ifname); + + connman_mesh_peer_set_state(mesh, CONNMAN_MESH_STATE_READY); +} + +static void mesh_peer_ip_release(struct connman_ipconfig *ipconfig, + const char *ifname) +{ + DBG("%s ip release", ifname); +} + +static const struct connman_ipconfig_ops mesh_peer_ip_ops = { + .up = mesh_peer_up, + .down = mesh_peer_down, + .lower_up = mesh_peer_lower_up, + .lower_down = mesh_peer_lower_down, + .ip_bound = mesh_peer_ip_bound, + .ip_release = mesh_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, &mesh_peer_ip_ops); + + return ipconfig; +} + +static int __connman_mesh_peer_disconnect(struct connman_mesh *mesh) +{ + int err; + + reply_pending(mesh, ECONNABORTED); + + if (!is_connected(mesh) && !is_connecting(mesh)) + return -ENOTCONN; + + err = mesh_driver->disconnect(mesh); + if (err < 0 && err != -EINPROGRESS) + return err; + + return err; +} + +static DBusMessage *disconnect_mesh_peer(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct connman_mesh *mesh = user_data; + int err; + + DBG("mesh %p", mesh); + err = __connman_mesh_peer_disconnect(mesh); + if (err < 0 && err != -EINPROGRESS) + return __connman_error_failed(msg, -err); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static bool __connman_mesh_peer_remove(struct connman_mesh *mesh) +{ + if (!mesh->favorite) + return false; + + __connman_mesh_peer_disconnect(mesh); + + mesh->favorite = false; + + __connman_storage_remove_service(mesh->identifier); + + return true; +} + +static DBusMessage *remove_mesh_peer(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct connman_mesh *mesh = user_data; + + DBG("mesh %p", mesh); + + if (!__connman_mesh_peer_remove(mesh)) + return __connman_error_not_supported(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *set_mesh_peer_property(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct connman_mesh *mesh = user_data; + DBusMessageIter iter, value; + const char *name; + int type; + + DBG("mesh %p", mesh); + + if (!dbus_message_iter_init(msg, &iter)) + return __connman_error_invalid_arguments(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return __connman_error_invalid_arguments(msg); + + dbus_message_iter_get_basic(&iter, &name); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return __connman_error_invalid_arguments(msg); + + dbus_message_iter_recurse(&iter, &value); + + type = dbus_message_iter_get_arg_type(&value); + + if (g_str_equal(name, "Passphrase")) { + char *passphrase; + + if (type != DBUS_TYPE_STRING) + return __connman_error_invalid_arguments(msg); + + dbus_message_iter_get_basic(&value, &passphrase); + + connman_mesh_set_passphrase(mesh, passphrase); + } else { + DBG("Invalid Property %s", name); + return __connman_error_invalid_property(msg); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static const GDBusMethodTable mesh_methods[] = { + { GDBUS_METHOD("GetProperties", + NULL, GDBUS_ARGS({ "properties", "a{sv}" }), + get_mesh_peer_properties) }, + { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, connect_mesh_peer) }, + { GDBUS_METHOD("Disconnect", NULL, NULL, disconnect_mesh_peer) }, + { GDBUS_METHOD("Remove", NULL, NULL, remove_mesh_peer) }, + { GDBUS_METHOD("SetProperty", + GDBUS_ARGS({ "name", "s" }, { "value", "v" }), + NULL, set_mesh_peer_property) }, + { }, +}; + +static const GDBusSignalTable mesh_signals[] = { + { GDBUS_SIGNAL("PropertyChanged", + GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, + { }, +}; + +int connman_mesh_register(struct connman_mesh *mesh) +{ + struct connman_mesh *temp; + DBG("mesh %p", mesh); + + if (mesh->path) + return -EALREADY; + + mesh->path = g_strdup_printf("%s/mesh/%s", CONNMAN_PATH, + mesh->identifier); + DBG("path %s", mesh->path); + + temp = g_hash_table_lookup(mesh_table, mesh->path); + if (temp) { + DBG("mesh path %s already exists", mesh->path); + + if (mesh->frequency != temp->frequency) { + DBG("Update frequency for mesh network %s", mesh->name); + connman_mesh_set_frequency(temp, mesh->frequency); + } + + mesh_free(mesh); + return -EALREADY; + } + + if (mesh->br_index > 0) + mesh->ipconfig = create_ipconfig(mesh->br_index, mesh); + else + mesh->ipconfig = create_ipconfig(mesh->index, mesh); + + if (!mesh->ipconfig) + return -ENOMEM; + + g_hash_table_insert(mesh_table, mesh->path, mesh); + + mesh_load(mesh); + + g_dbus_register_interface(connection, mesh->path, + CONNMAN_MESH_INTERFACE, + mesh_methods, mesh_signals, + NULL, mesh, NULL); + mesh->registered = true; + return 0; +} + +void connman_mesh_unregister(struct connman_mesh *mesh) +{ + DBG("mesh %p", mesh); + + if (!mesh->path || !mesh->registered) + return; + + g_dbus_unregister_interface(connection, mesh->path, + CONNMAN_MESH_INTERFACE); + mesh->registered = false; + + g_hash_table_remove(mesh_table, mesh->path); +} + +struct connman_mesh *connman_mesh_get(const char *interface_addr, + const char *identifier) +{ + char *ident = g_strdup_printf("%s/mesh/mesh_%s_%s", CONNMAN_PATH, + interface_addr, identifier); + struct connman_mesh *mesh; + + mesh = g_hash_table_lookup(mesh_table, ident); + g_free(ident); + + return mesh; +} + +int connman_mesh_driver_register(struct connman_mesh_driver *driver) +{ + if (mesh_driver && mesh_driver != driver) + return -EINVAL; + + mesh_driver = driver; + + return 0; +} + +void connman_mesh_driver_unregister(struct connman_mesh_driver *driver) +{ + if (mesh_driver != driver) + return; + + mesh_driver = NULL; +} + +int connman_mesh_eth_driver_register(struct connman_mesh_eth_driver *driver) +{ + if (mesh_eth_driver && mesh_eth_driver != driver) + return -EINVAL; + + mesh_eth_driver = driver; + + return 0; +} + +void connman_mesh_eth_driver_unregister(struct connman_mesh_eth_driver *driver) +{ + if (mesh_eth_driver != driver) + return; + + mesh_eth_driver = NULL; +} + +int __connman_mesh_init(void) +{ + DBG(""); + + connection = connman_dbus_get_connection(); + + mesh_table = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, mesh_free); + + connected_peer_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + mesh_connected_peer_free); + + disconnected_peer_table = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, mesh_disconnected_peer_free); + + nl80211_global = __connman_mesh_nl80211_global_init(); + return 0; +} + +void __connman_mesh_cleanup(void) +{ + DBG(""); + + __connman_mesh_nl80211_global_deinit(nl80211_global); + g_hash_table_destroy(mesh_table); + g_hash_table_destroy(connected_peer_table); + g_hash_table_destroy(disconnected_peer_table); + dbus_connection_unref(connection); +} -- cgit v1.2.3