hostapd: Global control interface notifications

This commit implements hostapd global control interface notifications
infrastructure. hostapd global control interface clients issue
ATTACH/DETACH commands to register and deregister with hostapd
correspondingly - the same way as for any other hostapd/wpa_supplicant
control interface.

Bug: 24270573

Change-Id: I46da39e8262a446e7779a1adfbcb08b5bfa382d7
Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 8ab2941..c606f2c 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -57,6 +57,7 @@
 
 
 static void hostapd_ctrl_iface_send(struct hostapd_data *hapd, int level,
+				    enum wpa_msg_type type,
 				    const char *buf, size_t len);
 
 
@@ -2137,7 +2138,7 @@
 	struct hostapd_data *hapd = ctx;
 	if (hapd == NULL)
 		return;
-	hostapd_ctrl_iface_send(hapd, level, txt, len);
+	hostapd_ctrl_iface_send(hapd, level, type, txt, len);
 }
 
 
@@ -2360,6 +2361,58 @@
 }
 
 
+static int hostapd_global_ctrl_iface_attach(struct hapd_interfaces *interfaces,
+					    struct sockaddr_un *from,
+					    socklen_t fromlen)
+{
+	struct wpa_ctrl_dst *dst;
+
+	dst = os_zalloc(sizeof(*dst));
+	if (dst == NULL)
+		return -1;
+	os_memcpy(&dst->addr, from, sizeof(struct sockaddr_un));
+	dst->addrlen = fromlen;
+	dst->debug_level = MSG_INFO;
+	dst->next = interfaces->global_ctrl_dst;
+	interfaces->global_ctrl_dst = dst;
+	wpa_hexdump(MSG_DEBUG, "CTRL_IFACE monitor attached (global)",
+		    from->sun_path,
+		    fromlen - offsetof(struct sockaddr_un, sun_path));
+	return 0;
+}
+
+
+static int hostapd_global_ctrl_iface_detach(struct hapd_interfaces *interfaces,
+					    struct sockaddr_un *from,
+					    socklen_t fromlen)
+{
+	struct wpa_ctrl_dst *dst, *prev = NULL;
+
+	dst = interfaces->global_ctrl_dst;
+	while (dst) {
+		if (fromlen == dst->addrlen &&
+		    os_memcmp(from->sun_path, dst->addr.sun_path,
+			      fromlen - offsetof(struct sockaddr_un, sun_path))
+		    == 0) {
+			wpa_hexdump(MSG_DEBUG,
+				    "CTRL_IFACE monitor detached (global)",
+				    from->sun_path,
+				    fromlen -
+				    offsetof(struct sockaddr_un, sun_path));
+			if (prev == NULL)
+				interfaces->global_ctrl_dst = dst->next;
+			else
+				prev->next = dst->next;
+			os_free(dst);
+			return 0;
+		}
+		prev = dst;
+		dst = dst->next;
+	}
+	return -1;
+}
+
+
 static void hostapd_ctrl_iface_flush(struct hapd_interfaces *interfaces)
 {
 #ifdef CONFIG_WPS_TESTING
@@ -2378,8 +2431,9 @@
 	int res;
 	struct sockaddr_un from;
 	socklen_t fromlen = sizeof(from);
-	char reply[24];
+	char *reply;
 	int reply_len;
+	const int reply_size = 4096;
 
 	res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
 		       (struct sockaddr *) &from, &fromlen);
@@ -2391,6 +2445,16 @@
 	buf[res] = '\0';
 	wpa_printf(MSG_DEBUG, "Global ctrl_iface command: %s", buf);
 
+	reply = os_malloc(reply_size);
+	if (reply == NULL) {
+		if (sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from,
+			   fromlen) < 0) {
+			wpa_printf(MSG_DEBUG, "CTRL: sendto failed: %s",
+				   strerror(errno));
+		}
+		return;
+	}
+
 	os_memcpy(reply, "OK\n", 3);
 	reply_len = 3;
 
@@ -2408,6 +2472,14 @@
 	} else if (os_strncmp(buf, "REMOVE ", 7) == 0) {
 		if (hostapd_ctrl_iface_remove(interfaces, buf + 7) < 0)
 			reply_len = -1;
+	} else if (os_strcmp(buf, "ATTACH") == 0) {
+		if (hostapd_global_ctrl_iface_attach(interfaces, &from,
+						     fromlen))
+			reply_len = -1;
+	} else if (os_strcmp(buf, "DETACH") == 0) {
+		if (hostapd_global_ctrl_iface_detach(interfaces, &from,
+			fromlen))
+			reply_len = -1;
 #ifdef CONFIG_MODULE_TESTS
 	} else if (os_strcmp(buf, "MODULE_TESTS") == 0) {
 		int hapd_module_tests(void);
@@ -2430,6 +2502,7 @@
 		wpa_printf(MSG_DEBUG, "CTRL: sendto failed: %s",
 			   strerror(errno));
 	}
+	os_free(reply);
 }
 
 
@@ -2567,6 +2640,7 @@
 void hostapd_global_ctrl_iface_deinit(struct hapd_interfaces *interfaces)
 {
 	char *fname = NULL;
+	struct wpa_ctrl_dst *dst, *prev;
 
 	if (interfaces->global_ctrl_sock > -1) {
 		eloop_unregister_read_sock(interfaces->global_ctrl_sock);
@@ -2591,13 +2665,23 @@
 					   strerror(errno));
 			}
 		}
-		os_free(interfaces->global_iface_path);
-		interfaces->global_iface_path = NULL;
+	}
+
+	os_free(interfaces->global_iface_path);
+	interfaces->global_iface_path = NULL;
+
+	dst = interfaces->global_ctrl_dst;
+	interfaces->global_ctrl_dst = NULL;
+	while (dst) {
+		prev = dst;
+		dst = dst->next;
+		os_free(prev);
 	}
 }
 
 
 static void hostapd_ctrl_iface_send(struct hostapd_data *hapd, int level,
+				    enum wpa_msg_type type,
 				    const char *buf, size_t len)
 {
 	struct wpa_ctrl_dst *dst, *next;
@@ -2605,9 +2689,17 @@
 	int idx;
 	struct iovec io[2];
 	char levelstr[10];
+	int s;
 
-	dst = hapd->ctrl_dst;
-	if (hapd->ctrl_sock < 0 || dst == NULL)
+	if (type != WPA_MSG_ONLY_GLOBAL) {
+		s = hapd->ctrl_sock;
+		dst = hapd->ctrl_dst;
+	} else {
+		s = hapd->iface->interfaces->global_ctrl_sock;
+		dst = hapd->iface->interfaces->global_ctrl_dst;
+	}
+
+	if (s < 0 || dst == NULL)
 		return;
 
 	os_snprintf(levelstr, sizeof(levelstr), "<%d>", level);
@@ -2628,16 +2720,22 @@
 				    offsetof(struct sockaddr_un, sun_path));
 			msg.msg_name = &dst->addr;
 			msg.msg_namelen = dst->addrlen;
-			if (sendmsg(hapd->ctrl_sock, &msg, 0) < 0) {
+			if (sendmsg(s, &msg, 0) < 0) {
 				int _errno = errno;
 				wpa_printf(MSG_INFO, "CTRL_IFACE monitor[%d]: "
 					   "%d - %s",
 					   idx, errno, strerror(errno));
 				dst->errors++;
 				if (dst->errors > 10 || _errno == ENOENT) {
-					hostapd_ctrl_iface_detach(
-						hapd, &dst->addr,
-						dst->addrlen);
+					if (type != WPA_MSG_ONLY_GLOBAL)
+						hostapd_ctrl_iface_detach(
+							hapd, &dst->addr,
+							dst->addrlen);
+					else
+						hostapd_global_ctrl_iface_detach(
+							hapd->iface->interfaces,
+							&dst->addr,
+							dst->addrlen);
 				}
 			} else
 				dst->errors = 0;
diff --git a/hostapd/main.c b/hostapd/main.c
index e36c948..62d0775 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -561,6 +561,7 @@
 	interfaces.global_iface_path = NULL;
 	interfaces.global_iface_name = NULL;
 	interfaces.global_ctrl_sock = -1;
+	interfaces.global_ctrl_dst = NULL;
 
 	for (;;) {
 		c = getopt(argc, argv, "b:Bde:f:hKP:Ttu:vg:G:");