mirror of
https://github.com/albfan/miraclecast.git
synced 2025-03-09 23:38:56 +00:00
miracled: add wifi-GO support
If we run as GO we need to support local AP events and dhcp-server handling. Both was already supported by our code-base so simply hook it up with miracled-wifi. Note that this adds a severe restriction: we can only have a single p2p-connection per interface. The wpa_supplicant events don't provide enough information to associate the ifnames to devices. But most drivers only support a single STA, anyway, so that's fine. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
This commit is contained in:
parent
f12a042306
commit
533959633e
1 changed files with 232 additions and 6 deletions
|
@ -23,6 +23,31 @@
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wifi Interaction
|
||||||
|
* As it turns out, no network-manager currently provides any Wifi-P2P APIs,
|
||||||
|
* therefore, we have no chance but do it ourselves. This file implements a
|
||||||
|
* simple wifi-P2P API by directly talking to a running wpa_supplicant. The
|
||||||
|
* supplicant may be managed by an external network-manager or by ourselves.
|
||||||
|
*
|
||||||
|
* Note that we do *not* intend to keep this interface. Network-managers really
|
||||||
|
* ought to provide something like that via DBus. Once they do, we will ditch
|
||||||
|
* this abstraction and instead use the given API.
|
||||||
|
*
|
||||||
|
* But no-one seems to be interested in that, and more importantly, no-one seems
|
||||||
|
* to be working on that. Hence, we try to do our best here and implement it
|
||||||
|
* ourselves.
|
||||||
|
* The current design is to connect to wpa_supplicant on the default per-ifname
|
||||||
|
* interface and then manage all P2P related stuff. We run "p2p_find" to start a
|
||||||
|
* peer-discovery on request and advertice each found peer via the event
|
||||||
|
* interface. On provision-request or on explicit connect-request, we start a
|
||||||
|
* P2P connection to the peer. Note that we actively prevent multiple such
|
||||||
|
* connections at the same time. The wpa_supplicant API is not able to assign
|
||||||
|
* local groups to p2p-addresses, so we cannot support more than one GO on a
|
||||||
|
* single interface. But that's usually fine as most drivers only support a
|
||||||
|
* single AP, anyway.
|
||||||
|
*/
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "wifi"
|
#define LOG_SUBSYSTEM "wifi"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
@ -54,6 +79,8 @@ struct wifi {
|
||||||
struct wfd_wpa_ctrl *wpa;
|
struct wfd_wpa_ctrl *wpa;
|
||||||
sd_event_source *wpa_source;
|
sd_event_source *wpa_source;
|
||||||
struct shl_dlist devs;
|
struct shl_dlist devs;
|
||||||
|
struct wifi_dev *connection;
|
||||||
|
struct wifi_dev *pending;
|
||||||
|
|
||||||
bool discoverable : 1;
|
bool discoverable : 1;
|
||||||
bool hup : 1;
|
bool hup : 1;
|
||||||
|
@ -67,6 +94,7 @@ struct wifi_dev {
|
||||||
struct wifi *w;
|
struct wifi *w;
|
||||||
|
|
||||||
char mac[WFD_WPA_EVENT_MAC_STRLEN];
|
char mac[WFD_WPA_EVENT_MAC_STRLEN];
|
||||||
|
char iface[WFD_WPA_EVENT_MAC_STRLEN];
|
||||||
char pin[WIFI_PIN_STRLEN + 1];
|
char pin[WIFI_PIN_STRLEN + 1];
|
||||||
unsigned int provision;
|
unsigned int provision;
|
||||||
|
|
||||||
|
@ -104,7 +132,7 @@ static struct wifi_dev *wifi_find_dev_by_mac(struct wifi *w, const char *mac)
|
||||||
|
|
||||||
shl_dlist_for_each(i, &w->devs) {
|
shl_dlist_for_each(i, &w->devs) {
|
||||||
d = shl_dlist_entry(i, struct wifi_dev, list);
|
d = shl_dlist_entry(i, struct wifi_dev, list);
|
||||||
if (!strcasecmp(d->mac, mac))
|
if (!strcasecmp(d->mac, mac) || !strcasecmp(d->iface, mac))
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,6 +526,9 @@ static void wifi_event_p2p_go_neg_success(struct wifi *w, char *msg,
|
||||||
|
|
||||||
log_debug("received P2P-GO-NEG-SUCCESS: %u:%s",
|
log_debug("received P2P-GO-NEG-SUCCESS: %u:%s",
|
||||||
ev->p.p2p_go_neg_success.role, d->mac);
|
ev->p.p2p_go_neg_success.role, d->mac);
|
||||||
|
|
||||||
|
strcpy(d->iface, ev->p.p2p_go_neg_success.peer_iface);
|
||||||
|
w->pending = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wifi_event_p2p_group_started(struct wifi *w, char *msg,
|
static void wifi_event_p2p_group_started(struct wifi *w, char *msg,
|
||||||
|
@ -507,10 +538,20 @@ static void wifi_event_p2p_group_started(struct wifi *w, char *msg,
|
||||||
|
|
||||||
d = wifi_find_dev_by_mac(w, ev->p.p2p_group_started.go_mac);
|
d = wifi_find_dev_by_mac(w, ev->p.p2p_group_started.go_mac);
|
||||||
if (!d) {
|
if (!d) {
|
||||||
|
/* If we cannot find the GO, it's probably because *we* are the
|
||||||
|
* GO. Unfortunately, wpa_supplicant doesn't provide the peer
|
||||||
|
* this group is for so just check whether we have a pending
|
||||||
|
* local GO and use it.
|
||||||
|
* Yes, this whole w->pending thing is a hack, but what can
|
||||||
|
* we do.. */
|
||||||
|
if (w->connection || !w->pending) {
|
||||||
log_debug("stray P2P-GROUP-STARTED event: %s", msg);
|
log_debug("stray P2P-GROUP-STARTED event: %s", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d = w->pending;
|
||||||
|
}
|
||||||
|
|
||||||
log_debug("received P2P-GROUP-STARTED: %s:%u:%s",
|
log_debug("received P2P-GROUP-STARTED: %s:%u:%s",
|
||||||
ev->p.p2p_group_started.ifname,
|
ev->p.p2p_group_started.ifname,
|
||||||
ev->p.p2p_group_started.role, d->mac);
|
ev->p.p2p_group_started.role, d->mac);
|
||||||
|
@ -525,6 +566,7 @@ static void wifi_event_p2p_group_started(struct wifi *w, char *msg,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w->pending = NULL;
|
||||||
wifi_dev_start(d, ev->p.p2p_group_started.ifname,
|
wifi_dev_start(d, ev->p.p2p_group_started.ifname,
|
||||||
ev->p.p2p_group_started.role);
|
ev->p.p2p_group_started.role);
|
||||||
}
|
}
|
||||||
|
@ -551,6 +593,46 @@ static void wifi_event_p2p_group_removed(struct wifi *w, char *msg,
|
||||||
wifi_dev_stop(d);
|
wifi_dev_stop(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wifi_event_ap_sta_connected(struct wifi *w, char *msg,
|
||||||
|
struct wfd_wpa_event *ev)
|
||||||
|
{
|
||||||
|
struct wifi_dev *d;
|
||||||
|
|
||||||
|
d = wifi_find_dev_by_mac(w, ev->p.ap_sta_connected.iface);
|
||||||
|
if (!d) {
|
||||||
|
d = wifi_find_dev_by_mac(w, ev->p.ap_sta_connected.mac);
|
||||||
|
if (!d) {
|
||||||
|
log_debug("stray AP-STA-CONNECTED event: %s", msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("received AP-STA-CONNECTED: %s %s",
|
||||||
|
ev->p.ap_sta_connected.iface,
|
||||||
|
ev->p.ap_sta_connected.mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wifi_event_ap_sta_disconnected(struct wifi *w, char *msg,
|
||||||
|
struct wfd_wpa_event *ev)
|
||||||
|
{
|
||||||
|
struct wifi_dev *d;
|
||||||
|
|
||||||
|
d = wifi_find_dev_by_mac(w, ev->p.ap_sta_disconnected.iface);
|
||||||
|
if (!d) {
|
||||||
|
d = wifi_find_dev_by_mac(w, ev->p.ap_sta_disconnected.mac);
|
||||||
|
if (!d) {
|
||||||
|
log_debug("stray AP-STA-DISCONNECTED event: %s", msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("received AP-STA-DISCONNECTED: %s %s",
|
||||||
|
ev->p.ap_sta_disconnected.iface,
|
||||||
|
ev->p.ap_sta_disconnected.mac);
|
||||||
|
|
||||||
|
wifi_dev_stop(d);
|
||||||
|
}
|
||||||
|
|
||||||
static void wifi_event_ctrl_event_terminating(struct wifi *w, char *msg,
|
static void wifi_event_ctrl_event_terminating(struct wifi *w, char *msg,
|
||||||
struct wfd_wpa_event *ev)
|
struct wfd_wpa_event *ev)
|
||||||
{
|
{
|
||||||
|
@ -600,6 +682,12 @@ static void wifi_wpa_event_fn(struct wfd_wpa_ctrl *ctrl, void *data,
|
||||||
case WFD_WPA_EVENT_P2P_GROUP_REMOVED:
|
case WFD_WPA_EVENT_P2P_GROUP_REMOVED:
|
||||||
wifi_event_p2p_group_removed(w, buf, &ev);
|
wifi_event_p2p_group_removed(w, buf, &ev);
|
||||||
break;
|
break;
|
||||||
|
case WFD_WPA_EVENT_AP_STA_CONNECTED:
|
||||||
|
wifi_event_ap_sta_connected(w, buf, &ev);
|
||||||
|
break;
|
||||||
|
case WFD_WPA_EVENT_AP_STA_DISCONNECTED:
|
||||||
|
wifi_event_ap_sta_disconnected(w, buf, &ev);
|
||||||
|
break;
|
||||||
case WFD_WPA_EVENT_CTRL_EVENT_SCAN_STARTED:
|
case WFD_WPA_EVENT_CTRL_EVENT_SCAN_STARTED:
|
||||||
/* ignore */
|
/* ignore */
|
||||||
break;
|
break;
|
||||||
|
@ -942,11 +1030,64 @@ static int wifi_dev_spawn_dhcp_client(struct wifi_dev *d)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int wifi_dev_spawn_dhcp_server(struct wifi_dev *d, unsigned int subnet)
|
||||||
|
{
|
||||||
|
char *argv[64], loglevel[64], commfd[64], prefix[64];
|
||||||
|
int i, r, fds[2];
|
||||||
|
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 to 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++] = d->ifname;
|
||||||
|
argv[i++] = "--comm-fd";
|
||||||
|
argv[i++] = commfd;
|
||||||
|
argv[i] = NULL;
|
||||||
|
|
||||||
|
execve(argv[0], argv, environ);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fds[1]);
|
||||||
|
d->dhcp_comm = fds[0];
|
||||||
|
d->dhcp_pid = pid;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int wifi_dev_comm_fn(sd_event_source *source, int fd, uint32_t mask,
|
static int wifi_dev_comm_fn(sd_event_source *source, int fd, uint32_t mask,
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
struct wifi_dev *d = data;
|
struct wifi_dev *d = data;
|
||||||
char buf[512], *t;
|
char buf[512], *t, *ip;
|
||||||
ssize_t l;
|
ssize_t l;
|
||||||
|
|
||||||
l = recv(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT);
|
l = recv(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT);
|
||||||
|
@ -986,13 +1127,40 @@ static int wifi_dev_comm_fn(sd_event_source *source, int fd, uint32_t mask,
|
||||||
free(d->remote_addr);
|
free(d->remote_addr);
|
||||||
d->remote_addr = t;
|
d->remote_addr = t;
|
||||||
break;
|
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;
|
||||||
|
if (strcasecmp(t, d->mac) && strcasecmp(t, d->iface)) {
|
||||||
|
log_debug("ignore 'R' line for unknown mac");
|
||||||
|
free(t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = strdup(ip);
|
||||||
|
if (!ip) {
|
||||||
|
log_vENOMEM();
|
||||||
|
free(t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(t);
|
||||||
|
|
||||||
|
free(d->remote_addr);
|
||||||
|
d->remote_addr = ip;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
free(t);
|
free(t);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d->local_addr && d->remote_addr) {
|
if (d->local_addr && d->remote_addr) {
|
||||||
/* got DHCP lease, connection is established */
|
/* got/sent DHCP lease, connection is established */
|
||||||
wifi_dev_set_connected(d, true, true);
|
wifi_dev_set_connected(d, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,15 +1192,25 @@ static int wifi_dev_start(struct wifi_dev *d, const char *ifname,
|
||||||
return 0;
|
return 0;
|
||||||
if (!ifname || role >= WFD_WPA_EVENT_ROLE_CNT)
|
if (!ifname || role >= WFD_WPA_EVENT_ROLE_CNT)
|
||||||
return log_EINVAL();
|
return log_EINVAL();
|
||||||
|
if (d->w->connection)
|
||||||
|
return -EALREADY;
|
||||||
|
|
||||||
d->ifname = strdup(ifname);
|
d->ifname = strdup(ifname);
|
||||||
if (!d->ifname)
|
if (!d->ifname)
|
||||||
return log_ENOMEM();
|
return log_ENOMEM();
|
||||||
|
|
||||||
d->role = role;
|
d->role = role;
|
||||||
|
d->w->connection = d;
|
||||||
|
d->w->pending = NULL;
|
||||||
|
|
||||||
switch (d->role) {
|
switch (d->role) {
|
||||||
case WFD_WPA_EVENT_ROLE_GO:
|
case WFD_WPA_EVENT_ROLE_GO:
|
||||||
|
r = wifi_dev_spawn_dhcp_server(d, 77);
|
||||||
|
if (r < 0) {
|
||||||
|
log_error("cannot spawn DHCP server for: %s:%s",
|
||||||
|
ifname, d->mac);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case WFD_WPA_EVENT_ROLE_CLIENT:
|
case WFD_WPA_EVENT_ROLE_CLIENT:
|
||||||
r = wifi_dev_spawn_dhcp_client(d);
|
r = wifi_dev_spawn_dhcp_client(d);
|
||||||
|
@ -1119,6 +1297,9 @@ static void wifi_dev_stop(struct wifi_dev *d)
|
||||||
free(d->ifname);
|
free(d->ifname);
|
||||||
d->ifname = NULL;
|
d->ifname = NULL;
|
||||||
d->role = WFD_WPA_EVENT_ROLE_CNT;
|
d->role = WFD_WPA_EVENT_ROLE_CNT;
|
||||||
|
if (d->w->connection == d)
|
||||||
|
d->w->connection = NULL;
|
||||||
|
d->w->pending = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wifi_dev_lost(struct wifi_dev *d)
|
static void wifi_dev_lost(struct wifi_dev *d)
|
||||||
|
@ -1130,6 +1311,8 @@ static void wifi_dev_lost(struct wifi_dev *d)
|
||||||
|
|
||||||
wifi_dev_stop(d);
|
wifi_dev_stop(d);
|
||||||
shl_dlist_unlink(&d->list);
|
shl_dlist_unlink(&d->list);
|
||||||
|
if (d->w->pending == d)
|
||||||
|
d->w->pending = NULL;
|
||||||
d->w = NULL;
|
d->w = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1188,6 +1371,8 @@ void wifi_dev_allow(struct wifi_dev *d, const char *pin)
|
||||||
return;
|
return;
|
||||||
if (d->provision == WIFI_PROVISION_CNT)
|
if (d->provision == WIFI_PROVISION_CNT)
|
||||||
return;
|
return;
|
||||||
|
if (d->w->connection)
|
||||||
|
return;
|
||||||
|
|
||||||
r = 0;
|
r = 0;
|
||||||
switch (d->provision) {
|
switch (d->provision) {
|
||||||
|
@ -1238,12 +1423,53 @@ void wifi_dev_reject(struct wifi_dev *d)
|
||||||
int wifi_dev_connect(struct wifi_dev *d, unsigned int provision,
|
int wifi_dev_connect(struct wifi_dev *d, unsigned int provision,
|
||||||
const char *pin)
|
const char *pin)
|
||||||
{
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
if (!wifi_dev_is_available(d))
|
if (!wifi_dev_is_available(d))
|
||||||
return log_EINVAL();
|
return log_EINVAL();
|
||||||
if (wifi_dev_is_running(d))
|
if (wifi_dev_is_running(d))
|
||||||
return 0;
|
return 0;
|
||||||
|
if (d->w->connection)
|
||||||
|
return -EALREADY;
|
||||||
|
|
||||||
return -EINVAL;
|
r = 0;
|
||||||
|
switch (d->provision) {
|
||||||
|
case WIFI_PROVISION_CNT:
|
||||||
|
/* fallback */
|
||||||
|
case WIFI_PROVISION_PBC:
|
||||||
|
r = wifi_requestf_ok(d->w,
|
||||||
|
"P2P_CONNECT %s pbc display",
|
||||||
|
d->mac);
|
||||||
|
break;
|
||||||
|
case WIFI_PROVISION_DISPLAY:
|
||||||
|
if (!pin || !*pin) {
|
||||||
|
r = log_EINVAL();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = wifi_requestf_ok(d->w,
|
||||||
|
"P2P_CONNECT %s %s display",
|
||||||
|
d->mac, d->pin);
|
||||||
|
break;
|
||||||
|
case WIFI_PROVISION_PIN:
|
||||||
|
if (!pin || !*pin) {
|
||||||
|
r = log_EINVAL();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = wifi_requestf_ok(d->w,
|
||||||
|
"P2P_CONNECT %s %s keypad",
|
||||||
|
d->mac, pin);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r < 0)
|
||||||
|
log_warning("cannot issue P2P_CONNECT on dev_connect(): %d", r);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifi_dev_disconnect(struct wifi_dev *d)
|
void wifi_dev_disconnect(struct wifi_dev *d)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue