Parcourir la source

WPS: Add initial part of External Registrar functionality

This is the first step in adding support for using wpa_supplicant as a
WPS External Registrar to manage APs over UPnP. Only the device
discovery part is implemented in this commit.
Jouni Malinen il y a 15 ans
Parent
commit
e9bcfebfce

+ 4 - 0
src/wps/wps.h

@@ -32,6 +32,7 @@ enum wsc_op_code {
 
 struct wps_registrar;
 struct upnp_wps_device_sm;
+struct wps_er;
 
 /**
  * struct wps_credential - WPS Credential
@@ -606,4 +607,7 @@ int wps_process_oob(struct wps_context *wps, struct oob_device_data *oob_dev,
 		    int registrar);
 int wps_attr_text(struct wpabuf *data, char *buf, char *end);
 
+struct wps_er * wps_er_init(struct wps_context *wps, const char *ifname);
+void wps_er_deinit(struct wps_er *er);
+
 #endif /* WPS_H */

+ 370 - 0
src/wps/wps_er.c

@@ -0,0 +1,370 @@
+/*
+ * Wi-Fi Protected Setup - External Registrar
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "uuid.h"
+#include "eloop.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+
+/* TODO:
+ * SSDP M-SEARCH multicast TX for WFA
+ * create AP entry
+ * fetch wps_device info based on LOCATION: from SSDP NOTIFY
+ * parse wps_device info into AP entry (name, SCPD/control/eventSub URLs, etc.
+ * subscribe to events
+ * send notification of new AP device with wpa_msg
+ * re-send notifications with wpa_msg if ER re-started (to update wpa_gui-qt4)
+ * (also re-send SSDP M-SEARCH in this case to find new APs)
+ * parse UPnP event messages
+ */
+
+static void wps_er_ap_timeout(void *eloop_data, void *user_ctx);
+
+
+struct wps_er_ap {
+	struct wps_er_ap *next;
+	struct in_addr addr;
+	char *location;
+
+};
+
+struct wps_er {
+	struct wps_registrar *reg;
+	char ifname[17];
+	char *mac_addr_text; /* mac addr of network i.f. we use */
+	u8 mac_addr[ETH_ALEN]; /* mac addr of network i.f. we use */
+	char *ip_addr_text; /* IP address of network i.f. we use */
+	unsigned ip_addr; /* IP address of network i.f. we use (host order) */
+	int multicast_sd;
+	int ssdp_sd;
+	struct wps_er_ap *ap;
+};
+
+
+static void wps_er_pin_needed_cb(void *ctx, const u8 *uuid_e,
+				 const struct wps_device_data *dev)
+{
+	wpa_printf(MSG_DEBUG, "WPS ER: PIN needed");
+}
+
+
+static struct wps_er_ap * wps_er_ap_get(struct wps_er *er,
+					struct in_addr *addr)
+{
+	struct wps_er_ap *ap;
+	for (ap = er->ap; ap; ap = ap->next) {
+		if (ap->addr.s_addr == addr->s_addr)
+			break;
+	}
+	return ap;
+}
+
+
+static void wps_er_ap_free(struct wps_er *er, struct wps_er_ap *ap)
+{
+	wpa_printf(MSG_DEBUG, "WPS ER: Removing AP entry for %s (%s)",
+		   inet_ntoa(ap->addr), ap->location);
+	eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
+	os_free(ap->location);
+	os_free(ap);
+}
+
+
+static void wps_er_ap_timeout(void *eloop_data, void *user_ctx)
+{
+	struct wps_er *er = eloop_data;
+	struct wps_er_ap *ap = user_ctx;
+	wpa_printf(MSG_DEBUG, "WPS ER: AP advertisement timed out");
+	wps_er_ap_free(er, ap);
+}
+
+
+static void wps_er_ap_add(struct wps_er *er, struct in_addr *addr,
+			  const char *location, int max_age)
+{
+	struct wps_er_ap *ap;
+
+	ap = wps_er_ap_get(er, addr);
+	if (ap) {
+		/* Update advertisement timeout */
+		eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
+		eloop_register_timeout(max_age, 0, wps_er_ap_timeout, er, ap);
+		return;
+	}
+
+	ap = os_zalloc(sizeof(*ap));
+	if (ap == NULL)
+		return;
+	ap->location = os_strdup(location);
+	if (ap->location == NULL) {
+		os_free(ap);
+		return;
+	}
+	ap->next = er->ap;
+	er->ap = ap;
+
+	ap->addr.s_addr = addr->s_addr;
+	eloop_register_timeout(max_age, 0, wps_er_ap_timeout, er, ap);
+
+	wpa_printf(MSG_DEBUG, "WPS ER: Added AP entry for %s (%s)",
+		   inet_ntoa(ap->addr), ap->location);
+
+	/* TODO: get device data and subscribe for events */
+}
+
+
+static void wps_er_ap_remove(struct wps_er *er, struct in_addr *addr)
+{
+	struct wps_er_ap *prev = NULL, *ap = er->ap;
+
+	while (ap) {
+		if (ap->addr.s_addr == addr->s_addr) {
+			if (prev)
+				prev->next = ap->next;
+			else
+				er->ap = ap->next;
+			wps_er_ap_free(er, ap);
+			return;
+		}
+		prev = ap;
+		ap = ap->next;
+	}
+}
+
+
+static void wps_er_ap_remove_all(struct wps_er *er)
+{
+	struct wps_er_ap *prev, *ap;
+
+	ap = er->ap;
+	er->ap = NULL;
+
+	while (ap) {
+		prev = ap;
+		ap = ap->next;
+		wps_er_ap_free(er, prev);
+	}
+}
+
+
+static void wps_er_ssdp_rx(int sd, void *eloop_ctx, void *sock_ctx)
+{
+	struct wps_er *er = eloop_ctx;
+	struct sockaddr_in addr; /* client address */
+	socklen_t addr_len;
+	int nread;
+	char buf[MULTICAST_MAX_READ], *pos, *pos2, *start;
+	int wfa = 0, byebye = 0;
+	int max_age = -1;
+	char *location = NULL;
+
+	addr_len = sizeof(addr);
+	nread = recvfrom(sd, buf, sizeof(buf) - 1, 0,
+			 (struct sockaddr *) &addr, &addr_len);
+	if (nread <= 0)
+		return;
+	buf[nread] = '\0';
+
+	wpa_printf(MSG_DEBUG, "WPS ER: Received SSDP from %s",
+		   inet_ntoa(addr.sin_addr));
+	wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Received SSDP contents",
+			  (u8 *) buf, nread);
+
+	if (sd == er->multicast_sd) {
+		/* Reply to M-SEARCH */
+		if (os_strncmp(buf, "HTTP/1.1 200 OK", 15) != 0)
+			return; /* unexpected response header */
+	} else {
+		/* Unsolicited message (likely NOTIFY or M-SEARCH) */
+		if (os_strncmp(buf, "NOTIFY ", 7) != 0)
+			return; /* only process notifications */
+	}
+
+	for (start = buf; start && *start; start = pos) {
+		pos = os_strchr(start, '\n');
+		if (pos) {
+			if (pos[-1] == '\r')
+				pos[-1] = '\0';
+			*pos++ = '\0';
+		}
+		if (os_strstr(start, "schemas-wifialliance-org:device:"
+			      "WFADevice:1"))
+			wfa = 1;
+		if (os_strstr(start, "schemas-wifialliance-org:service:"
+			      "WFAWLANConfig:1"))
+			wfa = 1;
+		if (os_strncasecmp(start, "LOCATION:", 9) == 0) {
+			start += 9;
+			while (*start == ' ')
+				start++;
+			location = start;
+		} else if (os_strncasecmp(start, "NTS:", 4) == 0) {
+			if (os_strstr(start, "ssdp:byebye"))
+				byebye = 1;
+		} else if (os_strncasecmp(start, "CACHE-CONTROL:", 14) == 0) {
+			start += 9;
+			while (*start == ' ')
+				start++;
+			pos2 = os_strstr(start, "max-age=");
+			if (pos2 == NULL)
+				continue;
+			pos2 += 8;
+			max_age = atoi(pos2);
+		}
+	}
+
+	if (!wfa)
+		return; /* Not WPS advertisement/reply */
+
+	if (byebye) {
+		wps_er_ap_remove(er, &addr.sin_addr);
+		return;
+	}
+
+	if (!location)
+		return; /* Unknown location */
+
+	if (max_age < 1)
+		return; /* No max-age reported */
+
+	wpa_printf(MSG_DEBUG, "WPS ER: AP discovered: %s "
+		   "(packet source: %s  max-age: %d)",
+		   location, inet_ntoa(addr.sin_addr), max_age);
+
+	wps_er_ap_add(er, &addr.sin_addr, location, max_age);
+}
+
+
+static void wps_er_send_ssdp_msearch(struct wps_er *er)
+{
+	struct wpabuf *msg;
+	struct sockaddr_in dest;
+
+	msg = wpabuf_alloc(500);
+	if (msg == NULL)
+		return;
+
+	wpabuf_put_str(msg,
+		       "M-SEARCH * HTTP/1.1\r\n"
+		       "HOST: 239.255.255.250:1900\r\n"
+		       "MAN: \"ssdp:discover\"\r\n"
+		       "MX: 3\r\n"
+		       "ST: urn:schemas-wifialliance-org:device:WFADevice:1"
+		       "\r\n"
+		       "\r\n");
+
+	os_memset(&dest, 0, sizeof(dest));
+	dest.sin_family = AF_INET;
+	dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+	dest.sin_port = htons(UPNP_MULTICAST_PORT);
+
+	if (sendto(er->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
+		   (struct sockaddr *) &dest, sizeof(dest)) < 0)
+		wpa_printf(MSG_DEBUG, "WPS ER: M-SEARCH sendto failed: "
+			   "%d (%s)", errno, strerror(errno));
+
+	wpabuf_free(msg);
+}
+
+
+struct wps_er *
+wps_er_init(struct wps_context *wps, const char *ifname)
+{
+	struct wps_er *er;
+	struct wps_registrar_config rcfg;
+
+	er = os_zalloc(sizeof(*er));
+	if (er == NULL)
+		return NULL;
+
+	er->multicast_sd = -1;
+	er->ssdp_sd = -1;
+
+	os_strlcpy(er->ifname, ifname, sizeof(er->ifname));
+	os_memset(&rcfg, 0, sizeof(rcfg));
+	rcfg.pin_needed_cb = wps_er_pin_needed_cb;
+	rcfg.cb_ctx = er;
+
+	er->reg = wps_registrar_init(wps, &rcfg);
+	if (er->reg == NULL) {
+		wps_er_deinit(er);
+		return NULL;
+	}
+
+	if (get_netif_info(ifname,
+			   &er->ip_addr, &er->ip_addr_text,
+			   er->mac_addr, &er->mac_addr_text)) {
+		wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
+			   "for %s. Does it have IP address?", ifname);
+		wps_er_deinit(er);
+		return NULL;
+	}
+
+	if (add_ssdp_network(ifname)) {
+		wps_er_deinit(er);
+		return NULL;
+	}
+
+	er->multicast_sd = ssdp_open_multicast_sock(er->ip_addr);
+	if (er->multicast_sd < 0) {
+		wps_er_deinit(er);
+		return NULL;
+	}
+
+	er->ssdp_sd = ssdp_listener_open();
+	if (er->ssdp_sd < 0) {
+		wps_er_deinit(er);
+		return NULL;
+	}
+	if (eloop_register_sock(er->multicast_sd, EVENT_TYPE_READ,
+				wps_er_ssdp_rx, er, NULL) ||
+	    eloop_register_sock(er->ssdp_sd, EVENT_TYPE_READ,
+				wps_er_ssdp_rx, er, NULL)) {
+		wps_er_deinit(er);
+		return NULL;
+	}
+
+	wpa_printf(MSG_DEBUG, "WPS ER: Start (ifname=%s ip_addr=%s "
+		   "mac_addr=%s)",
+		   er->ifname, er->ip_addr_text, er->mac_addr_text);
+
+	wps_er_send_ssdp_msearch(er);
+
+	return er;
+}
+
+
+void wps_er_deinit(struct wps_er *er)
+{
+	if (er == NULL)
+		return;
+	wps_er_ap_remove_all(er);
+	if (er->multicast_sd >= 0) {
+		eloop_unregister_sock(er->multicast_sd, EVENT_TYPE_READ);
+		close(er->multicast_sd);
+	}
+	if (er->ssdp_sd >= 0) {
+		eloop_unregister_sock(er->ssdp_sd, EVENT_TYPE_READ);
+		close(er->ssdp_sd);
+	}
+	wps_registrar_deinit(er->reg);
+	os_free(er->ip_addr_text);
+	os_free(er->mac_addr_text);
+	os_free(er);
+}

+ 3 - 4
src/wps/wps_upnp.c

@@ -885,9 +885,8 @@ static int eth_get(const char *device, u8 ea[ETH_ALEN])
  * @mac_addr_text: Buffer for returning allocated MAC address text
  * Returns: 0 on success, -1 on failure
  */
-static int get_netif_info(const char *net_if, unsigned *ip_addr,
-			  char **ip_addr_text, u8 mac[ETH_ALEN],
-			  char **mac_addr_text)
+int get_netif_info(const char *net_if, unsigned *ip_addr, char **ip_addr_text,
+		   u8 mac[ETH_ALEN], char **mac_addr_text)
 {
 	struct ifreq req;
 	int sock = -1;
@@ -930,7 +929,7 @@ static int get_netif_info(const char *net_if, unsigned *ip_addr,
 #else
 #error MAC address fetch not implemented
 #endif
-	os_snprintf(*mac_addr_text, 18, MACSTR, MAC2STR(req.ifr_addr.sa_data));
+	os_snprintf(*mac_addr_text, 18, MACSTR, MAC2STR(mac));
 
 	close(sock);
 	return 0;

+ 7 - 1
src/wps/wps_upnp_i.h

@@ -25,6 +25,8 @@
 #define UPNP_WPS_DEVICE_CONTROL_FILE "wps_control"
 #define UPNP_WPS_DEVICE_EVENT_FILE "wps_event"
 
+#define MULTICAST_MAX_READ 1600 /* max bytes we'll read for UPD request */
+
 
 struct web_connection;
 struct subscription;
@@ -168,6 +170,8 @@ void subscription_destroy(struct subscription *s);
 struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
 					const u8 uuid[UUID_LEN]);
 int send_wpabuf(int fd, struct wpabuf *buf);
+int get_netif_info(const char *net_if, unsigned *ip_addr, char **ip_addr_text,
+		   u8 mac[ETH_ALEN], char **mac_addr_text);
 
 /* wps_upnp_ssdp.c */
 void msearchreply_state_machine_stop(struct advertisement_state_machine *a);
@@ -175,7 +179,9 @@ int advertisement_state_machine_start(struct upnp_wps_device_sm *sm);
 void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm);
 void ssdp_listener_stop(struct upnp_wps_device_sm *sm);
 int ssdp_listener_start(struct upnp_wps_device_sm *sm);
-int add_ssdp_network(char *net_if);
+int ssdp_listener_open(void);
+int add_ssdp_network(const char *net_if);
+int ssdp_open_multicast_sock(u32 ip_addr);
 int ssdp_open_multicast(struct upnp_wps_device_sm *sm);
 
 /* wps_upnp_web.c */

+ 48 - 26
src/wps/wps_upnp_ssdp.c

@@ -24,7 +24,6 @@
 #define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */
 #define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */
 #define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */
-#define MULTICAST_MAX_READ 1600 /* max bytes we'll read for UPD request */
 #define MAX_MSEARCH 20          /* max simultaneous M-SEARCH replies ongoing */
 #define SSDP_TARGET  "239.0.0.0"
 #define SSDP_NETMASK "255.0.0.0"
@@ -657,7 +656,7 @@ bad:
  * ssdp_listener_stop - Stop SSDP listered
  * @sm: WPS UPnP state machine from upnp_wps_device_init()
  *
- * This function stops the SSDP listerner that was started by calling
+ * This function stops the SSDP listener that was started by calling
  * ssdp_listener_start().
  */
 void ssdp_listener_stop(struct upnp_wps_device_sm *sm)
@@ -719,23 +718,16 @@ static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
 }
 
 
-/**
- * ssdp_listener_start - Set up for receiving discovery (UDP) packets
- * @sm: WPS UPnP state machine from upnp_wps_device_init()
- * Returns: 0 on success, -1 on failure
- *
- * The SSDP listerner is stopped by calling ssdp_listener_stop().
- */
-int ssdp_listener_start(struct upnp_wps_device_sm *sm)
+int ssdp_listener_open(void)
 {
-	int sd = -1;
 	struct sockaddr_in addr;
 	struct ip_mreq mcast_addr;
 	int on = 1;
 	/* per UPnP spec, keep IP packet time to live (TTL) small */
 	unsigned char ttl = 4;
+	int sd;
 
-	sm->ssdp_sd = sd = socket(AF_INET, SOCK_DGRAM, 0);
+	sd = socket(AF_INET, SOCK_DGRAM, 0);
 	if (sd < 0)
 		goto fail;
 	if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
@@ -757,8 +749,29 @@ int ssdp_listener_start(struct upnp_wps_device_sm *sm)
 	if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
 		       &ttl, sizeof(ttl)))
 		goto fail;
-	if (eloop_register_sock(sd, EVENT_TYPE_READ, ssdp_listener_handler,
-				NULL, sm))
+
+	return sd;
+
+fail:
+	if (sd >= 0)
+		close(sd);
+	return -1;
+}
+
+
+/**
+ * ssdp_listener_start - Set up for receiving discovery (UDP) packets
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ *
+ * The SSDP listener is stopped by calling ssdp_listener_stop().
+ */
+int ssdp_listener_start(struct upnp_wps_device_sm *sm)
+{
+	sm->ssdp_sd = ssdp_listener_open();
+
+	if (eloop_register_sock(sm->ssdp_sd, EVENT_TYPE_READ,
+				ssdp_listener_handler, NULL, sm))
 		goto fail;
 	sm->ssdp_sd_registered = 1;
 	return 0;
@@ -782,7 +795,7 @@ fail:
  * once after booting up, but it does not hurt to call this more frequently
  * "to be safe".
  */
-int add_ssdp_network(char *net_if)
+int add_ssdp_network(const char *net_if)
 {
 #ifdef __linux__
 	int ret = -1;
@@ -798,7 +811,7 @@ int add_ssdp_network(char *net_if)
 	if (sock < 0)
 		goto fail;
 
-	rt.rt_dev = net_if;
+	rt.rt_dev = (char *) net_if;
 	sin = aliasing_hide_typecast(&rt.rt_dst, struct sockaddr_in);
 	sin->sin_family = AF_INET;
 	sin->sin_port = 0;
@@ -833,19 +846,14 @@ fail:
 }
 
 
-/**
- * ssdp_open_multicast - Open socket for sending multicast SSDP messages
- * @sm: WPS UPnP state machine from upnp_wps_device_init()
- * Returns: 0 on success, -1 on failure
- */
-int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
+int ssdp_open_multicast_sock(u32 ip_addr)
 {
-	int sd = -1;
+	int sd;
 	 /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet
 	  * time to live (TTL) small */
 	unsigned char ttl = 4;
 
-	sm->multicast_sd = sd = socket(AF_INET, SOCK_DGRAM, 0);
+	sd = socket(AF_INET, SOCK_DGRAM, 0);
 	if (sd < 0)
 		return -1;
 
@@ -855,7 +863,7 @@ int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
 #endif
 
 	if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,
-		       &sm->ip_addr, sizeof(sm->ip_addr)))
+		       &ip_addr, sizeof(ip_addr)))
 		return -1;
 	if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
 		       &ttl, sizeof(ttl)))
@@ -865,7 +873,7 @@ int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
 	{
 		struct ip_mreq mreq;
 		mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
-		mreq.imr_interface.s_addr = sm->ip_addr;
+		mreq.imr_interface.s_addr = ip_addr;
 		wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr "
 			   "0x%x",
 			   mreq.imr_multiaddr.s_addr,
@@ -886,5 +894,19 @@ int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
 	 * which aids debugging I suppose but isn't really necessary?
 	 */
 
+	return sd;
+}
+
+
+/**
+ * ssdp_open_multicast - Open socket for sending multicast SSDP messages
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
+{
+	sm->multicast_sd = ssdp_open_multicast_sock(sm->ip_addr);
+	if (sm->multicast_sd < 0)
+		return -1;
 	return 0;
 }

+ 6 - 0
wpa_supplicant/Makefile

@@ -497,6 +497,12 @@ ifdef NEED_WPS_OOB
 CFLAGS += -DCONFIG_WPS_OOB
 endif
 
+ifdef CONFIG_WPS_ER
+CONFIG_WPS_UPNP=y
+CFLAGS += -DCONFIG_WPS_ER
+OBJS += ../src/wps/wps_er.o
+endif
+
 ifdef CONFIG_WPS_UPNP
 CFLAGS += -DCONFIG_WPS_UPNP
 OBJS += ../src/wps/wps_upnp.o

+ 6 - 0
wpa_supplicant/ctrl_iface.c

@@ -1632,6 +1632,12 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 	} else if (os_strncmp(buf, "WPS_REG ", 8) == 0) {
 		if (wpa_supplicant_ctrl_iface_wps_reg(wpa_s, buf + 8))
 			reply_len = -1;
+	} else if (os_strcmp(buf, "WPS_ER_START") == 0) {
+		if (wpas_wps_er_start(wpa_s))
+			reply_len = -1;
+	} else if (os_strcmp(buf, "WPS_ER_STOP") == 0) {
+		if (wpas_wps_er_stop(wpa_s))
+			reply_len = -1;
 #endif /* CONFIG_WPS */
 #ifdef CONFIG_IBSS_RSN
 	} else if (os_strncmp(buf, "IBSS_RSN ", 9) == 0) {

+ 22 - 0
wpa_supplicant/wpa_cli.c

@@ -533,6 +533,22 @@ static int wpa_cli_cmd_wps_reg(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static int wpa_cli_cmd_wps_er_start(struct wpa_ctrl *ctrl, int argc,
+				    char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WPS_ER_START");
+
+}
+
+
+static int wpa_cli_cmd_wps_er_stop(struct wpa_ctrl *ctrl, int argc,
+				   char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WPS_ER_STOP");
+
+}
+
+
 static int wpa_cli_cmd_ibss_rsn(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
 	char cmd[256];
@@ -1397,6 +1413,12 @@ static struct wpa_cli_cmd wpa_cli_commands[] = {
 	{ "wps_reg", wpa_cli_cmd_wps_reg,
 	  cli_cmd_flag_sensitive,
 	  "<BSSID> <AP PIN> = start WPS Registrar to configure an AP" },
+	{ "wps_er_start", wpa_cli_cmd_wps_er_start,
+	  cli_cmd_flag_none,
+	  "= start Wi-Fi Protected Setup External Registrar" },
+	{ "wps_er_stop", wpa_cli_cmd_wps_er_stop,
+	  cli_cmd_flag_none,
+	  "= stop Wi-Fi Protected Setup External Registrar" },
 	{ "ibss_rsn", wpa_cli_cmd_ibss_rsn,
 	  cli_cmd_flag_none,
 	  "<addr> = request RSN authentication with <addr> in IBSS" },

+ 1 - 0
wpa_supplicant/wpa_supplicant_i.h

@@ -380,6 +380,7 @@ struct wpa_supplicant {
 
 	struct wps_context *wps;
 	int wps_success; /* WPS success event received */
+	struct wps_er *wps_er;
 	int blacklist_cleared;
 
 	struct wpabuf *pending_eapol_rx;

+ 30 - 0
wpa_supplicant/wps_supplicant.c

@@ -814,6 +814,9 @@ void wpas_wps_deinit(struct wpa_supplicant *wpa_s)
 	os_free(wpa_s->wps->network_key);
 	os_free(wpa_s->wps);
 	wpa_s->wps = NULL;
+
+	wps_er_deinit(wpa_s->wps_er);
+	wpa_s->wps_er = NULL;
 }
 
 
@@ -1021,3 +1024,30 @@ int wpas_wps_scan_result_text(const u8 *ies, size_t ies_len, char *buf,
 	wpabuf_free(wps_ie);
 	return ret;
 }
+
+
+int wpas_wps_er_start(struct wpa_supplicant *wpa_s)
+{
+#ifdef CONFIG_WPS_ER
+	if (wpa_s->wps_er) {
+		/* TODO: re-send ctrl_iface events for current data? */
+		return 0;
+	}
+	wpa_s->wps_er = wps_er_init(wpa_s->wps, wpa_s->ifname);
+	if (wpa_s->wps_er == NULL)
+		return -1;
+	return 0;
+#else /* CONFIG_WPS_ER */
+	return 0;
+#endif /* CONFIG_WPS_ER */
+}
+
+
+int wpas_wps_er_stop(struct wpa_supplicant *wpa_s)
+{
+#ifdef CONFIG_WPS_ER
+	wps_er_deinit(wpa_s->wps_er);
+	wpa_s->wps_er = NULL;
+#endif /* CONFIG_WPS_ER */
+	return 0;
+}

+ 2 - 0
wpa_supplicant/wps_supplicant.h

@@ -49,6 +49,8 @@ void wpas_wps_notify_scan_results(struct wpa_supplicant *wpa_s);
 int wpas_wps_searching(struct wpa_supplicant *wpa_s);
 int wpas_wps_scan_result_text(const u8 *ies, size_t ies_len, char *pos,
 			      char *end);
+int wpas_wps_er_start(struct wpa_supplicant *wpa_s);
+int wpas_wps_er_stop(struct wpa_supplicant *wpa_s);
 
 #else /* CONFIG_WPS */