minstrel-rcd: add work-in-progress minstrel remote control daemon
authorFelix Fietkau <nbd@nbd.name>
Tue, 2 Feb 2021 12:15:30 +0000 (13:15 +0100)
committerFelix Fietkau <nbd@nbd.name>
Sat, 11 Dec 2021 12:53:01 +0000 (13:53 +0100)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/utils/minstrel-rcd/Makefile [new file with mode: 0644]
package/utils/minstrel-rcd/files/minstrel-rcd.config [new file with mode: 0644]
package/utils/minstrel-rcd/files/minstrel-rcd.init [new file with mode: 0644]
package/utils/minstrel-rcd/src/CMakeLists.txt [new file with mode: 0644]
package/utils/minstrel-rcd/src/client.c [new file with mode: 0644]
package/utils/minstrel-rcd/src/main.c [new file with mode: 0644]
package/utils/minstrel-rcd/src/phy.c [new file with mode: 0644]
package/utils/minstrel-rcd/src/rcd.h [new file with mode: 0644]
package/utils/minstrel-rcd/src/server.c [new file with mode: 0644]

diff --git a/package/utils/minstrel-rcd/Makefile b/package/utils/minstrel-rcd/Makefile
new file mode 100644 (file)
index 0000000..31fe692
--- /dev/null
@@ -0,0 +1,37 @@
+#
+# Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=minstrel-rcd
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)-$(PKG_RELEASE)
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+define Package/minstrel-rcd
+  SECTION:=utils
+  CATEGORY:=Utilities
+  TITLE:=Daemon for remote controlling mac80211 minstrel rate control
+  DEPENDS:=+libubox
+  MAINTAINER:=Felix Fietkau <nbd@nbd.name>
+endef
+
+define Package/minstrel-rcd/conffiles
+/etc/config/minstrel-rcd
+endef
+
+define Package/minstrel-rcd/install
+       $(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/config
+       $(INSTALL_DATA) ./files/minstrel-rcd.config $(1)/etc/config/minstrel-rcd
+       $(INSTALL_BIN) ./files/minstrel-rcd.init $(1)/etc/init.d/minstrel-rcd
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/minstrel-rcd $(1)/usr/sbin/
+endef
+
+$(eval $(call BuildPackage,minstrel-rcd))
diff --git a/package/utils/minstrel-rcd/files/minstrel-rcd.config b/package/utils/minstrel-rcd/files/minstrel-rcd.config
new file mode 100644 (file)
index 0000000..b7db7e2
--- /dev/null
@@ -0,0 +1,3 @@
+config rcd rcd
+       option enabled '0'
+       option listen '0.0.0.0'
diff --git a/package/utils/minstrel-rcd/files/minstrel-rcd.init b/package/utils/minstrel-rcd/files/minstrel-rcd.init
new file mode 100644 (file)
index 0000000..2e6db42
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2020 OpenWrt.org
+
+START=99
+USE_PROCD=1
+PROG=/usr/sbin/minstrel-rcd
+
+service_triggers() {
+       procd_add_reload_trigger "minstrel-rcd"
+}
+
+validate_rcd_section() {
+       uci_load_validate minstrel-rcd rcd "$1" "$2" \
+               'listen:list(host)' 'enabled:bool:1'
+}
+
+start_rcd_instance() {
+       [ "$enabled" -eq 0 ] && return
+
+       procd_open_instance
+       procd_set_param command "$PROG"
+       for addr in $listen; do
+               procd_append_param command -h $addr
+       done
+       procd_set_param respawn
+       procd_close_instance
+}
+
+start_service() {
+       validate_rcd_section rcd start_rcd_instance
+}
diff --git a/package/utils/minstrel-rcd/src/CMakeLists.txt b/package/utils/minstrel-rcd/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bcabcc2
--- /dev/null
@@ -0,0 +1,21 @@
+cmake_minimum_required(VERSION 2.6)
+
+PROJECT(minstrel-rcd C)
+
+ADD_DEFINITIONS(-Wall -Werror)
+IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6)
+       ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration)
+       ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral)
+ENDIF()
+ADD_DEFINITIONS(-Os -std=gnu99 -g3 -Wmissing-declarations -Wno-unused-parameter)
+
+FIND_LIBRARY(ubox_library NAMES ubox)
+FIND_PATH(ubox_include_dir libubox/usock.h)
+INCLUDE_DIRECTORIES(${ubox_include_dir})
+
+ADD_EXECUTABLE(minstrel-rcd main.c phy.c server.c client.c)
+TARGET_LINK_LIBRARIES(minstrel-rcd ${ubox_library})
+
+INSTALL(TARGETS minstrel-rcd
+       RUNTIME DESTINATION sbin
+)
diff --git a/package/utils/minstrel-rcd/src/client.c b/package/utils/minstrel-rcd/src/client.c
new file mode 100644 (file)
index 0000000..b0aa710
--- /dev/null
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+#include "rcd.h"
+
+static LIST_HEAD(clients);
+
+void rcd_client_set_phy_state(struct client *cl, struct phy *phy, bool add)
+{
+       if (!cl) {
+               list_for_each_entry(cl, &clients, list)
+                       rcd_client_set_phy_state(cl, phy, add);
+               return;
+       }
+
+       if (add && !cl->init_done) {
+               rcd_phy_dump(cl, phy);
+               cl->init_done = true;
+       }
+
+       client_phy_printf(cl, phy, "0;%s\n", add ? "add" : "remove");
+}
+
+void rcd_client_phy_event(struct phy *phy, const char *str)
+{
+       struct client *cl;
+
+       list_for_each_entry(cl, &clients, list)
+               client_phy_printf(cl, phy, "%s\n", str);
+}
+
+static void
+client_start(struct client *cl)
+{
+       struct phy *phy;
+
+       vlist_for_each_element(&phy_list, phy, node)
+               rcd_client_set_phy_state(cl, phy, true);
+}
+
+static int
+client_handle_data(struct client *cl, char *data)
+{
+       char *sep;
+       int len = 0;
+
+       while ((sep = strchr(data, '\n')) != NULL) {
+               len += sep - data + 1;
+               if (sep[-1] == '\r')
+                       sep[-1] = 0;
+               *sep = 0;
+               rcd_phy_control(cl, data);
+               data = sep + 1;
+       }
+
+       return len;
+}
+
+static void
+client_notify_read(struct ustream *s, int bytes)
+{
+       struct client *cl = container_of(s, struct client, sfd.stream);
+       char *data;
+       int len;
+
+       while (1) {
+               data = ustream_get_read_buf(s, &len);
+               if (!data)
+                       return;
+
+               len = client_handle_data(cl, data);
+               if (!len)
+                       return;
+
+               ustream_consume(s, len);
+       }
+}
+
+static void
+client_notify_state(struct ustream *s)
+{
+       struct client *cl = container_of(s, struct client, sfd.stream);
+
+       if (!s->write_error && !s->eof)
+               return;
+
+       ustream_free(s);
+       close(cl->sfd.fd.fd);
+       list_del(&cl->list);
+       free(cl);
+}
+
+void rcd_client_accept(int fd)
+{
+       struct ustream *us;
+       struct client *cl;
+
+       cl = calloc(1, sizeof(*cl));
+       us = &cl->sfd.stream;
+       us->notify_read = client_notify_read;
+       us->notify_state = client_notify_state;
+       us->string_data = true;
+       ustream_fd_init(&cl->sfd, fd);
+       list_add_tail(&cl->list, &clients);
+       client_start(cl);
+}
diff --git a/package/utils/minstrel-rcd/src/main.c b/package/utils/minstrel-rcd/src/main.c
new file mode 100644 (file)
index 0000000..881ec6b
--- /dev/null
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+#include <libubox/usock.h>
+#include <libubox/ustream.h>
+#include "rcd.h"
+
+int main(int argc, char **argv)
+{
+       int ch;
+
+       uloop_init();
+
+       while ((ch = getopt(argc, argv, "h:")) != -1) {
+               switch (ch) {
+               case 'h':
+                       rcd_server_add(optarg);
+                       break;
+               }
+       }
+
+       rcd_phy_init();
+       rcd_server_init();
+       uloop_run();
+
+       uloop_end();
+
+       return 0;
+}
diff --git a/package/utils/minstrel-rcd/src/phy.c b/package/utils/minstrel-rcd/src/phy.c
new file mode 100644 (file)
index 0000000..39cc850
--- /dev/null
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+#include <libubox/avl-cmp.h>
+#include <glob.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <libgen.h>
+#include "rcd.h"
+
+static void phy_update(struct vlist_tree *tree, struct vlist_node *node_new,
+                      struct vlist_node *node_old);
+
+VLIST_TREE(phy_list, avl_strcmp, phy_update, true, false);
+
+static const char *
+phy_file_path(struct phy *phy, const char *file)
+{
+       static char path[64];
+
+       snprintf(path, sizeof(path), "/sys/kernel/debug/ieee80211/%s/rc/%s", phy_name(phy), file);
+
+       return path;
+}
+
+static int
+phy_event_read_buf(struct phy *phy, char *buf)
+{
+       char *cur, *next;
+       int len;
+
+       for (cur = buf; (next = strchr(cur, '\n')); cur = next + 1) {
+               *next = 0;
+
+               rcd_client_phy_event(phy, cur);
+       }
+
+       len = strlen(cur);
+       if (cur > buf)
+               memmove(buf, cur, len + 1);
+
+       return len;
+}
+
+static void
+phy_event_cb(struct uloop_fd *fd, unsigned int events)
+{
+       struct phy *phy = container_of(fd, struct phy, event_fd);
+       char buf[512];
+       int len, offset = 0;
+
+       while (1) {
+               len = read(fd->fd, buf + offset, sizeof(buf) - 1 - offset);
+               if (len < 0) {
+                       if (errno == EAGAIN)
+                               return;
+
+                       if (errno == EINTR)
+                               continue;
+
+                       vlist_delete(&phy_list, &phy->node);
+                       return;
+               }
+
+               if (!len)
+                       return;
+
+               buf[offset + len] = 0;
+               offset = phy_event_read_buf(phy, buf);
+       }
+}
+
+static void
+phy_init(struct phy *phy)
+{
+       phy->control_fd = -1;
+}
+
+static void
+phy_add(struct phy *phy)
+{
+       int cfd, efd;
+
+       cfd = open(phy_file_path(phy, "api_control"), O_WRONLY);
+       if (cfd < 0)
+               goto remove;
+
+       efd = open(phy_file_path(phy, "api_event"), O_RDONLY);
+       if (efd < 0)
+               goto close_cfd;
+
+       phy->control_fd = cfd;
+       phy->event_fd.fd = efd;
+       phy->event_fd.cb = phy_event_cb;
+       uloop_fd_add(&phy->event_fd, ULOOP_READ);
+
+       rcd_client_set_phy_state(NULL, phy, true);
+       return;
+
+close_cfd:
+       close(cfd);
+remove:
+       vlist_delete(&phy_list, &phy->node);
+}
+
+static void
+phy_remove(struct phy *phy)
+{
+       if (phy->control_fd < 0)
+               goto out;
+
+       rcd_client_set_phy_state(NULL, phy, false);
+       uloop_fd_delete(&phy->event_fd);
+       close(phy->control_fd);
+       close(phy->event_fd.fd);
+
+out:
+       free(phy);
+}
+
+static void
+phy_update(struct vlist_tree *tree, struct vlist_node *node_new,
+          struct vlist_node *node_old)
+{
+       struct phy *phy_new = node_new ? container_of(node_new, struct phy, node) : NULL;
+       struct phy *phy_old = node_old ? container_of(node_old, struct phy, node) : NULL;
+
+       if (phy_new && phy_old)
+               phy_remove(phy_new);
+       else if (phy_new)
+               phy_add(phy_new);
+       else
+               phy_remove(phy_old);
+}
+
+static void phy_refresh_timer(struct uloop_timeout *t)
+{
+       unsigned int i;
+       glob_t gl;
+
+       glob("/sys/kernel/debug/ieee80211/phy*", 0, NULL, &gl);
+       for (i = 0; i < gl.gl_pathc; i++) {
+               struct phy *phy;
+               char *name, *name_buf;
+
+               name = basename(gl.gl_pathv[i]);
+               phy = calloc_a(sizeof(*phy), &name_buf, strlen(name) + 1);
+               phy_init(phy);
+               vlist_add(&phy_list, &phy->node, strcpy(name_buf, name));
+       }
+       globfree(&gl);
+
+       uloop_timeout_set(t, 1000);
+}
+
+void rcd_phy_init_client(struct client *cl)
+{
+       struct phy *phy;
+
+       vlist_for_each_element(&phy_list, phy, node)
+               rcd_client_set_phy_state(cl, phy, true);
+}
+
+void rcd_phy_dump(struct client *cl, struct phy *phy)
+{
+       char buf[128];
+       FILE *f;
+
+       f = fopen(phy_file_path(phy, "api_info"), "r");
+       if (!f)
+               return;
+
+       while (fgets(buf, sizeof(buf), f) != NULL)
+               client_printf(cl, "*;0;%s", buf);
+
+       fclose(f);
+}
+
+void rcd_phy_control(struct client *cl, char *data)
+{
+       struct phy *phy;
+       const char *err;
+       char *sep;
+
+       sep = strchr(data, ';');
+       if (!sep) {
+               err = "Syntax error";
+               goto error;
+       }
+
+       *sep = 0;
+       phy = vlist_find(&phy_list, data, phy, node);
+       if (!phy) {
+               err = "PHY not found";
+               goto error;
+       }
+
+       data = sep + 1;
+retry:
+       if (write(phy->control_fd, data, strlen(data)) < 0) {
+               if (errno == EINTR || errno == EAGAIN)
+                       goto retry;
+
+               err = strerror(errno);
+               goto error;
+       }
+
+       return;
+
+error:
+       client_printf(cl, "*;0;#error;%s\n", err);
+}
+
+void rcd_phy_init(void)
+{
+       static struct uloop_timeout t = {
+               .cb = phy_refresh_timer
+       };
+
+       uloop_timeout_set(&t, 1);
+}
diff --git a/package/utils/minstrel-rcd/src/rcd.h b/package/utils/minstrel-rcd/src/rcd.h
new file mode 100644 (file)
index 0000000..4a3c8a3
--- /dev/null
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+#ifndef __MINSTREL_RCD_H
+#define __MINSTREL_RCD_H
+
+#include <libubox/list.h>
+#include <libubox/vlist.h>
+#include <libubox/uloop.h>
+#include <libubox/ustream.h>
+#include <libubox/utils.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define RCD_PORT 0x5243
+
+struct phy {
+       struct vlist_node node;
+
+       struct uloop_fd event_fd;
+       int control_fd;
+};
+
+struct client {
+       struct list_head list;
+       struct ustream_fd sfd;
+       bool init_done;
+};
+
+struct server {
+       struct list_head list;
+       struct uloop_fd fd;
+       const char *addr;
+};
+
+static inline const char *phy_name(struct phy *phy)
+{
+       return phy->node.avl.key;
+}
+
+extern struct vlist_tree phy_list;
+
+void rcd_server_add(const char *addr);
+void rcd_server_init(void);
+
+void rcd_client_accept(int fd);
+void rcd_client_phy_event(struct phy *phy, const char *str);
+void rcd_client_set_phy_state(struct client *cl, struct phy *phy, bool add);
+
+void rcd_phy_init(void);
+void rcd_phy_init_client(struct client *cl);
+void rcd_phy_dump(struct client *cl, struct phy *phy);
+void rcd_phy_control(struct client *cl, char *data);
+
+#define client_printf(cl, ...) ustream_printf(&(cl)->sfd.stream, __VA_ARGS__)
+#define client_phy_printf(cl, phy, fmt, ...) client_printf(cl, "%s;" fmt, phy_name(phy), ## __VA_ARGS__)
+
+#endif
diff --git a/package/utils/minstrel-rcd/src/server.c b/package/utils/minstrel-rcd/src/server.c
new file mode 100644 (file)
index 0000000..b4c53fa
--- /dev/null
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <libubox/usock.h>
+
+#include "rcd.h"
+
+static LIST_HEAD(servers);
+static LIST_HEAD(pending);
+static bool in_init;
+static struct uloop_timeout restart_timer;
+
+static void
+server_cb(struct uloop_fd *fd, unsigned int events)
+{
+       struct server *s = container_of(fd, struct server, fd);
+       struct sockaddr_in6 addr;
+       unsigned int sl;
+       int cfd;
+
+       while (1) {
+               sl = sizeof(addr);
+               cfd = accept(fd->fd, (struct sockaddr *)&addr, &sl);
+
+               if (cfd < 0) {
+                       if (errno == EAGAIN)
+                               return;
+
+                       if (errno == EINTR)
+                               continue;
+
+                       /* other error, restart */
+                       uloop_fd_delete(fd);
+                       close(fd->fd);
+                       list_move_tail(&s->list, &pending);
+                       uloop_timeout_set(&restart_timer, 1000);
+               }
+
+               rcd_client_accept(cfd);
+       }
+}
+
+static void server_start(struct server *s)
+{
+       s->fd.fd = usock(USOCK_SERVER | USOCK_NONBLOCK | USOCK_TCP, s->addr, usock_port(RCD_PORT));
+       if (s->fd.fd < 0) {
+               if (in_init)
+                       fprintf(stderr, "WARNING: Failed to open server port on %s\n", s->addr);
+               return;
+       }
+
+       s->fd.cb = server_cb;
+       uloop_fd_add(&s->fd, ULOOP_READ);
+       list_move_tail(&s->list, &servers);
+}
+
+static void
+server_start_pending(struct uloop_timeout *timeout)
+{
+       struct server *s, *tmp;
+
+       list_for_each_entry_safe(s, tmp, &pending, list)
+               server_start(s);
+
+       if (!list_empty(&pending))
+               uloop_timeout_set(timeout, 1000);
+}
+
+void rcd_server_add(const char *addr)
+{
+       struct server *s;
+
+       s = calloc(1, sizeof(*s));
+       s->addr = addr;
+       list_add_tail(&s->list, &pending);
+
+}
+
+void rcd_server_init(void)
+{
+       if (list_empty(&pending))
+               rcd_server_add("127.0.0.1");
+
+       restart_timer.cb = server_start_pending;
+
+       in_init = true;
+       server_start_pending(&restart_timer);
+       in_init = false;
+}