/* * 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 #include "shl_dlist.h" #include "shl_log.h" #include "shl_util.h" #include "util.h" #include "wifid.h" #include "wpas.h" #include "config.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 *wfd_subelements; 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; }; /* Device Password ID */ enum wps_dev_password_id { DEV_PW_DEFAULT = 0x0000, DEV_PW_USER_SPECIFIED = 0x0001, DEV_PW_MACHINE_SPECIFIED = 0x0002, DEV_PW_REKEY = 0x0003, DEV_PW_PUSHBUTTON = 0x0004, DEV_PW_REGISTRAR_SPECIFIED = 0x0005, DEV_PW_NFC_CONNECTION_HANDOVER = 0x0007 }; 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) || (p->sp->sta_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; char mac[MAC_STRLEN]; 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; reformat_mac(mac, t); sp = find_peer_by_any_mac(g->s, mac); 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*) "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; if (execvpe(argv[0], argv, environ)< 0) { log_error("dhcp failed (%d): %m", errno); } _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*) "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; if (execvpe(argv[0], argv, environ) < 0) { log_error("dhcp failed (%d): %m", errno); } _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 == 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; if (sp->s->pending == sp) { sp->s->pending = NULL; peer_supplicant_formation_failure(sp->p, "lost"); } 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->wfd_subelements); 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; } const char *supplicant_peer_get_wfd_subelements(struct supplicant_peer *sp) { if (!sp) return NULL; return sp->wfd_subelements; } 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 (sp->s->pending && sp->s->pending != sp) return log_ERR(-EALREADY); 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, "s", "pbc"); 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, "pin"); if (r < 0) return log_ERR(r); } else if (!strcmp(prov_type, "keypad")) { 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); sp->s->pending = sp; 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, *val; 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; } /* P2P-PEER reports the device name as 'device_name', P2P-DEVICE-FOUND * uses 'name. Allow either here.. */ r = wpas_message_dict_read(m, "device_name", 's', &name); if (r < 0) r = wpas_message_dict_read(m, "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)); } r = wpas_message_dict_read(m, "wfd_subelems", 's', &val); if (r >= 0) { t = strdup(val); if (!t) { log_vENOMEM(); } else { free(sp->wfd_subelements); sp->wfd_subelements = t; peer_supplicant_wfd_subelements_changed(sp->p); } } else { /* TODO: wfd_dev_info only contains the dev-info sub-elem, * while wfd_sublemens contains all. Fix that! The user has no * chance to distinguish both. * We currently use it only as boolen (set/unset) but once we * parse it we _definitely_ have to provide proper data. */ r = wpas_message_dict_read(m, "wfd_dev_info", 's', &val); if (r >= 0) { t = strdup(val); if (!t) { log_vENOMEM(); } else { free(sp->wfd_subelements); sp->wfd_subelements = t; peer_supplicant_wfd_subelements_changed(sp->p); } } } 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. */ 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; } supplicant_parse_peer(s, ev); 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; peer_supplicant_provision_discovery(sp->p, sp->prov, sp->pin); } static void supplicant_event_p2p_go_neg_request(struct supplicant *s, struct wpas_message *ev) { struct supplicant_peer *sp; const char *mac; int r; r = wpas_message_read(ev, "s", &mac); if (r < 0) { log_debug("no p2p-mac in P2P-GO-NEG-REQUEST information: %s", wpas_message_get_raw(ev)); return; } sp = find_peer_by_p2p_mac(s, mac); if (!sp) { log_debug("stale P2P-GO-NEG-REQUEST: %s", wpas_message_get_raw(ev)); return; } /* * prov should be set by previous * P2P-PROV-DISC-PBC-REQ * P2P-PROV-DISC-SHOW-PIN * or P2P-PROV-DISC-ENTER-PIN * if not set pbc mode */ if (!sp->prov) { sp->prov = strdup("pbc"); free(sp->pin); sp->pin = NULL; } if (!sp->g) { log_debug("GO Negotiation Request from %s", mac); peer_supplicant_go_neg_request(sp->p, sp->prov, sp->pin); } else { log_debug("GO Negotiation Request from 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; peer_supplicant_provision_discovery(sp->p, sp->prov, sp->pin); } 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; peer_supplicant_provision_discovery(sp->p, sp->prov, sp->pin); } 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, *ssid, *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_dict_read(ev, "ssid", 's', &ssid); if (r == 0) { log_debug("ssid: %s", ssid); } 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_p2p_group_formation_failure(struct supplicant *s, struct wpas_message *ev) { struct peer *p; /* There is no useful information in this event at all. Why would * anyone want to know to which group formation (or even peer?) this * event belongs to? No, we have to track all that ourselves, sigh.. */ if (s->pending) { log_debug("peer %s connection failed", s->pending->friendly_name); p = s->pending->p; s->pending = NULL; peer_supplicant_formation_failure(p, "unknown"); } } 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; } if (sp->s->pending == sp) { sp->s->pending = NULL; if (sp->p->connected) peer_supplicant_connected_changed(sp->p, false); else peer_supplicant_formation_failure(sp->p, "disconnected"); } 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-AP-AVAILABLE-AUTH") || !strcmp(name, "WPS-AP-AVAILABLE-PIN") || !strcmp(name, "CTRL-EVENT-EAP-STATUS") || !strcmp(name, "CTRL-EVENT-EAP-METHOD") || !strcmp(name, "CTRL-EVENT-EAP-STATUS") || !strcmp(name, "WPS-CRED-RECEIVED") || !strcmp(name, "WPS-AP-AVAILABLE") || !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, "No network configuration found for the current AP") || !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-GO-NEG-REQUEST")) supplicant_event_p2p_go_neg_request(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, "P2P-GROUP-FORMATION-FAILURE")) supplicant_event_p2p_group_formation_failure(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) { _wpas_message_unref_ struct wpas_message *m = NULL; struct supplicant *s = data; int r; /* SET received */ --s->setup_cnt; if (!wpas_message_is_ok(reply)) { log_warning("cannot enable wpas wifi-display support"); s->has_wfd = false; } if (s->has_wfd) { /* update subelements */ r = wpas_message_new_request(s->bus_global, "WFD_SUBELEM_SET", &m); if (r < 0) { log_vERR(r); goto error; } r = wpas_message_append(m, "s", "0"); if (r < 0) { log_vERR(r); goto error; } if (!shl_isempty(s->l->wfd_subelements)) { r = wpas_message_append(m, "s", s->l->wfd_subelements); 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; } } supplicant_try_ready(s); return 0; error: supplicant_failed(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 ? : "Miracle"); 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_vENOMEM(); } 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_set_wfd_subelements(struct supplicant *s, const char *val) { _wpas_message_unref_ struct wpas_message *m = NULL; int r; if (!s->running || !val) return log_EINVAL(); r = wpas_message_new_request(s->bus_global, "WFD_SUBELEM_SET", &m); if (r < 0) return log_ERR(r); r = wpas_message_append(m, "s", "0"); if (r < 0) return log_ERR(r); if (!shl_isempty(val)) { r = wpas_message_append(m, "s", val); 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 'WFD_SUBELEM_SET 0 %s' to wpas on %s", val, 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(); s->pending = NULL; /* * 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. Moreover, wpas' state tracking is quite * unreliable so we can never know whether we're really still scanning. * Therefore, we send the P2P_FIND on _each_ start_scan() request. It's * the callers responsibility to send it in proper intervals or after * they issues other wpas calls. Yes, this is ugly but currently the * only way to make this work reliably. */ 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 */ if(link_is_using_dev(s->l) && wpas_message_get_ifname(m)) { supplicant_event(s, m); } 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_find(char **binary) { _shl_free_ char *path = getenv("PATH"); if(!path) { return -EINVAL; } path = strdup(path); if(!path) { return log_ENOMEM(); } struct stat bin_stat; char *curr = path, *next; while(1) { curr = strtok_r(curr, ":", &next); if(!curr) { break; } _shl_free_ char *bin = shl_strcat(curr, "/wpa_supplicant"); if (!bin) return log_ENOMEM(); if(stat(bin, &bin_stat) < 0) { if(ENOENT == errno || ENOTDIR == errno) { goto end; } return log_ERRNO(); } if (!access(bin, X_OK)) { *binary = strdup(bin); return 0; } end: curr = NULL; } return -EINVAL; } 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); if (supplicant_find(&binary) < 0) { log_error("execution of wpas (%s) not possible: %m", binary); return -EINVAL; } log_info("wpa_supplicant found: %s", binary); 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", //"pbc keypad pin display", "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_time(s->l->m->event, &s->timer_source, CLOCK_MONOTONIC, 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; }