1
0
Fork 0
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:
David Herrmann 2014-02-17 12:23:04 +01:00
parent f12a042306
commit 533959633e

View file

@ -23,6 +23,31 @@
* 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"
#include <errno.h>
@ -54,6 +79,8 @@ struct wifi {
struct wfd_wpa_ctrl *wpa;
sd_event_source *wpa_source;
struct shl_dlist devs;
struct wifi_dev *connection;
struct wifi_dev *pending;
bool discoverable : 1;
bool hup : 1;
@ -67,6 +94,7 @@ struct wifi_dev {
struct wifi *w;
char mac[WFD_WPA_EVENT_MAC_STRLEN];
char iface[WFD_WPA_EVENT_MAC_STRLEN];
char pin[WIFI_PIN_STRLEN + 1];
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) {
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;
}
@ -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",
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,
@ -507,8 +538,18 @@ 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);
if (!d) {
log_debug("stray P2P-GROUP-STARTED event: %s", msg);
return;
/* 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);
return;
}
d = w->pending;
}
log_debug("received P2P-GROUP-STARTED: %s:%u:%s",
@ -525,6 +566,7 @@ static void wifi_event_p2p_group_started(struct wifi *w, char *msg,
return;
}
w->pending = NULL;
wifi_dev_start(d, ev->p.p2p_group_started.ifname,
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);
}
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,
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:
wifi_event_p2p_group_removed(w, buf, &ev);
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:
/* ignore */
break;
@ -942,11 +1030,64 @@ static int wifi_dev_spawn_dhcp_client(struct wifi_dev *d)
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,
void *data)
{
struct wifi_dev *d = data;
char buf[512], *t;
char buf[512], *t, *ip;
ssize_t l;
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);
d->remote_addr = 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;
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:
free(t);
break;
}
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);
}
@ -1024,15 +1192,25 @@ static int wifi_dev_start(struct wifi_dev *d, const char *ifname,
return 0;
if (!ifname || role >= WFD_WPA_EVENT_ROLE_CNT)
return log_EINVAL();
if (d->w->connection)
return -EALREADY;
d->ifname = strdup(ifname);
if (!d->ifname)
return log_ENOMEM();
d->role = role;
d->w->connection = d;
d->w->pending = NULL;
switch (d->role) {
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;
case WFD_WPA_EVENT_ROLE_CLIENT:
r = wifi_dev_spawn_dhcp_client(d);
@ -1119,6 +1297,9 @@ static void wifi_dev_stop(struct wifi_dev *d)
free(d->ifname);
d->ifname = NULL;
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)
@ -1130,6 +1311,8 @@ static void wifi_dev_lost(struct wifi_dev *d)
wifi_dev_stop(d);
shl_dlist_unlink(&d->list);
if (d->w->pending == d)
d->w->pending = NULL;
d->w = NULL;
}
@ -1188,6 +1371,8 @@ void wifi_dev_allow(struct wifi_dev *d, const char *pin)
return;
if (d->provision == WIFI_PROVISION_CNT)
return;
if (d->w->connection)
return;
r = 0;
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,
const char *pin)
{
int r;
if (!wifi_dev_is_available(d))
return log_EINVAL();
if (wifi_dev_is_running(d))
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)