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. * 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)