diff --git a/.gitignore b/.gitignore index 0a1a7f0..2bb051a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ configure libtool m4/ miracle-dhcp +miracle-wifid miraclectl miracled stamp-h1 diff --git a/Makefile.am b/Makefile.am index 2f3c36a..76d2969 100644 --- a/Makefile.am +++ b/Makefile.am @@ -86,6 +86,27 @@ libmiracle_shared_la_CPPFLAGS = $(AM_CPPFLAGS) libmiracle_shared_la_LDFLAGS = $(AM_LDFLAGS) libmiracle_shared_la_LIBADD = $(AM_LIBADD) +# +# miracle-wifid +# + +bin_PROGRAMS += miracle-wifid + +miracle_wifid_SOURCES = \ + src/wifi/wifid.h \ + src/wifi/wifid.c \ + src/wifi/wifid-dbus.c \ + src/wifi/wifid-link.c \ + src/wifi/wifid-peer.c \ + src/wifi/wifid-supplicant.c +miracle_wifid_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) +miracle_wifid_LDADD = \ + libmiracle-shared.la \ + $(DEPS_LIBS) +miracle_wifid_LDFLAGS = $(AM_LDFLAGS) + # # miraclectl # diff --git a/res/org.freedesktop.miracle.conf b/res/org.freedesktop.miracle.conf index 1741dfe..dee41ad 100644 --- a/res/org.freedesktop.miracle.conf +++ b/res/org.freedesktop.miracle.conf @@ -10,31 +10,48 @@ + + + + + + + + + + diff --git a/src/wifi/wifid-dbus.c b/src/wifi/wifid-dbus.c new file mode 100644 index 0000000..0c9eba1 --- /dev/null +++ b/src/wifi/wifid-dbus.c @@ -0,0 +1,835 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#define LOG_SUBSYSTEM "dbus" + +#include +#include +#include +#include +#include +#include +#include "shl_log.h" +#include "shl_util.h" +#include "util.h" +#include "wifid.h" + +static char *peer_dbus_get_path(struct peer *p) +{ + char buf[128], *node; + int r; + + sprintf(buf, "%s@%u", p->p2p_mac, p->l->ifindex); + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/peer", + buf, + &node); + if (r < 0) { + log_vERR(r); + return NULL; + } + + return node; +} + +static char *link_dbus_get_path(struct link *l) +{ + char buf[128], *node; + int r; + + sprintf(buf, "%u", l->ifindex); + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link", + buf, + &node); + if (r < 0) { + log_vERR(r); + return NULL; + } + + return node; +} + +/* + * Peer DBus + */ + +static int peer_dbus_connect(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + struct peer *p = data; + const char *prov, *pin; + int r; + + r = sd_bus_message_read(msg, "ss", &prov, &pin); + if (r < 0) + return r; + + if (!*prov || !strcmp(prov, "auto")) + prov = NULL; + if (!*pin) + pin = NULL; + + r = peer_connect(p, prov, pin); + if (r < 0) + return r; + + return sd_bus_reply_method_return(msg, NULL); +} + +static int peer_dbus_disconnect(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + struct peer *p = data; + + peer_disconnect(p); + return sd_bus_reply_method_return(msg, NULL); +} + +static int peer_dbus_get_link(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + _shl_free_ char *node = NULL; + struct peer *p = data; + int r; + + node = link_dbus_get_path(p->l); + if (!node) + return -ENOMEM; + + r = sd_bus_message_append_basic(reply, 'o', node); + if (r < 0) + return r; + + return 1; +} + +static int peer_dbus_get_p2p_mac(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct peer *p = data; + int r; + + r = sd_bus_message_append_basic(reply, 's', p->p2p_mac); + if (r < 0) + return r; + + return 1; +} + +static int peer_dbus_get_friendly_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct peer *p = data; + const char *name; + int r; + + name = peer_get_friendly_name(p); + if (!name) + name = ""; + + r = sd_bus_message_append_basic(reply, 's', name); + if (r < 0) + return r; + + return 1; +} + +static int peer_dbus_get_connected(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct peer *p = data; + int r; + + r = sd_bus_message_append(reply, "b", p->connected); + if (r < 0) + return r; + + return 1; +} + +static int peer_dbus_get_interface(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct peer *p = data; + int r; + + r = sd_bus_message_append(reply, "s", peer_get_interface(p)); + if (r < 0) + return r; + + return 1; +} + +static int peer_dbus_get_local_address(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct peer *p = data; + int r; + + r = sd_bus_message_append(reply, "s", peer_get_local_address(p)); + if (r < 0) + return r; + + return 1; +} + +static int peer_dbus_get_remote_address(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct peer *p = data; + int r; + + r = sd_bus_message_append(reply, "s", peer_get_remote_address(p)); + if (r < 0) + return r; + + return 1; +} + +static const sd_bus_vtable peer_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Connect", + "ss", + NULL, + peer_dbus_connect, + 0), + SD_BUS_METHOD("Disconnect", + NULL, + NULL, + peer_dbus_disconnect, + 0), + SD_BUS_PROPERTY("Link", + "o", + peer_dbus_get_link, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("P2PMac", + "s", + peer_dbus_get_p2p_mac, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FriendlyName", + "s", + peer_dbus_get_friendly_name, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Connected", + "b", + peer_dbus_get_connected, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Interface", + "s", + peer_dbus_get_interface, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("LocalAddress", + "s", + peer_dbus_get_local_address, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("RemoteAddress", + "s", + peer_dbus_get_remote_address, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_SIGNAL("ProvisionDiscovery", "ss", 0), + SD_BUS_VTABLE_END +}; + +static int peer_dbus_find(sd_bus *bus, + const char *path, + const char *interface, + void *data, + void **found, + sd_bus_error *err) +{ + _shl_free_ char *label = NULL; + struct manager *m = data; + struct link *l; + struct peer *p; + char *sep; + int r; + + r = sd_bus_path_decode(path, + "/org/freedesktop/miracle/wifi/peer", + &label); + if (r <= 0) + return r; + + sep = strchr(label, '@'); + if (sep) { + *sep = 0; + l = manager_find_link_by_label(m, sep + 1); + if (!l || !l->public) + return 0; + + p = link_find_peer_by_label(l, label); + if (!p || !p->public) + return 0; + } else { + p = NULL; + + MANAGER_FOREACH_LINK(l, m) { + if (!l->public) + continue; + + p = link_find_peer_by_label(l, label); + if (p) + break; + } + + if (!p || !p->public) + return 0; + } + + *found = p; + return 1; +} + +void peer_dbus_properties_changed(struct peer *p, const char *prop, ...) +{ + _shl_free_ char *node = NULL; + char **strv; + int r; + + if (!p->public) + return; + + node = peer_dbus_get_path(p); + if (!node) + return; + + strv = strv_from_stdarg_alloca(prop); + r = sd_bus_emit_properties_changed_strv(p->l->m->bus, + node, + "org.freedesktop.miracle.wifi.Peer", + strv); + if (r < 0) + log_vERR(r); +} + +void peer_dbus_provision_discovery(struct peer *p, + const char *type, + const char *pin) +{ + _shl_free_ char *node = NULL; + int r; + + if (!type) + return; + if (!pin) + pin = ""; + + node = peer_dbus_get_path(p); + if (!node) + return; + + r = sd_bus_emit_signal(p->l->m->bus, + node, + "org.freedesktop.miracle.wifi.Peer", + "ProvisionDiscovery", + "ss", type, pin); + if (r < 0) + log_vERR(r); +} + +void peer_dbus_added(struct peer *p) +{ + _shl_free_ char *node = NULL; + int r; + + node = peer_dbus_get_path(p); + if (!node) + return; + + r = sd_bus_emit_interfaces_added(p->l->m->bus, + node, + /* + "org.freedesktop.DBus.Properties", + "org.freedesktop.DBus.Introspectable", + */ + "org.freedesktop.miracle.wifi.Peer", + NULL); + if (r < 0) + log_vERR(r); +} + +void peer_dbus_removed(struct peer *p) +{ + _shl_free_ char *node = NULL; + int r; + + node = peer_dbus_get_path(p); + if (!node) + return; + + r = sd_bus_emit_interfaces_removed(p->l->m->bus, + node, + /* + "org.freedesktop.DBus.Properties", + "org.freedesktop.DBus.Introspectable", + */ + "org.freedesktop.miracle.wifi.Peer", + NULL); + if (r < 0) + log_vERR(r); +} + +/* + * Link DBus + */ + +static int link_dbus_get_interface_index(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append_basic(reply, 'u', &l->ifindex); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_get_interface_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append_basic(reply, 's', l->ifname); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_get_friendly_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append(reply, "s", link_get_friendly_name(l)); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_set_friendly_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + const char *name; + int r; + + r = sd_bus_message_read(value, "s", &name); + if (r < 0) + return r; + + if (!name || !*name) + return -EINVAL; + + return link_set_friendly_name(l, name); +} + +static int link_dbus_get_p2p_scanning(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append(reply, "b", link_get_p2p_scanning(l)); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_set_p2p_scanning(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + bool val; + int r; + + r = sd_bus_message_read(value, "b", &val); + if (r < 0) + return r; + + return link_set_p2p_scanning(l, val); +} + +static const sd_bus_vtable link_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("InterfaceIndex", + "u", + link_dbus_get_interface_index, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("InterfaceName", + "s", + link_dbus_get_interface_name, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_WRITABLE_PROPERTY("FriendlyName", + "s", + link_dbus_get_friendly_name, + link_dbus_set_friendly_name, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_WRITABLE_PROPERTY("P2PScanning", + "b", + link_dbus_get_p2p_scanning, + link_dbus_set_p2p_scanning, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; + +static int link_dbus_find(sd_bus *bus, + const char *path, + const char *interface, + void *data, + void **found, + sd_bus_error *err) +{ + _shl_free_ char *label = NULL; + struct manager *m = data; + struct link *l; + int r; + + r = sd_bus_path_decode(path, + "/org/freedesktop/miracle/wifi/link", + &label); + if (r <= 0) + return r; + + l = manager_find_link_by_label(m, label); + if (!l || !l->public) + return 0; + + *found = l; + return 1; +} + +void link_dbus_properties_changed(struct link *l, const char *prop, ...) +{ + _shl_free_ char *node = NULL; + char **strv; + int r; + + if (!l->public) + return; + + node = link_dbus_get_path(l); + if (!node) + return; + + strv = strv_from_stdarg_alloca(prop); + r = sd_bus_emit_properties_changed_strv(l->m->bus, + node, + "org.freedesktop.miracle.wifi.Link", + strv); + if (r < 0) + log_vERR(r); +} + +void link_dbus_added(struct link *l) +{ + _shl_free_ char *node = NULL; + int r; + + node = link_dbus_get_path(l); + if (!node) + return; + + r = sd_bus_emit_interfaces_added(l->m->bus, + node, + /* + "org.freedesktop.DBus.Properties", + "org.freedesktop.DBus.Introspectable", + */ + "org.freedesktop.miracle.wifi.Link", + NULL); + if (r < 0) + log_vERR(r); +} + +void link_dbus_removed(struct link *l) +{ + _shl_free_ char *node = NULL; + int r; + + node = link_dbus_get_path(l); + if (!node) + return; + + r = sd_bus_emit_interfaces_removed(l->m->bus, + node, + /* + "org.freedesktop.DBus.Properties", + "org.freedesktop.DBus.Introspectable", + */ + "org.freedesktop.miracle.wifi.Link", + NULL); + if (r < 0) + log_vERR(r); +} + +/* + * Manager DBus + */ + +static const sd_bus_vtable manager_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_VTABLE_END +}; + +static int manager_dbus_enumerate(sd_bus *bus, + const char *path, + void *data, + char ***out, + sd_bus_error *err) +{ + struct manager *m = data; + struct link *l; + struct peer *p; + size_t i, peer_cnt; + char **nodes, *node; + int r; + + peer_cnt = 0; + MANAGER_FOREACH_LINK(l, m) + if (l->public) + peer_cnt += l->peer_cnt; + + nodes = malloc(sizeof(*nodes) * (m->link_cnt + peer_cnt + 2)); + if (!nodes) + return log_ENOMEM(); + + i = 0; + MANAGER_FOREACH_LINK(l, m) { + if (i >= m->link_cnt + peer_cnt) { + log_warning("overflow: skipping link %s", + l->ifname); + continue; + } + + if (!l->public) + continue; + + node = link_dbus_get_path(l); + if (!node) + goto error; + + nodes[i++] = node; + + LINK_FOREACH_PEER(p, l) { + if (i >= m->link_cnt + peer_cnt) { + log_warning("overflow: skipping peer %s", + p->p2p_mac); + continue; + } + + if (!p->public) + continue; + + node = peer_dbus_get_path(p); + if (!node) + goto error; + + nodes[i++] = node; + } + } + + node = strdup("/org/freedesktop/miracle/wifi"); + if (!node) { + r = log_ENOMEM(); + goto error; + } + + nodes[i++] = node; + nodes[i] = NULL; + *out = nodes; + + return 0; + +error: + while (i--) + free(nodes[i]); + free(nodes); + return r; +} + +int manager_dbus_connect(struct manager *m) +{ + int r; + + r = sd_bus_add_object_vtable(m->bus, + "/org/freedesktop/miracle/wifi", + "org.freedesktop.miracle.wifi.Manager", + manager_dbus_vtable, + m); + if (r < 0) + goto error; + + r = sd_bus_add_node_enumerator(m->bus, + "/org/freedesktop/miracle/wifi", + manager_dbus_enumerate, + m); + if (r < 0) + goto error; + + r = sd_bus_add_fallback_vtable(m->bus, + "/org/freedesktop/miracle/wifi/link", + "org.freedesktop.miracle.wifi.Link", + link_dbus_vtable, + link_dbus_find, + m); + if (r < 0) + goto error; + + r = sd_bus_add_fallback_vtable(m->bus, + "/org/freedesktop/miracle/wifi/peer", + "org.freedesktop.miracle.wifi.Peer", + peer_dbus_vtable, + peer_dbus_find, + m); + if (r < 0) + goto error; + + r = sd_bus_add_object_manager(m->bus, "/org/freedesktop/miracle/wifi"); + if (r < 0) + goto error; + + r = sd_bus_request_name(m->bus, "org.freedesktop.miracle.wifi", 0); + if (r < 0) { + log_error("cannot claim org.freedesktop.miracle.wifi bus-name: %d", + r); + goto error_silent; + } + + return 0; + +error: + log_ERR(r); +error_silent: + manager_dbus_disconnect(m); + return r; +} + +void manager_dbus_disconnect(struct manager *m) +{ + if (!m || !m->bus) + return; + + sd_bus_release_name(m->bus, "org.freedesktop.miracle.wifi"); + sd_bus_remove_object_manager(m->bus, "/org/freedesktop/miracle/wifi"); + sd_bus_remove_fallback_vtable(m->bus, + "/org/freedesktop/miracle/wifi/peer", + "org.freedesktop.miracle.wifi.Peer", + peer_dbus_vtable, + peer_dbus_find, + m); + sd_bus_remove_fallback_vtable(m->bus, + "/org/freedesktop/miracle/wifi/link", + "org.freedesktop.miracle.wifi.Link", + link_dbus_vtable, + link_dbus_find, + m); + sd_bus_remove_node_enumerator(m->bus, + "/org/freedesktop/miracle/wifi", + manager_dbus_enumerate, + m); + sd_bus_remove_object_vtable(m->bus, + "/org/freedesktop/miracle/wifi", + "org.freedesktop.miracle.wifi.Manager", + manager_dbus_vtable, + m); +} diff --git a/src/wifi/wifid-link.c b/src/wifi/wifid-link.c new file mode 100644 index 0000000..139195a --- /dev/null +++ b/src/wifi/wifid-link.c @@ -0,0 +1,262 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#define LOG_SUBSYSTEM "link" + +#include +#include +#include +#include +#include "shl_dlist.h" +#include "shl_log.h" +#include "shl_util.h" +#include "util.h" +#include "wifid.h" + +/* + * Link Handling + */ + +struct peer *link_find_peer(struct link *l, const char *p2p_mac) +{ + char **elem; + bool res; + + res = shl_htable_lookup_str(&l->peers, p2p_mac, NULL, &elem); + if (!res) + return NULL; + + return peer_from_htable(elem); +} + +struct peer *link_find_peer_by_label(struct link *l, const char *label) +{ + char mac[MAC_STRLEN]; + + reformat_mac(mac, label); + + return link_find_peer(l, mac); +} + +int link_new(struct manager *m, + unsigned int ifindex, + const char *ifname, + struct link **out) +{ + struct link *l; + int r; + + if (!m || !ifindex || !ifname) + return log_EINVAL(); + + if (shl_htable_lookup_uint(&m->links, ifindex, NULL)) + return -EALREADY; + + log_debug("new link: %s (%u)", ifname, ifindex); + + l = calloc(1, sizeof(*l)); + if (!l) + return log_ENOMEM(); + + l->m = m; + l->ifindex = ifindex; + shl_htable_init_str(&l->peers); + + l->ifname = strdup(ifname); + if (!l->ifname) { + r = log_ENOMEM(); + goto error; + } + + r = supplicant_new(l, &l->s); + if (r < 0) + goto error; + + r = shl_htable_insert_uint(&m->links, &l->ifindex); + if (r < 0) { + log_vERR(r); + goto error; + } + + ++m->link_cnt; + log_info("add link: %s", l->ifname); + + if (out) + *out = l; + + return 0; + +error: + link_free(l); + return r; +} + +void link_free(struct link *l) +{ + if (!l) + return; + + log_debug("free link: %s (%u)", l->ifname, l->ifindex); + + link_set_managed(l, false); + + if (shl_htable_remove_uint(&l->m->links, l->ifindex, NULL)) { + log_info("remove link: %s", l->ifname); + --l->m->link_cnt; + } + + supplicant_free(l->s); + + /* link_set_managed(l, false) already removed all peers */ + shl_htable_clear_str(&l->peers, NULL, NULL); + + free(l->friendly_name); + free(l->ifname); + free(l); +} + +void link_set_managed(struct link *l, bool set) +{ + int r; + + if (!l) + return log_vEINVAL(); + if (l->managed == set) + return; + + if (set) { + log_info("manage link %s", l->ifname); + + r = supplicant_start(l->s); + if (r < 0) { + log_error("cannot start supplicant on %s", l->ifname); + return; + } + } else { + log_info("link %s no longer managed", l->ifname); + supplicant_stop(l->s); + } + + l->managed = set; +} + +int link_renamed(struct link *l, const char *ifname) +{ + char *t; + + if (!l || !ifname) + return log_EINVAL(); + if (!strcmp(l->ifname, ifname)) + return 0; + + log_info("link %s (%u) was renamed to %s", + l->ifname, l->ifindex, ifname); + + t = strdup(ifname); + if (!t) + return log_ENOMEM(); + + free(l->ifname); + l->ifname = t; + + link_dbus_properties_changed(l, "InterfaceName", NULL); + + return 0; +} + +int link_set_friendly_name(struct link *l, const char *name) +{ + char *t; + int r; + + if (!l || !name || !*name) + return log_EINVAL(); + + t = strdup(name); + if (!t) + return log_ENOMEM(); + + if (supplicant_is_ready(l->s)) { + r = supplicant_set_friendly_name(l->s, name); + if (r < 0) { + free(t); + return r; + } + } + + free(l->friendly_name); + l->friendly_name = t; + link_dbus_properties_changed(l, "FriendlyName", NULL); + + return 0; +} + +const char *link_get_friendly_name(struct link *l) +{ + if (!l) + return NULL; + + return l->friendly_name; +} + +int link_set_p2p_scanning(struct link *l, bool set) +{ + if (!l) + return log_EINVAL(); + + if (set) { + return supplicant_p2p_start_scan(l->s); + } else { + supplicant_p2p_stop_scan(l->s); + return 0; + } +} + +bool link_get_p2p_scanning(struct link *l) +{ + return l && supplicant_p2p_scanning(l->s); +} + +void link_supplicant_started(struct link *l) +{ + if (!l || l->public) + return; + + log_debug("link %s started", l->ifname); + l->public = true; + link_dbus_added(l); +} + +void link_supplicant_stopped(struct link *l) +{ + if (!l || !l->public) + return; + + log_debug("link %s stopped", l->ifname); + link_dbus_removed(l); + l->public = false; +} + +void link_supplicant_p2p_scan_changed(struct link *l, bool new_value) +{ + if (!l) + return; + + link_dbus_properties_changed(l, "P2PScanning", NULL); +} diff --git a/src/wifi/wifid-peer.c b/src/wifi/wifid-peer.c new file mode 100644 index 0000000..6022ca9 --- /dev/null +++ b/src/wifi/wifid-peer.c @@ -0,0 +1,198 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#define LOG_SUBSYSTEM "peer" + +#include +#include +#include +#include +#include "shl_dlist.h" +#include "shl_log.h" +#include "shl_util.h" +#include "util.h" +#include "wifid.h" + +/* + * Peer Handling + */ + +int peer_new(struct link *l, + const char *p2p_mac, + struct peer **out) +{ + char mac[MAC_STRLEN]; + struct peer *p; + int r; + + if (!l || !p2p_mac) + return log_EINVAL(); + + reformat_mac(mac, p2p_mac); + + if (shl_htable_lookup_str(&l->peers, mac, NULL, NULL)) + return -EALREADY; + + log_debug("new peer: %s @ %s", mac, l->ifname); + + p = calloc(1, sizeof(*p)); + if (!p) + return log_ENOMEM(); + + p->l = l; + p->p2p_mac = calloc(1, MAC_STRLEN); + if (!p->p2p_mac) { + r = log_ENOMEM(); + goto error; + } + strncpy(p->p2p_mac, mac, MAC_STRLEN - 1); + + r = shl_htable_insert_str(&l->peers, &p->p2p_mac, NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + + ++l->peer_cnt; + log_info("add peer: %s", p->p2p_mac); + + if (out) + *out = p; + + return 0; + +error: + peer_free(p); + return r; +} + +void peer_free(struct peer *p) +{ + if (!p) + return; + + log_debug("free peer: %s @ %s", p->p2p_mac, p->l->ifname); + + if (shl_htable_remove_str(&p->l->peers, p->p2p_mac, NULL, NULL)) { + log_info("remove peer: %s", p->p2p_mac); + --p->l->peer_cnt; + } + + free(p->p2p_mac); + free(p); +} + +const char *peer_get_friendly_name(struct peer *p) +{ + if (!p) + return NULL; + + return supplicant_peer_get_friendly_name(p->sp); +} + +const char *peer_get_interface(struct peer *p) +{ + if (!p || !p->connected) + return NULL; + + return supplicant_peer_get_interface(p->sp); +} + +const char *peer_get_local_address(struct peer *p) +{ + if (!p || !p->connected) + return NULL; + + return supplicant_peer_get_local_address(p->sp); +} + +const char *peer_get_remote_address(struct peer *p) +{ + if (!p || !p->connected) + return NULL; + + return supplicant_peer_get_remote_address(p->sp); +} + +int peer_connect(struct peer *p, const char *prov, const char *pin) +{ + if (!p) + return log_EINVAL(); + + return supplicant_peer_connect(p->sp, prov, pin); +} + +void peer_disconnect(struct peer *p) +{ + if (!p) + return log_vEINVAL(); + + supplicant_peer_disconnect(p->sp); +} + +void peer_supplicant_started(struct peer *p) +{ + if (!p || p->public) + return; + + log_debug("peer %s @ %s started", p->p2p_mac, p->l->ifname); + p->public = true; + peer_dbus_added(p); +} + +void peer_supplicant_stopped(struct peer *p) +{ + if (!p || !p->public) + return; + + log_debug("peer %s @ %s stopped", p->p2p_mac, p->l->ifname); + peer_dbus_removed(p); + p->public = false; +} + +void peer_supplicant_friendly_name_changed(struct peer *p) +{ + if (!p || !p->public) + return; + + peer_dbus_properties_changed(p, "FriendlyName", NULL); +} + +void peer_supplicant_provision_discovery(struct peer *p, + const char *prov, + const char *pin) +{ + if (!p || !p->public) + return; + + peer_dbus_provision_discovery(p, prov, pin); +} + +void peer_supplicant_connected_changed(struct peer *p, bool connected) +{ + if (!p || p->connected == connected) + return; + + p->connected = connected; + peer_dbus_properties_changed(p, "Connected", + "Interface", + "LocalAddress", + "RemoteAddress", + NULL); +} diff --git a/src/wifi/wifid-supplicant.c b/src/wifi/wifid-supplicant.c new file mode 100644 index 0000000..1c2e992 --- /dev/null +++ b/src/wifi/wifid-supplicant.c @@ -0,0 +1,2440 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#define LOG_SUBSYSTEM "supplicant" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shl_dlist.h" +#include "shl_log.h" +#include "shl_util.h" +#include "util.h" +#include "wifid.h" +#include "wpas.h" + +struct supplicant_group { + unsigned long users; + struct shl_dlist list; + struct supplicant *s; + struct supplicant_peer *sp; + + unsigned int subnet; + char *ifname; + char *local_addr; + + int dhcp_comm; + sd_event_source *dhcp_comm_source; + pid_t dhcp_pid; + sd_event_source *dhcp_pid_source; + + bool go : 1; +}; + +struct supplicant_peer { + struct peer *p; + struct supplicant *s; /* shortcut for p->l->s */ + struct supplicant_group *g; + + char *friendly_name; + char *remote_addr; + char *prov; + char *pin; + char *sta_mac; +}; + +struct supplicant { + struct link *l; + + pid_t pid; + sd_event_source *child_source; + sd_event_source *timer_source; + struct shl_ratelimit restart_rate; + struct shl_ratelimit exec_rate; + uint64_t open_cnt; + char *conf_path; + char *global_ctrl; + char *dev_ctrl; + + struct wpas *bus_global; + struct wpas *bus_dev; + + size_t setup_cnt; + + char *p2p_mac; + struct shl_dlist groups; + struct supplicant_peer *pending; + + bool running : 1; + bool has_p2p : 1; + bool has_wfd : 1; + bool p2p_scanning : 1; +}; + +static void supplicant_failed(struct supplicant *s); +static void supplicant_peer_drop_group(struct supplicant_peer *sp); + +static struct supplicant_peer *find_peer_by_p2p_mac(struct supplicant *s, + const char *p2p_mac) +{ + struct peer *p; + + p = link_find_peer(s->l, p2p_mac); + if (p) + return p->sp; + + return NULL; +} + +static struct supplicant_peer *find_peer_by_any_mac(struct supplicant *s, + const char *mac) +{ + struct peer *p; + + LINK_FOREACH_PEER(p, s->l) { + if (!strcmp(p->p2p_mac, mac) || !strcmp(p->sp->sta_mac, mac)) + return p->sp; + } + + return NULL; +} + +static struct supplicant_group *find_group_by_ifname(struct supplicant *s, + const char *ifname) +{ + struct shl_dlist *i; + struct supplicant_group *g; + + shl_dlist_for_each(i, &s->groups) { + g = shl_dlist_entry(i, struct supplicant_group, list); + if (!strcmp(g->ifname, ifname)) + return g; + } + + return NULL; +} + +/* + * Supplicant Groups + * The wpas daemon can create separate interfaces on-the-fly. Usually, they're + * used for P2P operations, but others are possible, too. A supplicant_group + * object is a shared dummy that is created once the iface appears and removed + * once the last user drops it again. + * As the initial iface is always created without users, we need some timer that + * controls how long unused ifaces stay around. If the target device (or any + * other peer) does not use the iface in a reasonable time, we simply destroy it + * again. + */ + +static void supplicant_group_free(struct supplicant_group *g) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + struct peer *p; + int r; + + if (!g) + return; + + log_debug("free group %s", g->ifname); + + r = wpas_message_new_request(g->s->bus_global, + "P2P_GROUP_REMOVE", + &m); + if (r >= 0) { + r = wpas_message_append(m, "s", g->ifname); + if (r >= 0) { + r = wpas_call_async(g->s->bus_global, + m, NULL, NULL, 0, NULL); + if (r < 0) + log_vERR(r); + } else { + log_vERR(r); + } + } else { + log_vERR(r); + } + + if (g->dhcp_pid > 0) { + sd_event_source_unref(g->dhcp_pid_source); + g->dhcp_pid_source = NULL; + + log_debug("killing DHCP-process pid:%d..", + g->dhcp_pid); + r = kill(g->dhcp_pid, SIGTERM); + if (r < 0) + r = kill(g->dhcp_pid, SIGKILL); + if (r < 0) + log_warning("cannot kill DHCP-process pid:%d: %m", + (int)g->dhcp_pid); + g->dhcp_pid = 0; + } + + if (g->dhcp_comm >= 0) { + sd_event_source_unref(g->dhcp_comm_source); + g->dhcp_comm_source = NULL; + close(g->dhcp_comm); + g->dhcp_comm = -1; + } + + LINK_FOREACH_PEER(p, g->s->l) + if (p->sp->g == g) + supplicant_peer_drop_group(p->sp); + + shl_dlist_unlink(&g->list); + + free(g->local_addr); + free(g->ifname); + free(g); +} + +static int supplicant_group_comm_fn(sd_event_source *source, + int fd, + uint32_t mask, + void *data) +{ + struct supplicant_group *g = data; + struct supplicant_peer *sp; + struct peer *p; + char buf[512], *t, *ip; + ssize_t l; + + l = recv(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT); + if (l < 0) { + l = -errno; + if (l == -EAGAIN || l == -EINTR) + return 0; + + log_vERRNO(); + goto error; + } else if (!l) { + log_error("HUP on dhcp-comm socket on %s", g->ifname); + goto error; + } else if (l > sizeof(buf) - 1) { + l = sizeof(buf) - 1; + } + + buf[l] = 0; + log_debug("dhcp-comm-%s: %s", g->ifname, buf); + + /* we only parse "X:" right now */ + if (l < 3 || buf[1] != ':' || !buf[2]) + return 0; + + t = strdup(&buf[2]); + if (!t) { + log_vENOMEM(); + return 0; + } + + switch (buf[0]) { + case 'L': + free(g->local_addr); + g->local_addr = t; + break; + case 'G': + if (g->sp) { + free(g->sp->remote_addr); + g->sp->remote_addr = t; + } else { + free(t); + } + break; + case 'R': + ip = strchr(t, ' '); + if (!ip || ip == t || !ip[1]) { + log_warning("invalid dhcp 'R' line: %s", t); + free(t); + break; + } + + *ip++ = 0; + sp = find_peer_by_any_mac(g->s, t); + if (sp) { + ip = strdup(ip); + if (!ip) { + log_vENOMEM(); + free(t); + break; + } + + free(t); + + free(sp->remote_addr); + sp->remote_addr = ip; + } else { + log_debug("ignore 'R' line for unknown mac"); + free(t); + } + + break; + default: + free(t); + break; + } + + if (g->local_addr) { + if (g->sp) { + p = g->sp->p; + if (p->sp->remote_addr) + peer_supplicant_connected_changed(p, true); + } else { + LINK_FOREACH_PEER(p, g->s->l) { + if (p->sp->g != g || !p->sp->remote_addr) + continue; + + peer_supplicant_connected_changed(p, true); + } + } + } + + return 0; + +error: + supplicant_group_free(g); + return 0; +} + +static int supplicant_group_pid_fn(sd_event_source *source, + const siginfo_t *info, + void *data) +{ + struct supplicant_group *g = data; + + log_error("DHCP client/server for %s died, stopping connection", + g->ifname); + + supplicant_group_free(g); + + return 0; +} + +static int supplicant_group_spawn_dhcp_server(struct supplicant_group *g, + unsigned int subnet) +{ + char *argv[64], loglevel[64], commfd[64], prefix[64]; + char journal_id[128]; + int i, r, fds[2], fd_journal; + pid_t pid; + sigset_t mask; + + r = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); + if (r < 0) + return log_ERRNO(); + + pid = fork(); + if (pid < 0) { + close(fds[0]); + close(fds[1]); + return log_ERRNO(); + } else if (!pid) { + /* child */ + + close(fds[0]); + sprintf(loglevel, "%u", log_max_sev); + sprintf(commfd, "%d", fds[1]); + sprintf(prefix, "192.168.%u", subnet); + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout/stderr to journal */ + sprintf(journal_id, "miracle-dhcp-%s", g->ifname); + fd_journal = sd_journal_stream_fd(journal_id, LOG_INFO, false); + if (fd_journal >= 0) { + /* dup journal-fd to stdout and stderr */ + dup2(fd_journal, 1); + dup2(fd_journal, 2); + } else { + /* no journal? redirect stdout to parent's stderr */ + dup2(2, 1); + } + + i = 0; + argv[i++] = (char*) BUILD_BINDIR "/miracle-dhcp"; + argv[i++] = "--server"; + argv[i++] = "--prefix"; + argv[i++] = prefix; + argv[i++] = "--log-level"; + argv[i++] = loglevel; + argv[i++] = "--netdev"; + argv[i++] = g->ifname; + argv[i++] = "--comm-fd"; + argv[i++] = commfd; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + close(fds[1]); + g->dhcp_comm = fds[0]; + g->dhcp_pid = pid; + + return 0; +} + +static int supplicant_group_spawn_dhcp_client(struct supplicant_group *g) +{ + char *argv[64], loglevel[64], commfd[64]; + char journal_id[128]; + int i, r, fds[2], fd_journal; + pid_t pid; + sigset_t mask; + + r = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); + if (r < 0) + return log_ERRNO(); + + pid = fork(); + if (pid < 0) { + close(fds[0]); + close(fds[1]); + return log_ERRNO(); + } else if (!pid) { + /* child */ + + close(fds[0]); + sprintf(loglevel, "%u", log_max_sev); + sprintf(commfd, "%d", fds[1]); + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout/stderr to journal */ + sprintf(journal_id, "miracle-dhcp-%s", g->ifname); + fd_journal = sd_journal_stream_fd(journal_id, LOG_INFO, false); + if (fd_journal >= 0) { + /* dup journal-fd to stdout and stderr */ + dup2(fd_journal, 1); + dup2(fd_journal, 2); + } else { + /* no journal? redirect stdout to parent's stderr */ + dup2(2, 1); + } + + i = 0; + argv[i++] = (char*) BUILD_BINDIR "/miracle-dhcp"; + argv[i++] = "--log-level"; + argv[i++] = loglevel; + argv[i++] = "--netdev"; + argv[i++] = g->ifname; + argv[i++] = "--comm-fd"; + argv[i++] = commfd; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + close(fds[1]); + g->dhcp_comm = fds[0]; + g->dhcp_pid = pid; + + return 0; +} + +static int supplicant_group_new(struct supplicant *s, + struct supplicant_group **out, + const char *ifname, + bool go) +{ + struct supplicant_group *g, *j; + struct shl_dlist *i; + unsigned int subnet; + int r; + + if (!s || !ifname) + return log_EINVAL(); + + log_debug("new group: %s", ifname); + + g = calloc(1, sizeof(*g)); + if (!g) + return log_ENOMEM(); + + g->s = s; + g->go = go; + g->dhcp_comm = -1; + + g->ifname = strdup(ifname); + if (!g->ifname) { + r = log_ENOMEM(); + goto error; + } + + if (g->go) { + /* find free subnet */ + for (subnet = 50; subnet < 256; ++subnet) { + shl_dlist_for_each(i, &s->groups) { + j = shl_dlist_entry(i, + struct supplicant_group, + list); + if (j->subnet == j->subnet) + break; + } + + if (i == &s->groups) { + g->subnet = subnet; + break; + } + } + + if (g->subnet) { + r = supplicant_group_spawn_dhcp_server(g, g->subnet); + } else { + log_warning("out of free subnets for local groups"); + r = -EINVAL; + } + } else { + r = supplicant_group_spawn_dhcp_client(g); + } + if (r < 0) + goto error; + + r = sd_event_add_io(s->l->m->event, + &g->dhcp_comm_source, + g->dhcp_comm, + EPOLLHUP | EPOLLERR | EPOLLIN, + supplicant_group_comm_fn, + g); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_event_add_child(s->l->m->event, + &g->dhcp_pid_source, + g->dhcp_pid, + WEXITED, + supplicant_group_pid_fn, + g); + if (r < 0) { + log_vERR(r); + goto error; + } + + shl_dlist_link(&s->groups, &g->list); + if (out) + *out = g; + return 0; + +error: + supplicant_group_free(g); + return r; +} + +static void supplicant_group_keep(struct supplicant_group *g) +{ + if (!g) + return; + + ++g->users; +} + +static void supplicant_group_drop(struct supplicant_group *g) +{ + if (!g || !g->users || --g->users) + return; + + supplicant_group_free(g); +} + +/* + * Supplicant Peers + * Wpas has a quite high-level P2P interface, which makes it impossible to deal + * with P2P devices as stations, but instead requires us to handle them as + * separate peers. + * A supplicant_peer object represents a remote P2P peer. Each peer might have + * multiple real stations, but the wpas API limits us to a single connection. + * Therefore, we treat each peer as a whole and allow only one private + * connection to it, regardless whether it's a local GO or client. + */ + +static void supplicant_peer_set_group(struct supplicant_peer *sp, + struct supplicant_group *g) +{ + if (sp->g) { + if (sp->g == g) + return; + + supplicant_peer_drop_group(sp); + } + + sp->g = g; + supplicant_group_keep(g); +} + +static void supplicant_peer_drop_group(struct supplicant_peer *sp) +{ + if (!sp->g) + return; + + if (sp->g->sp == sp) + sp->g = NULL; + + supplicant_group_drop(sp->g); + sp->g = NULL; + + free(sp->remote_addr); + sp->remote_addr = NULL; + free(sp->sta_mac); + sp->sta_mac = NULL; + + peer_supplicant_connected_changed(sp->p, false); +} + +static int supplicant_peer_new(struct supplicant *s, + const char *p2p_mac, + struct supplicant_peer **out) +{ + struct supplicant_peer *sp; + struct peer *p; + int r; + + r = peer_new(s->l, p2p_mac, &p); + if (r < 0) { + log_error("cannot add new supplicant-peer for %s: %d", + p2p_mac, r); + return r; + } + + sp = calloc(1, sizeof(*sp)); + if (!sp) { + r = log_ENOMEM(); + peer_free(p); + return r; + } + + sp->p = p; + sp->s = s; + p->sp = sp; + + *out = sp; + return 0; +} + +static void supplicant_peer_free(struct supplicant_peer *sp) +{ + if (!sp) + return; + + supplicant_peer_drop_group(sp); + peer_supplicant_stopped(sp->p); + peer_free(sp->p); + + free(sp->sta_mac); + free(sp->remote_addr); + free(sp->pin); + free(sp->prov); + free(sp->friendly_name); + free(sp); +} + +const char *supplicant_peer_get_friendly_name(struct supplicant_peer *sp) +{ + if (!sp) + return NULL; + + return sp->friendly_name; +} + +const char *supplicant_peer_get_interface(struct supplicant_peer *sp) +{ + if (!sp || !sp->g) + return NULL; + + return sp->g->ifname; +} + +const char *supplicant_peer_get_local_address(struct supplicant_peer *sp) +{ + if (!sp || !sp->g) + return NULL; + + return sp->g->local_addr; +} + +const char *supplicant_peer_get_remote_address(struct supplicant_peer *sp) +{ + if (!sp || !sp->g) + return NULL; + + return sp->remote_addr; +} + +int supplicant_peer_connect(struct supplicant_peer *sp, + const char *prov_type, + const char *pin) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + int r; + + if (!sp || !sp->s->running) + return log_EINVAL(); + if (sp->g) + return 0; + + if (!prov_type && !(prov_type = sp->prov)) + prov_type = "pbc"; + if (!pin) + pin = sp->pin; + + log_debug("connect to %s via %s/%s", sp->p->p2p_mac, prov_type, pin); + + r = wpas_message_new_request(sp->s->bus_global, + "P2P_CONNECT", + &m); + if (r < 0) + return log_ERR(r); + + r = wpas_message_append(m, "s", sp->p->p2p_mac); + if (r < 0) + return log_ERR(r); + + if (!strcmp(prov_type, "pbc")) { + r = wpas_message_append(m, "ss", "pbc", "display"); + if (r < 0) + return log_ERR(r); + } else if (!strcmp(prov_type, "display")) { + if (!pin || !*pin) + return -EINVAL; + + r = wpas_message_append(m, "ss", pin, "display"); + if (r < 0) + return log_ERR(r); + } else if (!strcmp(prov_type, "pin")) { + if (!pin || !*pin) + return -EINVAL; + + r = wpas_message_append(m, "ss", pin, "keypad"); + if (r < 0) + return log_ERR(r); + } else { + return -EINVAL; + } + + r = wpas_call_async(sp->s->bus_global, m, NULL, NULL, 0, NULL); + if (r < 0) + return log_ERR(r); + + return 0; +} + +void supplicant_peer_disconnect(struct supplicant_peer *sp) +{ + if (!sp) + return; + + log_debug("disconnect from %s", sp->p->p2p_mac); + + /* clear cache even if not connected; can be used as custom reset */ + free(sp->pin); + sp->pin = NULL; + free(sp->prov); + sp->prov = NULL; + + supplicant_peer_drop_group(sp); +} + +/* + * Supplicant Communication + * Following are the core communication elements of the supplicant handling. + * The supplicant core control handling starts the communication with + * supplicant_started() and stops it with supplicant_stopped(). The + * communication part thus does not have to deal with supplicant + * process-execution, restarting and ctrl-iface establishment. + * + * The communication is highly asynchronous. Once we're started, we know that + * we have a working comm-channel to wpas. We send initialization sequences to + * wpas and once everything is set-up, we call into the link-layer to notify + * them that the link is ready now. + * + * At any time, if there is a fatal error, we simply call supplicant_failed(), + * which is part of the core supplicant control interface. It will tear down the + * supplicant, stop our communication layer via supplicant_stopped() and then + * schedule a restart. + */ + +static void supplicant_parse_peer(struct supplicant *s, + struct wpas_message *m) +{ + struct supplicant_peer *sp; + const char *mac, *name; + char *t; + int r; + + r = wpas_message_read(m, "s", &mac); + if (r < 0) { + log_debug("no p2p-mac in P2P_PEER information: %s", + wpas_message_get_raw(m)); + return; + } + + sp = find_peer_by_p2p_mac(s, mac); + if (!sp) { + r = supplicant_peer_new(s, mac, &sp); + if (r < 0) + return; + } + + r = wpas_message_dict_read(m, "device_name", 's', &name); + if (r >= 0) { + t = strdup(name); + if (!t) { + log_vENOMEM(); + } else { + free(sp->friendly_name); + sp->friendly_name = t; + peer_supplicant_friendly_name_changed(sp->p); + } + } else { + log_debug("no device_name in P2P_PEER information: %s", + wpas_message_get_raw(m)); + } + + if (s->running) + peer_supplicant_started(sp->p); +} + +static void supplicant_event_p2p_find_stopped(struct supplicant *s, + struct wpas_message *m) +{ + if (!s->p2p_scanning) + return; + + log_debug("p2p-scanning stopped on %s", s->l->ifname); + s->p2p_scanning = false; + link_supplicant_p2p_scan_changed(s->l, false); +} + +static int supplicant_p2p_peer_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + struct supplicant *s = data; + + if (wpas_message_is_fail(reply)) + return 0; + + supplicant_parse_peer(s, reply); + return 0; +} + +static void supplicant_event_p2p_device_found(struct supplicant *s, + struct wpas_message *ev) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + const char *mac; + int r; + + /* + * The P2P-DEVICE-FOUND event is quite small. Request a full + * peer-report and only use the peer once it returns. + */ + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &mac); + if (r < 0) { + log_debug("no p2p_dev_addr in P2P-DEVICE-FOUND: %s", + wpas_message_get_raw(ev)); + return; + } + + r = wpas_message_new_request(s->bus_global, + "P2P_PEER", + &m); + if (r < 0) + goto error; + + r = wpas_message_append(m, "s", mac); + if (r < 0) + goto error; + + r = wpas_call_async(s->bus_global, + m, + supplicant_p2p_peer_fn, + s, + 0, + NULL); + if (r < 0) + goto error; + + log_debug("requesting data for new peer %s", mac); + return; + +error: + log_warning("cannot retrieve peer information from wpas for %s", + mac); +} + +static void supplicant_event_p2p_device_lost(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + const char *mac; + int r; + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &mac); + if (r < 0) { + log_debug("no p2p_dev_addr in P2P-DEVICE-LOST: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, mac); + if (sp) { + log_debug("lost peer %s", mac); + supplicant_peer_free(sp); + } else { + log_debug("stale P2P-DEVICE-LOST: %s", + wpas_message_get_raw(ev)); + } +} + +static void supplicant_event_p2p_prov_disc_pbc_req(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + const char *mac; + char *t; + int r; + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &mac); + if (r < 0) { + log_debug("no p2p_dev_addr in P2P-PROV-DISC-PBC-REQ: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, mac); + if (!sp) { + log_debug("stale P2P-PROV-DISC-PBC-REQ: %s", + wpas_message_get_raw(ev)); + return; + } + + t = strdup("pbc"); + if (!t) + return log_vENOMEM(); + + free(sp->prov); + sp->prov = t; + free(sp->pin); + sp->pin = NULL; + + if (!sp->g) { + log_debug("PBC provision discovery for %s", mac); + peer_supplicant_provision_discovery(sp->p, sp->prov, sp->pin); + } else { + log_debug("PBC provision discovery for already connected peer %s", + mac); + } +} + +static void supplicant_event_p2p_prov_disc_show_pin(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + const char *mac, *pin; + char *t, *u; + int r; + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &mac); + if (r < 0) { + log_debug("no p2p_dev_addr in P2P-PROV-DISC-SHOW-PIN: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, mac); + if (!sp) { + log_debug("stale P2P-PROV-DISC-SHOW-PIN: %s", + wpas_message_get_raw(ev)); + return; + } + + r = wpas_message_argv_read(ev, 1, 's', &pin); + if (r < 0) { + log_debug("no pin given in P2P-PROV-DISC-SHOW-PIN: %s", + wpas_message_get_raw(ev)); + return; + } + + t = strdup("display"); + if (!t) + return log_vENOMEM(); + + u = strdup(pin); + if (!u) { + free(t); + return log_vENOMEM(); + } + + free(sp->prov); + sp->prov = t; + free(sp->pin); + sp->pin = u; + + if (!sp->g) { + log_debug("DISPLAY provision discovery for %s", mac); + peer_supplicant_provision_discovery(sp->p, sp->prov, sp->pin); + } else { + log_debug("DISPLAY provision discovery for already connected peer %s", + mac); + } +} + +static void supplicant_event_p2p_prov_disc_enter_pin(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + const char *mac; + char *t; + int r; + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &mac); + if (r < 0) { + log_debug("no p2p_dev_addr in P2P-PROV-DISC-ENTER-PIN: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, mac); + if (!sp) { + log_debug("stale P2P-PROV-DISC-ENTER-PIN: %s", + wpas_message_get_raw(ev)); + return; + } + + t = strdup("pin"); + if (!t) + return log_vENOMEM(); + + free(sp->prov); + sp->prov = t; + free(sp->pin); + sp->pin = NULL; + + if (!sp->g) { + log_debug("PIN provision discovery for %s", mac); + peer_supplicant_provision_discovery(sp->p, sp->prov, sp->pin); + } else { + log_debug("PIN provision discovery for already connected peer %s", + mac); + } +} + +static void supplicant_event_p2p_go_neg_success(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + const char *mac, *sta; + char *t; + int r; + + r = wpas_message_dict_read(ev, "peer_dev", 's', &mac); + if (r < 0) { + log_debug("no peer_dev in P2P-GO-NEG-SUCCESS: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, mac); + if (!sp) { + log_debug("stale P2P-GO-NEG-SUCCESS: %s", + wpas_message_get_raw(ev)); + return; + } + + if (sp->g) { + log_debug("P2P-GO-NEG-SUCCESS on already connected peer: %s", + wpas_message_get_raw(ev)); + return; + } + + r = wpas_message_dict_read(ev, "peer_iface", 's', &sta); + if (r < 0) { + log_debug("no peer_iface in P2P-GO-NEG-SUCCESS: %s", + wpas_message_get_raw(ev)); + return; + } + + if (!sp->sta_mac || strcmp(sp->sta_mac, sta)) { + t = strdup(sta); + if (!t) + return log_vENOMEM(); + + log_debug("set STA-MAC for %s from %s to %s (via GO-NEG-SUCCESS)", + mac, sp->sta_mac ? : "", sta); + + free(sp->sta_mac); + sp->sta_mac = t; + } +} + +static void supplicant_event_p2p_group_started(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + struct supplicant_group *g; + const char *mac, *ifname, *go; + bool is_go; + int r; + + r = wpas_message_dict_read(ev, "go_dev_addr", 's', &mac); + if (r < 0) { + log_debug("no go_dev_addr in P2P-GROUP-STARTED: %s", + wpas_message_get_raw(ev)); + return; + } + + r = wpas_message_argv_read(ev, 0, 's', &ifname); + if (r < 0) { + log_debug("no ifname in P2P-GROUP-STARTED: %s", + wpas_message_get_raw(ev)); + return; + } + + r = wpas_message_argv_read(ev, 1, 's', &go); + if (r < 0) { + log_debug("no GO/client type in P2P-GROUP-STARTED: %s", + wpas_message_get_raw(ev)); + return; + } + + is_go = !strcmp(go, "GO"); + + sp = find_peer_by_p2p_mac(s, mac); + if (!sp) { + if (!s->p2p_mac || strcmp(s->p2p_mac, mac)) { + log_debug("stray P2P-GROUP-STARTED: %s", + wpas_message_get_raw(ev)); + return; + } + } + + g = find_group_by_ifname(s, ifname); + if (!g) { + r = supplicant_group_new(s, &g, ifname, is_go); + if (r < 0) + return; + + log_debug("start %s group on new group %s as %s/%d", + sp ? "remote" : "local", g->ifname, go, is_go); + } else { + log_debug("start %s group on existing group %s as %s/%d", + sp ? "remote" : "local", g->ifname, go, is_go); + } + + if (sp) { + supplicant_peer_set_group(sp, g); + g->sp = sp; + } + + /* TODO: For local-groups, we should schedule some timer so the + * group gets removed in case the remote side never connects. */ +} + +static void supplicant_event_p2p_group_removed(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_group *g; + const char *ifname; + int r; + + r = wpas_message_argv_read(ev, 0, 's', &ifname); + if (r < 0) { + log_debug("no ifname in P2P-GROUP-REMOVED: %s", + wpas_message_get_raw(ev)); + return; + } + + g = find_group_by_ifname(s, ifname); + if (!g) { + log_debug("stray P2P-GROUP-REMOVED: %s", + wpas_message_get_raw(ev)); + return; + } + + log_debug("remove group %s", ifname); + supplicant_group_free(g); +} + +static void supplicant_event_ap_sta_connected(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + struct supplicant_group *g; + const char *sta_mac, *p2p_mac, *ifname; + char *t; + int r; + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &p2p_mac); + if (r < 0) { + log_debug("no p2p_dev_addr in AP-STA-CONNECTED: %s", + wpas_message_get_raw(ev)); + return; + } + + r = wpas_message_argv_read(ev, 0, 's', &sta_mac); + if (r < 0) { + log_debug("no station-mac in AP-STA-CONNECTED: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, p2p_mac); + if (!sp) { + log_debug("stray AP-STA-CONNECTED: %s", + wpas_message_get_raw(ev)); + return; + } + + if (sp->g) { + log_debug("AP-STA-CONNECTED for already connected peer: %s", + wpas_message_get_raw(ev)); + return; + } + + if (!sp->sta_mac || strcmp(sp->sta_mac, sta_mac)) { + t = strdup(sta_mac); + if (!t) + return log_vENOMEM(); + + log_debug("set STA-MAC for %s from %s to %s (via AP-STA-CONNECTED)", + p2p_mac, sp->sta_mac ? : "", sta_mac); + + free(sp->sta_mac); + sp->sta_mac = t; + } + + ifname = wpas_message_get_ifname(ev); + if (!ifname) { + log_debug("no ifname in AP-STA-CONNECTED: %s", + wpas_message_get_raw(ev)); + return; + } + + g = find_group_by_ifname(s, ifname); + if (!g) { + log_debug("unknown ifname %s in AP-STA-CONNECTED: %s", + ifname, wpas_message_get_raw(ev)); + return; + } + + log_debug("bind peer %s to existing local group %s", p2p_mac, ifname); + supplicant_peer_set_group(sp, g); +} + +static void supplicant_event_ap_sta_disconnected(struct supplicant *s, + struct wpas_message *ev) +{ + struct supplicant_peer *sp; + const char *p2p_mac; + int r; + + r = wpas_message_dict_read(ev, "p2p_dev_addr", 's', &p2p_mac); + if (r < 0) { + log_debug("no p2p_dev_addr in AP-STA-DISCONNECTED: %s", + wpas_message_get_raw(ev)); + return; + } + + sp = find_peer_by_p2p_mac(s, p2p_mac); + if (!sp) { + log_debug("stray AP-STA-DISCONNECTED: %s", + wpas_message_get_raw(ev)); + return; + } + + log_debug("unbind peer %s from its group", p2p_mac); + supplicant_peer_drop_group(sp); +} + +static void supplicant_event(struct supplicant *s, struct wpas_message *m) +{ + const char *name; + + if (wpas_message_is_event(m, NULL)) { + name = wpas_message_get_name(m); + if (!name) { + log_debug("unnamed wpas-event: %s", + wpas_message_get_raw(m)); + return; + } + + /* ignored events */ + if (!strcmp(name, "CTRL-EVENT-SCAN-STARTED") || + !strcmp(name, "CTRL-EVENT-SCAN-RESULTS") || + !strcmp(name, "CTRL-EVENT-EAP-STARTED") || + !strcmp(name, "CTRL-EVENT-EAP-PROPOSED-METHOD") || + !strcmp(name, "CTRL-EVENT-EAP-FAILURE") || + !strcmp(name, "CTRL-EVENT-BSS-REMOVED") || + !strcmp(name, "CTRL-EVENT-BSS-ADDED") || + !strcmp(name, "CTRL-EVENT-CONNECTED") || + !strcmp(name, "CTRL-EVENT-DISCONNECTED") || + !strcmp(name, "WPS-PBC-ACTIVE") || + !strcmp(name, "WPS-PBC-DISABLE") || + !strcmp(name, "WPS-AP-AVAILABLE-PBC") || + !strcmp(name, "WPS-REG-SUCCESS") || + !strcmp(name, "WPS-SUCCESS") || + !strcmp(name, "WPS-ENROLLEE-SEEN") || + !strcmp(name, "P2P-GROUP-FORMATION-SUCCESS") || + !strcmp(name, "AP-ENABLED") || + !strcmp(name, "SME:") || + !strcmp(name, "WPA:") || + !strcmp(name, "Trying") || + !strcmp(name, "Associated")) + return; + + if (!strcmp(name, "P2P-FIND-STOPPED")) + supplicant_event_p2p_find_stopped(s, m); + else if (!strcmp(name, "P2P-DEVICE-FOUND")) + supplicant_event_p2p_device_found(s, m); + else if (!strcmp(name, "P2P-DEVICE-LOST")) + supplicant_event_p2p_device_lost(s, m); + else if (!strcmp(name, "P2P-PROV-DISC-PBC-REQ")) + supplicant_event_p2p_prov_disc_pbc_req(s, m); + else if (!strcmp(name, "P2P-PROV-DISC-SHOW-PIN")) + supplicant_event_p2p_prov_disc_show_pin(s, m); + else if (!strcmp(name, "P2P-PROV-DISC-ENTER-PIN")) + supplicant_event_p2p_prov_disc_enter_pin(s, m); + else if (!strcmp(name, "P2P-GO-NEG-SUCCESS")) + supplicant_event_p2p_go_neg_success(s, m); + else if (!strcmp(name, "P2P-GROUP-STARTED")) + supplicant_event_p2p_group_started(s, m); + else if (!strcmp(name, "P2P-GROUP-REMOVED")) + supplicant_event_p2p_group_removed(s, m); + else if (!strcmp(name, "AP-STA-CONNECTED")) + supplicant_event_ap_sta_connected(s, m); + else if (!strcmp(name, "AP-STA-DISCONNECTED")) + supplicant_event_ap_sta_disconnected(s, m); + else + log_debug("unhandled wpas-event: %s", + wpas_message_get_raw(m)); + } else { + log_debug("unhandled wpas-message: %s", + wpas_message_get_raw(m)); + } +} + +static void supplicant_try_ready(struct supplicant *s) +{ + struct peer *p; + + if (s->running) + return; + + /* if all setup-commands completed, notify link layer */ + if (s->setup_cnt > 0) + return; + + if (!s->has_p2p) + s->has_wfd = false; + + s->running = true; + link_supplicant_started(s->l); + + LINK_FOREACH_PEER(p, s->l) + peer_supplicant_started(p); +} + +static int supplicant_p2p_set_disallow_freq_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + struct supplicant *s = data; + + /* P2P_SET disallow_freq received */ + --s->setup_cnt; + + if (!wpas_message_is_ok(reply)) + log_warning("cannot set p2p disallow_freq field"); + + supplicant_try_ready(s); + return 0; +} + +static int supplicant_init_p2p_peer_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + _shl_free_ char *next = NULL; + struct supplicant *s = data; + const char *mac; + int r; + + /* + * Using P2P_PEER to get a list of initial peers is racy. If a peer + * exits while we iterate over the list, a "P2P_PEER NEXT-" + * command will fail. Furthermore, if we get a P2P-DEVICE-LOST event + * for a peer before we could add it, we will never handle the event. + * Blame whoever wrote that stupid wpas interface.. + */ + + /* got P2P_PEER response */ + --s->setup_cnt; + + /* FAIL means end-of-list */ + if (!wpas_message_is_fail(reply)) { + r = wpas_message_read(reply, "s", &mac); + if (r < 0) { + log_vERR(r); + goto error; + } + + wpas_message_rewind(reply); + supplicant_parse_peer(s, reply); + + r = wpas_message_new_request(s->bus_global, + "P2P_PEER", + &m); + if (r < 0) { + log_vERR(r); + goto error; + } + + next = shl_strcat("NEXT-", mac); + if (!next) { + r = log_ENOMEM(); + goto error; + } + + r = wpas_message_append(m, "s", next); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_call_async(s->bus_global, + m, + supplicant_init_p2p_peer_fn, + s, + 0, + NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + + /* require next P2P_PEER listing */ + ++s->setup_cnt; + } + + supplicant_try_ready(s); + return 0; + +error: + log_warning("cannot read some initial P2P peers, ignoring"); + supplicant_try_ready(s); + return 0; +} + +static int supplicant_set_wifi_display_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + struct supplicant *s = data; + + /* SET received */ + --s->setup_cnt; + + if (!wpas_message_is_ok(reply)) { + log_warning("cannot enable wpas wifi-display support"); + s->has_wfd = false; + } + + supplicant_try_ready(s); + return 0; +} + +static int supplicant_status_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + struct supplicant *s = data; + const char *p2p_state = NULL, *wifi_display = NULL, *p2p_mac = NULL; + char *t; + int r; + + /* STATUS received */ + --s->setup_cnt; + + wpas_message_dict_read(reply, "p2p_state", 's', &p2p_state); + wpas_message_dict_read(reply, "wifi_display", 's', &wifi_display); + wpas_message_dict_read(reply, "p2p_device_address", 's', &p2p_mac); + + if (!p2p_state) { + log_warning("wpa_supplicant or driver does not support P2P"); + } else if (!strcmp(p2p_state, "DISABLED")) { + log_warning("P2P support disabled on given interface"); + } else { + s->has_p2p = true; + + r = wpas_message_new_request(s->bus_global, + "SET", + &m); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_message_append(m, "ss", + "device_name", + s->l->friendly_name ? : "unknown"); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_call_async(s->bus_global, + m, + NULL, + NULL, + 0, + NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + + /* require P2P_SET disallow_freq response */ + ++s->setup_cnt; + + r = wpas_message_new_request(s->bus_global, + "P2P_SET", + &m); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_message_append(m, "ss", "disallow_freq", "5180-5900"); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_call_async(s->bus_global, + m, + supplicant_p2p_set_disallow_freq_fn, + s, + 0, + NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + + /* require P2P_PEER listing */ + ++s->setup_cnt; + + r = wpas_message_new_request(s->bus_global, + "P2P_PEER", + &m); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_message_append(m, "s", "FIRST"); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_call_async(s->bus_global, + m, + supplicant_init_p2p_peer_fn, + s, + 0, + NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + } + + if (!wifi_display) { + log_warning("wpa_supplicant does not support wifi-display"); + } else if (s->has_p2p) { + s->has_wfd = true; + + /* require SET response */ + ++s->setup_cnt; + + r = wpas_message_new_request(s->bus_global, + "SET", + &m); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_message_append(m, "ss", "wifi_display", "1"); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_call_async(s->bus_global, + m, + supplicant_set_wifi_display_fn, + s, + 0, + NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + } + + if (p2p_mac) { + log_debug("local p2p-address is: %s", p2p_mac); + t = strdup(p2p_mac); + if (!t) { + log_ENOMEM(); + } else { + free(s->p2p_mac); + s->p2p_mac = t; + } + } + + supplicant_try_ready(s); + return 0; + +error: + supplicant_failed(s); + return 0; +} + +static void supplicant_started(struct supplicant *s) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + int r; + + /* clear left-overs from previous runs */ + s->p2p_scanning = false; + + /* require STATUS response */ + ++s->setup_cnt; + + r = wpas_message_new_request(s->bus_global, + "STATUS", + &m); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = wpas_call_async(s->bus_global, + m, + supplicant_status_fn, + s, + 0, + NULL); + if (r < 0) { + log_vERR(r); + goto error; + } + + supplicant_try_ready(s); + return; + +error: + supplicant_failed(s); +} + +static void supplicant_stopped(struct supplicant *s) +{ + struct supplicant_group *g; + struct peer *p; + + while ((p = LINK_FIRST_PEER(s->l))) + supplicant_peer_free(p->sp); + + while (!shl_dlist_empty(&s->groups)) { + g = shl_dlist_first_entry(&s->groups, + struct supplicant_group, + list); + supplicant_group_free(g); + } + + free(s->p2p_mac); + s->p2p_mac = NULL; + + if (s->running) { + s->running = false; + link_supplicant_stopped(s->l); + } +} + +static int supplicant_p2p_find_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + struct supplicant *s = data; + + /* if already scanning, ignore any failures */ + if (s->p2p_scanning) + return 0; + + if (!wpas_message_is_ok(reply)) { + log_warning("P2P_FIND failed"); + return 0; + } + + log_debug("p2p-scanning now active on %s", s->l->ifname); + s->p2p_scanning = true; + link_supplicant_p2p_scan_changed(s->l, true); + + return 0; +} + +int supplicant_set_friendly_name(struct supplicant *s, const char *name) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + int r; + + if (!s->running || !name || !*name) + return log_EINVAL();; + + r = wpas_message_new_request(s->bus_global, + "SET", + &m); + if (r < 0) + return log_ERR(r); + + r = wpas_message_append(m, "ss", "device_name", name); + if (r < 0) + return log_ERR(r); + + r = wpas_call_async(s->bus_global, + m, + NULL, + NULL, + 0, + NULL); + if (r < 0) + return log_ERR(r); + + log_debug("send 'SET device_name %s' to wpas on %s", + name, s->l->ifname); + + return 0; +} + +int supplicant_p2p_start_scan(struct supplicant *s) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + int r; + + if (!s->running || !s->has_p2p) + return log_EINVAL(); + + /* + * This call is asynchronous. You can safely issue multiple of these + * in parallel. You're supposed to track the "p2p_scanning" boolean + * value to see whether scanning really is active or not. + * + * Note that we could make this synchronous, but there's no real gain. + * You still don't get any meaningful errors from wpa_supplicant, so + * there's really no use to it. + */ + + if (s->p2p_scanning) + return 0; + + r = wpas_message_new_request(s->bus_global, + "P2P_FIND", + &m); + if (r < 0) + return log_ERR(r); + + r = wpas_call_async(s->bus_global, + m, + supplicant_p2p_find_fn, + s, + 0, + NULL); + if (r < 0) + return log_ERR(r); + + log_debug("sent P2P_FIND to wpas on %s", s->l->ifname); + + return 0; +} + +void supplicant_p2p_stop_scan(struct supplicant *s) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + int r; + + if (!s->running || !s->has_p2p) + return log_vEINVAL(); + + /* + * Always send the P2P_STOP_FIND message even if we think we're not + * scanning right now. There might be an asynchronous p2p_find pending, + * so abort that by a later p2p_stop_find. + */ + + r = wpas_message_new_request(s->bus_global, + "P2P_STOP_FIND", + &m); + if (r < 0) + return log_vERR(r); + + r = wpas_call_async(s->bus_global, + m, + NULL, + NULL, + 0, + NULL); + if (r < 0) + return log_vERR(r); + + log_debug("sent P2P_STOP_FIND to wpas on %s", s->l->ifname); +} + +bool supplicant_p2p_scanning(struct supplicant *s) +{ + return s && s->running && s->has_p2p && s->p2p_scanning; +} + +/* + * Supplicant Control + * This is the core supplicant-handling, each object manages one external + * wpa_supplicant process doing the hard work for us. We do all that + * asynchronously. + * + * We support rate-controlled restarting of wpas connections, respawning in case + * of errors and other fallback handling. We open one connection to the global + * control interface of wpas, and in case of legacy drivers, a separate + * p2p-dev-* iface connection. + * + * We don't use shared wpas instances across multiple devices. wpas runs only a + * single p2p-supplicant per instance, so we'd be highly limited. Furthermore, + * wpas is synchronous in most operations which is very annoying if you have to + * deal with multiple parallel interfaces. + */ + +int supplicant_new(struct link *l, + struct supplicant **out) +{ + struct supplicant *s; + + if (!l) + return log_EINVAL(); + + log_debug("new supplicant for %s", l->ifname); + + s = calloc(1, sizeof(*s)); + if (!s) + return log_ENOMEM(); + + s->l = l; + s->pid = -1; + shl_dlist_init(&s->groups); + + /* allow 2 restarts in 10s */ + SHL_RATELIMIT_INIT(s->restart_rate, 10 * 1000ULL * 1000ULL, 2); + /* allow 3 execs in 10s */ + SHL_RATELIMIT_INIT(s->exec_rate, 10 * 1000ULL * 1000ULL, 3); + + if (out) + *out = s; + + return 0; +} + +void supplicant_free(struct supplicant *s) +{ + if (!s) + return; + + log_debug("free supplicant of %s", s->l->ifname); + + supplicant_stop(s); + free(s); +} + +static int supplicant_dev_fn(struct wpas *w, + struct wpas_message *m, + void *data) +{ + struct supplicant *s = data; + + if (!m) { + log_error("HUP on supplicant dev-socket of %s", s->l->ifname); + goto error; + } + + supplicant_event(s, m); + return 0; + +error: + supplicant_failed(s); + return 0; +} + +static int supplicant_global_fn(struct wpas *w, + struct wpas_message *m, + void *data) +{ + struct supplicant *s = data; + + if (!m) { + log_error("HUP on supplicant socket of %s", s->l->ifname); + goto error; + } + + /* ignore events on the global-iface, we only listen on dev-iface */ + + return 0; + +error: + supplicant_failed(s); + return 0; +} + +static int supplicant_dev_attach_fn(struct wpas *w, + struct wpas_message *m, + void *data) +{ + struct supplicant *s = data; + + if (!wpas_message_is_ok(m)) { + log_error("cannot attach to dev-wpas interface of %s", + s->l->ifname); + goto error; + } + + supplicant_started(s); + return 0; + +error: + supplicant_failed(s); + return 0; +} + +static int supplicant_global_attach_fn(struct wpas *w, + struct wpas_message *reply, + void *data) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + struct supplicant *s = data; + int r; + + if (!wpas_message_is_ok(reply)) { + log_error("cannot attach to global wpas interface of %s", + s->l->ifname); + goto error; + } + + /* + * Devices with P2P_DEVICE support (instead of direct P2P_GO/CLIENT + * support) are broken with a *lot* of wpa_supplicant versions on the + * global interface. Therefore, try to open the p2p-dev-* interface + * after the global-ATTACH succeded (which means the iface is properly + * set up). If this works, use the p2p-dev-* interface, otherwise, just + * copy the global interface over to bus_dev. + * Event-forwarding is broken on the global-interface in such cases, + * too. So ATTACH again on the other interface and ignore events on the + * global interface in that case. + */ + + r = wpas_open(s->dev_ctrl, &s->bus_dev); + if (r >= 0) { + r = wpas_attach_event(s->bus_dev, s->l->m->event, 0); + if (r < 0) + goto error; + + r = wpas_add_match(s->bus_dev, supplicant_dev_fn, s); + if (r < 0) + goto error; + + r = wpas_message_new_request(s->bus_dev, "ATTACH", &m); + if (r < 0) + goto error; + + r = wpas_call_async(s->bus_dev, + m, + supplicant_dev_attach_fn, + s, + 0, + NULL); + if (r < 0) + goto error; + + return 0; + } + + s->bus_dev = s->bus_global; + wpas_ref(s->bus_dev); + + r = wpas_add_match(s->bus_dev, supplicant_dev_fn, s); + if (r < 0) + goto error; + + supplicant_started(s); + return 0; + +error: + supplicant_failed(s); + return 0; +} + +static int supplicant_open(struct supplicant *s) +{ + _wpas_message_unref_ struct wpas_message *m = NULL; + int r; + + log_debug("open supplicant of %s", s->l->ifname); + + r = wpas_open(s->global_ctrl, &s->bus_global); + if (r < 0) { + if (r != -ENOENT && r != ECONNREFUSED) + log_error("cannot connect to wpas: %d", r); + return r; + } + + r = wpas_attach_event(s->bus_global, s->l->m->event, 0); + if (r < 0) + goto error; + + r = wpas_add_match(s->bus_global, supplicant_global_fn, s); + if (r < 0) + goto error; + + r = wpas_message_new_request(s->bus_global, "ATTACH", &m); + if (r < 0) + goto error; + + r = wpas_call_async(s->bus_global, + m, + supplicant_global_attach_fn, + s, + 0, + NULL); + if (r < 0) + goto error; + + return 0; + +error: + log_error("cannot connect to wpas: %d", r); + wpas_unref(s->bus_global); + s->bus_global = NULL; + return r; +} + +static void supplicant_close(struct supplicant *s) +{ + log_debug("close supplicant of %s", s->l->ifname); + + wpas_remove_match(s->bus_dev, supplicant_dev_fn, s); + wpas_detach_event(s->bus_dev); + wpas_unref(s->bus_dev); + s->bus_dev = NULL; + + wpas_remove_match(s->bus_global, supplicant_global_fn, s); + wpas_detach_event(s->bus_global); + wpas_unref(s->bus_global); + s->bus_global = NULL; +} + +static void supplicant_failed(struct supplicant *s) +{ + uint64_t ms; + int r; + + if (shl_ratelimit_test(&s->restart_rate)) { + ms = 200ULL; + log_error("wpas (pid:%d) failed unexpectedly, relaunching after short grace period..", + (int)s->pid); + } else { + ms = 30 * 1000ULL; + log_error("wpas (pid:%d) failed again.. entering grace period, waiting %llus before relaunching", + (int)s->pid, (unsigned long long)ms / 1000ULL); + } + + ms *= 1000; + ms += shl_now(CLOCK_MONOTONIC); + sd_event_source_set_time(s->timer_source, ms); + sd_event_source_set_enabled(s->timer_source, SD_EVENT_ON); + + /* Always send SIGTERM, even if already dead the child is still not + * reaped so we can still send signals. */ + if (s->pid > 0) { + log_debug("terminating wpas (pid:%d)", (int)s->pid); + r = kill(s->pid, SIGTERM); + if (r < 0) + r = kill(s->pid, SIGKILL); + if (r < 0) + log_warning("cannot kill wpas pid:%d: %m", + (int)s->pid); + } + + s->pid = 0; + sd_event_source_unref(s->child_source); + s->child_source = NULL; + + supplicant_close(s); + supplicant_stopped(s); +} + +static int supplicant_child_fn(sd_event_source *source, + const siginfo_t *si, + void *data) +{ + struct supplicant *s = data; + + supplicant_failed(s); + + return 0; +} + +static void supplicant_run(struct supplicant *s, const char *binary) +{ + char *argv[64], journal_id[128]; + int i, fd_journal; + sigset_t mask; + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout/stderr to journal */ + sprintf(journal_id, "miracle-wifid-%s-%u", + s->l->ifname, s->l->ifindex); + fd_journal = sd_journal_stream_fd(journal_id, LOG_INFO, false); + if (fd_journal >= 0) { + /* dup journal-fd to stdout and stderr */ + dup2(fd_journal, 1); + dup2(fd_journal, 2); + } else { + /* no journal? redirect stdout to parent's stderr */ + dup2(2, 1); + } + + /* initialize wpa_supplicant args */ + i = 0; + argv[i++] = (char*)binary; + + /* debugging? */ + if (arg_wpa_loglevel >= LOG_DEBUG) + argv[i++] = "-dd"; + else if (arg_wpa_loglevel >= LOG_INFO) + argv[i++] = "-d"; + else if (arg_wpa_loglevel < LOG_ERROR) + argv[i++] = "-qq"; + else if (arg_wpa_loglevel < LOG_NOTICE) + argv[i++] = "-q"; + + argv[i++] = "-c"; + argv[i++] = s->conf_path; + argv[i++] = "-C"; + argv[i++] = (char*)"/run/miracle/wifi"; + argv[i++] = "-i"; + argv[i++] = s->l->ifname; + argv[i++] = "-g"; + argv[i++] = s->global_ctrl; + argv[i] = NULL; + + /* execute wpa_supplicant; if it fails, the caller issues exit(1) */ + execve(argv[0], argv, environ); +} + +static int supplicant_spawn(struct supplicant *s) +{ + _shl_free_ char *binary = NULL; + pid_t pid; + int r; + + if (!s) + return log_EINVAL(); + if (s->pid > 0) + return 0; + + log_debug("spawn supplicant of %s", s->l->ifname); + + binary = shl_strcat(arg_wpa_bindir, "/wpa_supplicant"); + if (!binary) + return log_ENOMEM(); + + if (access(binary, X_OK) < 0) { + log_error("execution of wpas (%s) not possible: %m", binary); + return -EINVAL; + } + + pid = fork(); + if (pid < 0) { + return log_ERRNO(); + } else if (!pid) { + supplicant_run(s, binary); + exit(1); + } + + s->pid = pid; + s->open_cnt = 0; + log_info("wpas spawned as pid:%d", (int)pid); + + sd_event_source_unref(s->child_source); + s->child_source = NULL; + + r = sd_event_add_child(s->l->m->event, + &s->child_source, + s->pid, + WEXITED, + supplicant_child_fn, + s); + if (r < 0) + return log_ERR(r); + + return 0; +} + +static int supplicant_timer_fn(sd_event_source *source, + uint64_t usec, + void *data) +{ + struct supplicant *s = data; + uint64_t ms; + int r; + + if (!s->pid) { + r = supplicant_spawn(s); + if (r < 0) { + /* We cannot *spawn* wpas, we might have hit a + * system-update or similar. Try again in a short + * timespan, but if that fails to often, we stop trying + * for a longer time. Note that the binary has been + * around during startup, otherwise, supplicant_start() + * would have failed. Therefore, we know that there at + * least used to be an executable so it's fine to + * retry.*/ + if (shl_ratelimit_test(&s->exec_rate)) { + ms = 1000ULL; /* 1000ms */ + log_error("cannot execute wpas, retrying after short grace period.."); + } else { + ms = 60 * 1000ULL; /* 60s */ + log_error("still cannot execute wpas.. entering grace period, waiting %llus before retrying", + (unsigned long long)ms / 1000ULL); + } + + ms *= 1000ULL; + ms += shl_now(CLOCK_MONOTONIC); + sd_event_source_set_time(source, ms); + sd_event_source_set_enabled(source, SD_EVENT_ON); + } else { + ms = shl_now(CLOCK_MONOTONIC); + ms += 200 * 1000ULL; /* 200ms startup timer */ + sd_event_source_set_time(source, ms); + sd_event_source_set_enabled(source, SD_EVENT_ON); + } + } else if (s->pid > 0 && !s->running) { + r = supplicant_open(s); + if (r < 0) { + /* Cannot connect to supplicant, retry in 200ms + * but increase the timeout for each attempt so + * we lower the rate in case sth goes wrong. */ + s->open_cnt = shl_min(s->open_cnt + 1, (uint64_t)1000); + ms = s->open_cnt * 200 * 1000ULL; + ms += shl_now(CLOCK_MONOTONIC); + sd_event_source_set_time(source, ms); + sd_event_source_set_enabled(source, SD_EVENT_ON); + if (s->open_cnt == 5) + log_warning("still cannot connect to wpas after 5 retries"); + } else { + /* wpas is running smoothly, disable timer */ + sd_event_source_set_enabled(source, SD_EVENT_OFF); + } + } else { + /* Who armed this timer? What timer is this? */ + sd_event_source_set_enabled(source, SD_EVENT_OFF); + } + + return 0; +} + +static int supplicant_write_config(struct supplicant *s) +{ + _shl_free_ char *path = NULL; + FILE *f; + int r; + + r = asprintf(&path, "/run/miracle/wifi/%s-%u.conf", + s->l->ifname, s->l->ifindex); + if (r < 0) + return log_ENOMEM(); + + f = fopen(path, "we"); + if (!f) + return log_ERRNO(); + + r = fprintf(f, + "# Generated configuration - DO NOT EDIT!\n" + "device_name=%s\n" + "device_type=%s\n" + "config_methods=%s\n" + "driver_param=%s\n" + "ap_scan=%s\n" + "# End of configuration\n", + s->l->friendly_name ? : "unknown", + "1-0050F204-1", + "pbc", + "p2p_device=1", + "1"); + if (r < 0) { + r = log_ERRNO(); + fclose(f); + return r; + } + + fclose(f); + free(s->conf_path); + s->conf_path = path; + path = NULL; + + return 0; +} + +int supplicant_start(struct supplicant *s) +{ + int r; + + if (!s) + return log_EINVAL(); + if (s->pid >= 0) + return 0; + + log_debug("start supplicant of %s", s->l->ifname); + + SHL_RATELIMIT_RESET(s->restart_rate); + SHL_RATELIMIT_RESET(s->exec_rate); + + r = asprintf(&s->global_ctrl, "/run/miracle/wifi/%s-%u.global", + s->l->ifname, s->l->ifindex); + if (r < 0) { + r = log_ENOMEM(); + goto error; + } + + r = asprintf(&s->dev_ctrl, "/run/miracle/wifi/p2p-dev-%s", + s->l->ifname); + if (r < 0) { + r = log_ENOMEM(); + goto error; + } + + r = supplicant_write_config(s); + if (r < 0) + goto error; + + /* add initial 200ms startup timer */ + r = sd_event_add_monotonic(s->l->m->event, + &s->timer_source, + shl_now(CLOCK_MONOTONIC) + 200 * 1000ULL, + 0, + supplicant_timer_fn, + s); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = supplicant_spawn(s); + if (r < 0) + goto error; + + return 0; + +error: + supplicant_stop(s); + return r; +} + +void supplicant_stop(struct supplicant *s) +{ + int r; + + if (!s) + return log_vEINVAL(); + + log_debug("stop supplicant of %s", s->l->ifname); + + supplicant_close(s); + + sd_event_source_unref(s->child_source); + s->child_source = NULL; + sd_event_source_unref(s->timer_source); + s->timer_source = NULL; + + if (s->pid > 0) { + r = kill(s->pid, SIGTERM); + if (r < 0) + r = kill(s->pid, SIGKILL); + if (r < 0) + log_warning("cannot kill wpas pid:%d: %m", + (int)s->pid); + } + + if (s->conf_path) { + unlink(s->conf_path); + free(s->conf_path); + s->conf_path = NULL; + } + + free(s->global_ctrl); + s->global_ctrl = NULL; + free(s->dev_ctrl); + s->dev_ctrl = NULL; + + s->pid = -1; + supplicant_stopped(s); +} + +bool supplicant_is_running(struct supplicant *s) +{ + if (!s) { + log_vEINVAL(); + return false; + } + + /* pid > 0 means supplicant-process is known, pid == 0 means we are + * currently in a grace period to restart it. pid < 0 means we are + * not managing any wpas instance. */ + + return s->pid >= 0; +} + +bool supplicant_is_ready(struct supplicant *s) +{ + if (!s) { + log_vEINVAL(); + return false; + } + + return s->running; +} diff --git a/src/wifi/wifid.c b/src/wifi/wifid.c new file mode 100644 index 0000000..ea6079d --- /dev/null +++ b/src/wifi/wifid.c @@ -0,0 +1,544 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shl_htable.h" +#include "shl_macro.h" +#include "shl_log.h" +#include "shl_util.h" +#include "util.h" +#include "wifid.h" + +const char *arg_wpa_bindir = "/usr/bin"; +unsigned int arg_wpa_loglevel = LOG_NOTICE; + +/* + * Manager Handling + */ + +struct link *manager_find_link(struct manager *m, unsigned int ifindex) +{ + unsigned int *elem; + bool res; + + res = shl_htable_lookup_uint(&m->links, ifindex, &elem); + if (!res) + return NULL; + + return link_from_htable(elem); +} + +struct link *manager_find_link_by_label(struct manager *m, const char *label) +{ + const char *next; + unsigned int idx; + int r; + + r = shl_atoi_u(label, 10, &next, &idx); + if (r < 0 || *next) + return NULL; + + return manager_find_link(m, idx); +} + +static void manager_add_udev_link(struct manager *m, + struct udev_device *d) +{ + struct link *l; + unsigned int ifindex; + const char *ifname; + int r; + + ifindex = ifindex_from_udev_device(d); + if (!ifindex) + return; + + ifname = udev_device_get_property_value(d, "INTERFACE"); + if (!ifname) + return; + + /* ignore dynamic p2p interfaces */ + if (shl_startswith(ifname, "p2p-")) + return; + + r = link_new(m, ifindex, ifname, &l); + if (r < 0) + return; + + link_set_friendly_name(l, m->friendly_name); + + if (udev_device_has_tag(d, "miracle")) + link_set_managed(l, true); +} + +static int manager_udev_fn(sd_event_source *source, + int fd, + uint32_t mask, + void *data) +{ + _cleanup_udev_device_ struct udev_device *d = NULL; + struct manager *m = data; + const char *action, *ifname; + unsigned int ifindex; + struct link *l; + + d = udev_monitor_receive_device(m->udev_mon); + if (!d) + return 0; + + ifindex = ifindex_from_udev_device(d); + if (!ifindex) + return 0; + + l = manager_find_link(m, ifindex); + + action = udev_device_get_action(d); + if (action && !strcmp(action, "remove")) { + if (l) + link_free(l); + } else if (l) { + if (action && !strcmp(action, "move")) { + ifname = udev_device_get_property_value(d, "INTERFACE"); + if (ifname) + link_renamed(l, ifname); + } + + if (udev_device_has_tag(d, "miracle")) + link_set_managed(l, true); + else + link_set_managed(l, false); + } else { + manager_add_udev_link(m, d); + } + + return 0; +} + +static int manager_signal_fn(sd_event_source *source, + const struct signalfd_siginfo *ssi, + void *data) +{ + struct manager *m = data; + + if (ssi->ssi_signo == SIGCHLD) { + log_debug("caught SIGCHLD for %ld, reaping child", (long)ssi->ssi_pid); + waitid(P_PID, ssi->ssi_pid, NULL, WNOHANG|WEXITED); + return 0; + } else if (ssi->ssi_signo == SIGPIPE) { + /* ignore SIGPIPE */ + return 0; + } + + log_notice("caught signal %d, exiting..", (int)ssi->ssi_signo); + sd_event_exit(m->event, 0); + + return 0; +} + +static void manager_free(struct manager *m) +{ + unsigned int i; + struct link *l; + + if (!m) + return; + + while ((l = MANAGER_FIRST_LINK(m))) + link_free(l); + + manager_dbus_disconnect(m); + + shl_htable_clear_uint(&m->links, NULL, NULL); + + sd_event_source_unref(m->udev_mon_source); + udev_monitor_unref(m->udev_mon); + udev_unref(m->udev); + + for (i = 0; m->sigs[i]; ++i) + sd_event_source_unref(m->sigs[i]); + + sd_bus_unref(m->bus); + sd_event_unref(m->event); + + free(m->friendly_name); + free(m); +} + +static int manager_new(struct manager **out) +{ + struct manager *m; + static const int sigs[] = { + SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0 + }; + unsigned int i; + sigset_t mask; + int r; + + m = calloc(1, sizeof(*m)); + if (!m) + return log_ENOMEM(); + + shl_htable_init_uint(&m->links); + + r = sd_event_default(&m->event); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_event_set_watchdog(m->event, true); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_bus_default_system(&m->bus); + if (r < 0) { + log_error("cannot connect to system bus: %d", r); + goto error; + } + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) { + log_vERR(r); + goto error; + } + + for (i = 0; sigs[i]; ++i) { + sigemptyset(&mask); + sigaddset(&mask, sigs[i]); + sigprocmask(SIG_BLOCK, &mask, NULL); + + r = sd_event_add_signal(m->event, + &m->sigs[i], + sigs[i], + manager_signal_fn, + m); + if (r < 0) { + log_vERR(r); + goto error; + } + + /* low-priority to allow others to handle it first */ + sd_event_source_set_priority(m->sigs[i], 100); + } + + m->udev = udev_new(); + if (!m->udev) { + r = log_ENOMEM(); + goto error; + } + + m->udev_mon = udev_monitor_new_from_netlink(m->udev, "udev"); + if (!m->udev_mon) { + r = log_ENOMEM(); + goto error; + } + + r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_mon, + "net", + "wlan"); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = udev_monitor_enable_receiving(m->udev_mon); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_event_add_io(m->event, + &m->udev_mon_source, + udev_monitor_get_fd(m->udev_mon), + EPOLLHUP | EPOLLERR | EPOLLIN, + manager_udev_fn, + m); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = manager_dbus_connect(m); + if (r < 0) + goto error; + + if (out) + *out = m; + + return 0; + +error: + manager_free(m); + return r; +} + +static void manager_read_name(struct manager *m) +{ + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_sd_bus_message_ sd_bus_message *rep = NULL; + const char *name; + char *str; + int r; + + r = sd_bus_call_method(m->bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.DBus.Properties", + "Get", + &err, + &rep, + "ss", "org.freedesktop.hostname1", "Hostname"); + if (r < 0) + goto error; + + r = sd_bus_message_enter_container(rep, 'v', "s"); + if (r < 0) + goto error; + + r = sd_bus_message_read(rep, "s", &name); + if (r < 0) + goto error; + + if (shl_isempty(name)) { + log_warning("no hostname set on systemd.hostname1, using: %s", + m->friendly_name); + return; + } + + str = strdup(name); + if (!str) + return log_vENOMEM(); + + free(m->friendly_name); + m->friendly_name = str; + log_debug("friendly-name from local hostname: %s", str); + + return; + +error: + log_warning("cannot read hostname from systemd.hostname1: %s", + bus_error_message(&err, r)); +} + +static void manager_read_links(struct manager *m) +{ + _cleanup_udev_enumerate_ struct udev_enumerate *e = NULL; + struct udev_list_entry *l; + struct udev_device *d; + int r; + + e = udev_enumerate_new(m->udev); + if (!e) + goto error; + + r = udev_enumerate_add_match_subsystem(e, "net"); + if (r < 0) + goto error; + + r = udev_enumerate_add_match_property(e, "DEVTYPE", "wlan"); + if (r < 0) + goto error; + + r = udev_enumerate_add_match_is_initialized(e); + if (r < 0) + goto error; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + goto error; + + udev_list_entry_foreach(l, udev_enumerate_get_list_entry(e)) { + d = udev_device_new_from_syspath(m->udev, + udev_list_entry_get_name(l)); + if (!d) + goto error; + + manager_add_udev_link(m, d); + udev_device_unref(d); + } + + return; + +error: + log_warning("cannot enumerate links via udev"); +} + +static int manager_startup(struct manager *m) +{ + int r; + + r = shl_mkdir_p_prefix("/run", "/run/miracle", 0755); + if (r >= 0) + r = shl_mkdir_p_prefix("/run/miracle", + "/run/miracle/wifi", + 0700); + if (r < 0) { + log_error("cannot create maintenance directories in /run: %d", + r); + return r; + } + + manager_read_name(m); + manager_read_links(m); + + return 0; +} + +static int manager_run(struct manager *m) +{ + return sd_event_loop(m->event); +} + +static int help(void) +{ + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ + printf("%s [OPTIONS...] ...\n\n" + "Wifi Management Daemon.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-time Prefix log-messages with timestamp\n" + "\n" + " --wpa-bindir wpa_supplicant binary dir [/usr/bin]\n" + " --wpa-loglevel wpa_supplicant log-level\n" + , program_invocation_short_name); + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ + + return 0; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + ARG_LOG_TIME, + + ARG_WPA_BINDIR, + ARG_WPA_LOGLEVEL, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + + { "wpa-bindir", required_argument, NULL, ARG_WPA_BINDIR }, + { "wpa-loglevel", required_argument, NULL, ARG_WPA_LOGLEVEL }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + return help(); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + log_max_sev = atoi(optarg); + break; + case ARG_LOG_TIME: + log_init_time(); + break; + + case ARG_WPA_BINDIR: + arg_wpa_bindir = optarg; + break; + case ARG_WPA_LOGLEVEL: + arg_wpa_loglevel = atoi(optarg); + break; + case '?': + return -EINVAL; + } + } + + if (optind < argc) { + log_error("unparsed remaining arguments starting with: %s", + argv[optind]); + return -EINVAL; + } + + log_format(LOG_DEFAULT_BASE, NULL, LOG_INFO, + "miracle-wifid - revision %s %s %s", + "some-rev-TODO-xyz", __DATE__, __TIME__); + + return 1; +} + +int main(int argc, char **argv) +{ + struct manager *m = NULL; + int r; + + srand(time(NULL)); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = manager_new(&m); + if (r < 0) + goto finish; + + r = manager_startup(m); + if (r < 0) + goto finish; + + r = sd_notify(false, "READY=1\n" + "STATUS=Running.."); + if (r < 0) { + log_vERR(r); + goto finish; + } + + r = manager_run(m); + +finish: + sd_notify(false, "STATUS=Exiting.."); + manager_free(m); + + log_debug("exiting.."); + return abs(r); +} diff --git a/src/wifi/wifid.h b/src/wifi/wifid.h new file mode 100644 index 0000000..aa87818 --- /dev/null +++ b/src/wifi/wifid.h @@ -0,0 +1,194 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "shl_dlist.h" +#include "shl_htable.h" + +#ifndef WIFID_H +#define WIFID_H + +struct manager; +struct link; +struct peer; +struct supplicant; +struct supplicant_peer; + +/* supplicant */ + +int supplicant_new(struct link *l, + struct supplicant **out); +void supplicant_free(struct supplicant *s); + +int supplicant_start(struct supplicant *s); +void supplicant_stop(struct supplicant *s); +bool supplicant_is_running(struct supplicant *s); +bool supplicant_is_ready(struct supplicant *s); + +int supplicant_set_friendly_name(struct supplicant *s, const char *name); +int supplicant_p2p_start_scan(struct supplicant *s); +void supplicant_p2p_stop_scan(struct supplicant *s); +bool supplicant_p2p_scanning(struct supplicant *s); + +/* supplicant peer */ + +const char *supplicant_peer_get_friendly_name(struct supplicant_peer *sp); +const char *supplicant_peer_get_interface(struct supplicant_peer *sp); +const char *supplicant_peer_get_local_address(struct supplicant_peer *sp); +const char *supplicant_peer_get_remote_address(struct supplicant_peer *sp); +int supplicant_peer_connect(struct supplicant_peer *sp, + const char *prov_type, + const char *pin); +void supplicant_peer_disconnect(struct supplicant_peer *sp); + +/* peer */ + +struct peer { + struct link *l; + char *p2p_mac; + struct supplicant_peer *sp; + + bool public : 1; + bool connected : 1; +}; + +#define peer_from_htable(_p) \ + shl_htable_offsetof((_p), struct peer, p2p_mac) + +int peer_new(struct link *l, + const char *p2p_mac, + struct peer **out); +void peer_free(struct peer *p); + +const char *peer_get_friendly_name(struct peer *p); +const char *peer_get_interface(struct peer *p); +const char *peer_get_local_address(struct peer *p); +const char *peer_get_remote_address(struct peer *p); +int peer_connect(struct peer *p, const char *prov, const char *pin); +void peer_disconnect(struct peer *p); + +int peer_allow(struct peer *p); +void peer_reject(struct peer *p); + +void peer_supplicant_started(struct peer *p); +void peer_supplicant_stopped(struct peer *p); +void peer_supplicant_friendly_name_changed(struct peer *p); +void peer_supplicant_provision_discovery(struct peer *p, + const char *prov, + const char *pin); +void peer_supplicant_connected_changed(struct peer *p, bool connected); + +_shl_sentinel_ +void peer_dbus_properties_changed(struct peer *p, const char *prop, ...); +void peer_dbus_provision_discovery(struct peer *p, + const char *prov, + const char *pin); +void peer_dbus_added(struct peer *p); +void peer_dbus_removed(struct peer *p); + +/* link */ + +struct link { + struct manager *m; + unsigned int ifindex; + struct supplicant *s; + + char *ifname; + char *friendly_name; + + size_t peer_cnt; + struct shl_htable peers; + + bool managed : 1; + bool public : 1; +}; + +#define link_from_htable(_l) \ + shl_htable_offsetof((_l), struct link, ifindex) +#define LINK_FIRST_PEER(_l) \ + SHL_HTABLE_FIRST_MACRO(&(_l)->peers, peer_from_htable) +#define LINK_FOREACH_PEER(_i, _l) \ + SHL_HTABLE_FOREACH_MACRO(_i, &(_l)->peers, peer_from_htable) + +struct peer *link_find_peer(struct link *l, const char *p2p_mac); +struct peer *link_find_peer_by_label(struct link *l, const char *label); + +int link_new(struct manager *m, + unsigned int ifindex, + const char *ifname, + struct link **out); +void link_free(struct link *l); + +void link_set_managed(struct link *l, bool set); +int link_renamed(struct link *l, const char *ifname); + +int link_set_friendly_name(struct link *l, const char *name); +const char *link_get_friendly_name(struct link *l); +int link_set_p2p_scanning(struct link *l, bool set); +bool link_get_p2p_scanning(struct link *l); + +void link_supplicant_started(struct link *l); +void link_supplicant_stopped(struct link *l); +void link_supplicant_p2p_scan_changed(struct link *l, bool new_value); + +_shl_sentinel_ +void link_dbus_properties_changed(struct link *l, const char *prop, ...); +void link_dbus_added(struct link *l); +void link_dbus_removed(struct link *l); + +/* manager */ + +struct manager { + sd_event *event; + sd_bus *bus; + sd_event_source *sigs[_NSIG]; + struct udev *udev; + struct udev_monitor *udev_mon; + sd_event_source *udev_mon_source; + + char *friendly_name; + + size_t link_cnt; + struct shl_htable links; +}; + +#define MANAGER_FIRST_LINK(_m) \ + SHL_HTABLE_FIRST_MACRO(&(_m)->links, link_from_htable) +#define MANAGER_FOREACH_LINK(_i, _m) \ + SHL_HTABLE_FOREACH_MACRO(_i, &(_m)->links, link_from_htable) + +struct link *manager_find_link(struct manager *m, unsigned int ifindex); +struct link *manager_find_link_by_label(struct manager *m, const char *label); + +/* dbus */ + +int manager_dbus_connect(struct manager *m); +void manager_dbus_disconnect(struct manager *m); + +/* cli arguments */ + +extern const char *arg_wpa_bindir; +extern unsigned int arg_wpa_loglevel; + +#endif /* WIFID_H */