[v2] alfred: notify event listener via unix socket

Message ID 20220502114651.771309-1-mareklindner@neomailbox.ch (mailing list archive)
State Changes Requested, archived
Delegated to: Simon Wunderlich
Headers
Series [v2] alfred: notify event listener via unix socket |

Commit Message

Marek Lindner May 2, 2022, 11:46 a.m. UTC
  The alfred server instance accepts event notification registration
via the unix socket. These notification sockets only inform
registered parties of the availability of an alfred datatype change.
The actual data itself needs to be retrieved via the existing data
retrieval mechanisms.

Unlike the update-command this event monitor allows:

- multiple parallel listeners
- programmatic access to changes without requiring multiple processes

The alfred client allows to monitor events via the newly added '-E'
(event monitor) command line option. Serving as debugging tool and
example code at the same time.

Signed-off-by: Marek Lindner <mareklindner@neomailbox.ch>
---

v2:
- fix typ0s
- replace list_del_init() with list_del()
- remove unnecessary INIT_LIST_HEAD()
- change --event-monitor to not require an argument

 alfred.h     |  15 ++++++
 client.c     |  54 ++++++++++++++++++++
 main.c       |  10 +++-
 man/alfred.8 |   3 ++
 packet.h     |  26 ++++++++++
 recv.c       |   4 +-
 server.c     |   5 ++
 unix_sock.c  | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 256 insertions(+), 2 deletions(-)
  

Comments

Sven Eckelmann May 14, 2022, 9:51 a.m. UTC | #1
On Monday, 2 May 2022 13:46:51 CEST Marek Lindner wrote:
> The alfred server instance accepts event notification registration
> via the unix socket. These notification sockets only inform
> registered parties of the availability of an alfred datatype change.
> The actual data itself needs to be retrieved via the existing data
> retrieval mechanisms.
> 
> Unlike the update-command this event monitor allows:
> 
> - multiple parallel listeners
> - programmatic access to changes without requiring multiple processes
> 
> The alfred client allows to monitor events via the newly added '-E'
> (event monitor) command line option. Serving as debugging tool and
> example code at the same time.
> 
> Signed-off-by: Marek Lindner <mareklindner@neomailbox.ch>
> ---
> 
> v2:
> - fix typ0s
> - replace list_del_init() with list_del()
> - remove unnecessary INIT_LIST_HEAD()
> - change --event-monitor to not require an argument

This version cannot be applied on top of the current master. I have attached 
the diff for the things which (afaik) need to be changed when you rebase the 
patch.

I have also attached the change to support the reporting of the source mac for 
the changed dataset.


Kind regards,
	Sven
  
Marek Lindner May 17, 2022, 7:44 a.m. UTC | #2
Hi,

On Saturday, 14 May 2022 11:51:38 CEST Sven Eckelmann wrote:
> This version cannot be applied on top of the current master. I have attached
> the diff for the things which (afaik) need to be changed when you rebase
> the patch.
> 
> I have also attached the change to support the reporting of the source mac
> for the changed dataset.

I like the epoll change (to avoid unnecessary clutter) and also the source MAC 
reporting. For debugging purposes it might be useful to know where the change 
came from.

Thanks for preparing the rebase & code change patches. Wouldn't you say that 
EPOLL_CTL_DEL somewhere near unix_sock_event_listener_free() could be helpful?

Something like:
-static void unix_sock_event_listener_free(struct event_listener *listener)
+static void unix_sock_event_listener_free(struct globals *globals,
+                                         struct event_listener *listener)
 {
        list_del(&listener->list);
+       epoll_ctl(globals->epollfd, EPOLL_CTL_DEL, listener->fd, NULL);
        close(listener->fd);
        free(listener);
 }

Kind regards,
Marek Lindner
  
Sven Eckelmann May 17, 2022, 11:32 a.m. UTC | #3
On Tuesday, 17 May 2022 09:44:07 CEST Marek Lindner wrote:
[...]
> Thanks for preparing the rebase & code change patches. Wouldn't you say that 
> EPOLL_CTL_DEL somewhere near unix_sock_event_listener_free() could be helpful?
> 
> Something like:
> -static void unix_sock_event_listener_free(struct event_listener *listener)
> +static void unix_sock_event_listener_free(struct globals *globals,
> +                                         struct event_listener *listener)
>  {
>         list_del(&listener->list);
> +       epoll_ctl(globals->epollfd, EPOLL_CTL_DEL, listener->fd, NULL);
>         close(listener->fd);
>         free(listener);
>  }

No, alfred doesn't share file descriptors and thus close of the fd will do 
this for us [1]. 

Kind regards,
	Sven


[1] https://man7.org/linux/man-pages/man7/epoll.7.html
    "Will closing a file descriptor cause it to be removed from
     all epoll interest lists?"
  

Patch

diff --git a/alfred.h b/alfred.h
index 2d98a30..f442c48 100644
--- a/alfred.h
+++ b/alfred.h
@@ -94,6 +94,7 @@  enum clientmode {
 	CLIENT_CHANGE_INTERFACE,
 	CLIENT_CHANGE_BAT_IFACE,
 	CLIENT_SERVER_STATUS,
+	CLIENT_EVENT_MONITOR,
 };
 
 struct interface {
@@ -110,8 +111,15 @@  struct interface {
 	struct list_head list;
 };
 
+struct event_listener {
+	int fd;
+
+	struct list_head list;
+};
+
 struct globals {
 	struct list_head interfaces;
+	struct list_head event_listeners;
 
 	char *net_iface;
 	struct server *best_server;	/* NULL if we are a server ourselves */
@@ -157,6 +165,7 @@  int alfred_client_modeswitch(struct globals *globals);
 int alfred_client_change_interface(struct globals *globals);
 int alfred_client_change_bat_iface(struct globals *globals);
 int alfred_client_server_status(struct globals *globals);
+int alfred_client_event_monitor(struct globals *globals);
 /* recv.c */
 int recv_alfred_packet(struct globals *globals, struct interface *interface,
 		       int recv_sock);
@@ -186,6 +195,12 @@  int unix_sock_open_client(struct globals *globals);
 int unix_sock_close(struct globals *globals);
 int unix_sock_req_data_finish(struct globals *globals,
 			      struct transaction_head *head);
+int unix_sock_events_select_prepare(struct globals *globals, fd_set *fds,
+				    fd_set *errfds, int maxsock);
+void unix_sock_events_select_handle(struct globals *globals,
+				    fd_set *fds, fd_set *errfds);
+void unix_sock_events_close_all(struct globals *globals);
+void unix_sock_event_notify(struct globals *globals, uint8_t type);
 /* vis.c */
 int vis_update_data(struct globals *globals);
 /* netsock.c */
diff --git a/client.c b/client.c
index 81cdd7c..d86d23c 100644
--- a/client.c
+++ b/client.c
@@ -452,3 +452,57 @@  err:
 	unix_sock_close(globals);
 	return 0;
 }
+
+int alfred_client_event_monitor(struct globals *globals)
+{
+	struct alfred_event_register_v0 event_register;
+	struct alfred_event_notify_v0 event_notify;
+	int ret, len;
+
+	if (unix_sock_open_client(globals))
+		return -1;
+
+	len = sizeof(event_register);
+
+	event_register.header.type = ALFRED_EVENT_REGISTER;
+	event_register.header.version = ALFRED_VERSION;
+	event_register.header.length = 0;
+
+	ret = write(globals->unix_sock, &event_register, len);
+	if (ret != len) {
+		fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n",
+			__func__, ret, len, strerror(errno));
+		goto err;
+	}
+
+	while (true) {
+		len = read(globals->unix_sock, &event_notify, sizeof(event_notify));
+		if (len == 0) {
+			fprintf(stdout, "Server closed the connection\n");
+			goto err;
+		}
+
+		if (len < 0) {
+			perror("read from unix socket failed");
+			goto err;
+		}
+
+		if (len != sizeof(event_notify)) {
+			fprintf(stderr, "notify read bytes: %d (expected: %zu)\n",
+				len, sizeof(event_notify));
+				goto err;
+		}
+
+		if (event_notify.header.version != ALFRED_VERSION)
+			continue;
+
+		if (event_notify.header.type != ALFRED_EVENT_NOTIFY)
+			continue;
+
+		fprintf(stdout, "Event: type = %hhu\n", event_notify.type);
+	}
+
+err:
+	unix_sock_close(globals);
+	return 0;
+}
diff --git a/main.c b/main.c
index 68d6efd..6dfb1e7 100644
--- a/main.c
+++ b/main.c
@@ -39,6 +39,7 @@  static void alfred_usage(void)
 	printf("  -I, --change-interface [interface]  change to the specified interface(s)\n");
 	printf("  -B, --change-bat-iface [interface]  change to the specified batman-adv interface\n");
 	printf("  -S, --server-status                 request server status info such as mode & interfaces\n");
+	printf("  -E, --event-monitor                 monitor alfred data record update events\n");
 	printf("\n");
 	printf("server mode options:\n");
 	printf("  -i, --interface                     specify the interface (or comma separated list of interfaces) to listen on\n");
@@ -164,6 +165,7 @@  static struct globals *alfred_init(int argc, char *argv[])
 		{"change-interface",	required_argument,	NULL,	'I'},
 		{"change-bat-iface",	required_argument,	NULL,	'B'},
 		{"server-status",	required_argument,	NULL,	'S'},
+		{"event-monitor",	no_argument,		NULL,	'E'},
 		{"unix-path",		required_argument,	NULL,	'u'},
 		{"update-command",	required_argument,	NULL,	'c'},
 		{"version",		no_argument,		NULL,	'v'},
@@ -181,6 +183,7 @@  static struct globals *alfred_init(int argc, char *argv[])
 	memset(globals, 0, sizeof(*globals));
 
 	INIT_LIST_HEAD(&globals->interfaces);
+	INIT_LIST_HEAD(&globals->event_listeners);
 	globals->net_iface = NULL;
 	globals->opmode = OPMODE_SECONDARY;
 	globals->clientmode = CLIENT_NONE;
@@ -198,7 +201,7 @@  static struct globals *alfred_init(int argc, char *argv[])
 
 	time_random_seed();
 
-	while ((opt = getopt_long(argc, argv, "ms:r:hi:b:vV:M:I:B:Su:dc:p:4:f", long_options,
+	while ((opt = getopt_long(argc, argv, "ms:r:hi:b:vV:M:I:B:SEu:dc:p:4:f", long_options,
 				  &opt_ind)) != -1) {
 		switch (opt) {
 		case 'r':
@@ -263,6 +266,9 @@  static struct globals *alfred_init(int argc, char *argv[])
 		case 'S':
 			globals->clientmode = CLIENT_SERVER_STATUS;
 			break;
+		case 'E':
+			globals->clientmode = CLIENT_EVENT_MONITOR;
+			break;
 		case 'u':
 			globals->unix_path = optarg;
 			break;
@@ -328,6 +334,8 @@  int main(int argc, char *argv[])
 		return alfred_client_change_bat_iface(globals);
 	case CLIENT_SERVER_STATUS:
 		return alfred_client_server_status(globals);
+	case CLIENT_EVENT_MONITOR:
+		return alfred_client_event_monitor(globals);
 	}
 
 	return 0;
diff --git a/man/alfred.8 b/man/alfred.8
index cf0eafc..ca83f02 100644
--- a/man/alfred.8
+++ b/man/alfred.8
@@ -97,6 +97,9 @@  Change the alfred server to use the new \fBbatman-adv interface\fP
 .TP
 \fB\-S\fP, \fB\-\-server\-status\fP
 Request server status information such as mode & interfaces\fP
+.TP
+\fB\-E\fP, \fB\-\-event\-monitor\fP
+Start alfred event monitor connecting to the alfred server and reporting update events\fP
 .
 .SH SERVER OPTIONS
 .TP
diff --git a/packet.h b/packet.h
index 0c1a2eb..84b027f 100644
--- a/packet.h
+++ b/packet.h
@@ -59,6 +59,8 @@  struct alfred_transaction_mgmt {
  * @ALFRED_STATUS_ERROR: Error was detected during the transaction
  * @ALFRED_MODESWITCH: Switch between different operation modes
  * @ALFRED_CHANGE_INTERFACE: Change the listening interface
+ * @ALFRED_EVENT_REGISTER: Request to be notified about alfred update events
+ * @ALFRED_EVENT_NOTIFY: Data record update has been received
  */
 enum alfred_packet_type {
 	ALFRED_PUSH_DATA = 0,
@@ -70,6 +72,8 @@  enum alfred_packet_type {
 	ALFRED_CHANGE_INTERFACE = 6,
 	ALFRED_CHANGE_BAT_IFACE = 7,
 	ALFRED_SERVER_STATUS = 8,
+	ALFRED_EVENT_REGISTER = 9,
+	ALFRED_EVENT_NOTIFY = 10,
 };
 
 /* packets */
@@ -229,6 +233,28 @@  struct alfred_server_status_rep_v0 {
 	struct alfred_tlv header;
 } __packed;
 
+/**
+ * struct alfred_event_register_v0 - event registration message
+ * @header: TLV header describing the complete packet
+ *
+ * Sent by the client to daemon to register for data record updates
+ */
+struct alfred_event_register_v0 {
+	struct alfred_tlv header;
+} __packed;
+
+/**
+ * struct alfred_event_notify_v0 - event notification message
+ * @header: TLV header describing the complete packet
+ * @type: Type of the data triggering the event
+ *
+ * Sent by the daemon to client on data record update
+ */
+struct alfred_event_notify_v0 {
+	struct alfred_tlv header;
+	uint8_t type;
+} __packed;
+
 /**
  * struct alfred_status_v0 - Status info of a transaction
  * @header: TLV header describing the complete packet
diff --git a/recv.c b/recv.c
index 8acad10..36b3a49 100644
--- a/recv.c
+++ b/recv.c
@@ -76,8 +76,10 @@  static int finish_alfred_push_data(struct globals *globals,
 		/* check that data was changed */
 		if (new_entry_created ||
 		    dataset->data.header.length != data_len ||
-		    memcmp(dataset->buf, data->data, data_len) != 0)
+		    memcmp(dataset->buf, data->data, data_len) != 0) {
 			changed_data_type(globals, data->header.type);
+			unix_sock_event_notify(globals, data->header.type);
+		}
 
 		/* free old buffer */
 		if (dataset->buf) {
diff --git a/server.c b/server.c
index bfc37bc..4078fe6 100644
--- a/server.c
+++ b/server.c
@@ -442,6 +442,8 @@  int alfred_server(struct globals *globals)
 
 		maxsock = netsock_prepare_select(globals, &fds, maxsock);
 		maxsock = netsock_prepare_select(globals, &errfds, maxsock);
+		maxsock = unix_sock_events_select_prepare(globals, &fds,
+							  &errfds, maxsock);
 
 		ret = pselect(maxsock + 1, &fds, NULL, &errfds, &tv, NULL);
 
@@ -450,6 +452,8 @@  int alfred_server(struct globals *globals)
 		} else if (ret) {
 			netsock_check_error(globals, &errfds);
 
+			unix_sock_events_select_handle(globals, &fds, &errfds);
+
 			if (FD_ISSET(globals->unix_sock, &fds)) {
 				unix_sock_read(globals);
 				continue;
@@ -478,5 +482,6 @@  int alfred_server(struct globals *globals)
 
 	netsock_close_all(globals);
 	unix_sock_close(globals);
+	unix_sock_events_close_all(globals);
 	return 0;
 }
diff --git a/unix_sock.c b/unix_sock.c
index 3894736..f488763 100644
--- a/unix_sock.c
+++ b/unix_sock.c
@@ -13,6 +13,7 @@ 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <fcntl.h>
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/un.h>
@@ -84,6 +85,7 @@  static int unix_sock_add_data(struct globals *globals,
 			      struct alfred_push_data_v0 *push,
 			      int client_sock)
 {
+	bool new_entry_created = false;
 	struct alfred_data *data;
 	struct dataset *dataset;
 	int len, data_len, ret = -1;
@@ -138,10 +140,17 @@  static int unix_sock_add_data(struct globals *globals,
 			free(dataset);
 			goto err;
 		}
+		new_entry_created = true;
 	}
 	dataset->data_source = SOURCE_LOCAL;
 	clock_gettime(CLOCK_MONOTONIC, &dataset->last_seen);
 
+	/* check that data was changed */
+	if (new_entry_created ||
+	    dataset->data.header.length != data_len ||
+	    memcmp(dataset->buf, data->data, data_len) != 0)
+		unix_sock_event_notify(globals, data->header.type);
+
 	/* free old buffer */
 	free(dataset->buf);
 
@@ -472,6 +481,36 @@  err:
 	return ret;
 }
 
+static int unix_sock_register_listener(struct globals *globals, int client_sock)
+{
+	struct event_listener *listener;
+	int ret;
+
+	ret = fcntl(client_sock, F_GETFL, 0);
+	if (ret < 0) {
+		perror("failed to get file status flags");
+		goto err;
+	}
+
+	ret = fcntl(client_sock, F_SETFL, ret | O_NONBLOCK);
+	if (ret < 0) {
+		perror("failed to set file status flags");
+		goto err;
+	}
+
+	listener = malloc(sizeof(*listener));
+	if (!listener)
+		goto err;
+
+	listener->fd = client_sock;
+	list_add_tail(&listener->list, &globals->event_listeners);
+	return 0;
+
+err:
+	close(client_sock);
+	return -1;
+}
+
 int unix_sock_read(struct globals *globals)
 {
 	int client_sock;
@@ -537,6 +576,9 @@  int unix_sock_read(struct globals *globals)
 	case ALFRED_SERVER_STATUS:
 		ret = unix_sock_server_status(globals, client_sock);
 		break;
+	case ALFRED_EVENT_REGISTER:
+		ret = unix_sock_register_listener(globals, client_sock);
+		break;
 	default:
 		/* unknown packet type */
 		ret = -1;
@@ -555,3 +597,102 @@  int unix_sock_close(struct globals *globals)
 	close(globals->unix_sock);
 	return 0;
 }
+
+static void unix_sock_event_listener_free(struct event_listener *listener)
+{
+	list_del(&listener->list);
+	close(listener->fd);
+	free(listener);
+}
+
+static void unix_sock_event_notify_listener(struct event_listener *listener,
+					    uint8_t type)
+{
+	struct alfred_event_notify_v0 notify;
+	int ret;
+
+	notify.header.type = ALFRED_EVENT_NOTIFY;
+	notify.header.version = ALFRED_VERSION;
+	notify.header.length = FIXED_TLV_LEN(notify);
+	notify.type = type;
+
+	ret = write(listener->fd, &notify, sizeof(notify));
+	if (ret == sizeof(notify))
+		return;
+
+	unix_sock_event_listener_free(listener);
+}
+
+int unix_sock_events_select_prepare(struct globals *globals, fd_set *fds,
+				    fd_set *errfds, int maxsock)
+{
+	struct event_listener *listener;
+
+	list_for_each_entry(listener, &globals->event_listeners, list) {
+		if (listener->fd < 0)
+			continue;
+
+		FD_SET(listener->fd, fds);
+		FD_SET(listener->fd, errfds);
+
+		if (maxsock < listener->fd)
+			maxsock = listener->fd;
+	}
+
+	return maxsock;
+}
+
+void unix_sock_events_select_handle(struct globals *globals,
+				    fd_set *fds, fd_set *errfds)
+{
+	struct event_listener *listener, *tmp;
+	char buff[4];
+	int ret;
+
+	list_for_each_entry_safe(listener, tmp,
+				 &globals->event_listeners, list) {
+		if (FD_ISSET(listener->fd, fds)) {
+			ret = recv(listener->fd, buff, sizeof(buff),
+				   MSG_PEEK | MSG_DONTWAIT);
+			/* listener has hung up */
+			if (ret == 0)
+				unix_sock_event_listener_free(listener);
+			else if (ret > 0) {
+				fprintf(stderr, "Event listener has written to socket: %d - closing\n",
+					listener->fd);
+				unix_sock_event_listener_free(listener);
+			}
+
+			if (ret >= 0)
+				continue;
+		}
+
+		if (FD_ISSET(listener->fd, errfds)) {
+			fprintf(stderr, "Error on event listener detected: %d\n",
+				listener->fd);
+			unix_sock_event_listener_free(listener);
+			continue;
+		}
+	}
+}
+
+void unix_sock_events_close_all(struct globals *globals)
+{
+	struct event_listener *listener, *tmp;
+
+	list_for_each_entry_safe(listener, tmp,
+				 &globals->event_listeners, list) {
+		unix_sock_event_listener_free(listener);
+	}
+}
+
+void unix_sock_event_notify(struct globals *globals, uint8_t type)
+{
+	struct event_listener *listener, *tmp;
+
+	/* if event notify is unsuccessful, listener socket is closed */
+	list_for_each_entry_safe(listener, tmp,
+				 &globals->event_listeners, list) {
+		unix_sock_event_notify_listener(listener, type);
+	}
+}