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 */