From b9d80be5cbb09bbc4d7df7c4e71ae97b35af82d7 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 2 Feb 2021 13:15:30 +0100 Subject: [PATCH] minstrel-rcd: add work-in-progress minstrel remote control daemon Signed-off-by: Felix Fietkau --- package/utils/minstrel-rcd/Makefile | 37 +++ .../minstrel-rcd/files/minstrel-rcd.config | 3 + .../minstrel-rcd/files/minstrel-rcd.init | 31 +++ package/utils/minstrel-rcd/src/CMakeLists.txt | 21 ++ package/utils/minstrel-rcd/src/client.c | 105 +++++++++ package/utils/minstrel-rcd/src/main.c | 28 +++ package/utils/minstrel-rcd/src/phy.c | 221 ++++++++++++++++++ package/utils/minstrel-rcd/src/rcd.h | 61 +++++ package/utils/minstrel-rcd/src/server.c | 94 ++++++++ 9 files changed, 601 insertions(+) create mode 100644 package/utils/minstrel-rcd/Makefile create mode 100644 package/utils/minstrel-rcd/files/minstrel-rcd.config create mode 100644 package/utils/minstrel-rcd/files/minstrel-rcd.init create mode 100644 package/utils/minstrel-rcd/src/CMakeLists.txt create mode 100644 package/utils/minstrel-rcd/src/client.c create mode 100644 package/utils/minstrel-rcd/src/main.c create mode 100644 package/utils/minstrel-rcd/src/phy.c create mode 100644 package/utils/minstrel-rcd/src/rcd.h create mode 100644 package/utils/minstrel-rcd/src/server.c diff --git a/package/utils/minstrel-rcd/Makefile b/package/utils/minstrel-rcd/Makefile new file mode 100644 index 0000000000..31fe692a46 --- /dev/null +++ b/package/utils/minstrel-rcd/Makefile @@ -0,0 +1,37 @@ +# +# Copyright (C) 2021 Felix Fietkau +# +# 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 +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 index 0000000000..b7db7e282c --- /dev/null +++ b/package/utils/minstrel-rcd/files/minstrel-rcd.config @@ -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 index 0000000000..2e6db42283 --- /dev/null +++ b/package/utils/minstrel-rcd/files/minstrel-rcd.init @@ -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 index 0000000000..bcabcc2632 --- /dev/null +++ b/package/utils/minstrel-rcd/src/CMakeLists.txt @@ -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 index 0000000000..b0aa710bd8 --- /dev/null +++ b/package/utils/minstrel-rcd/src/client.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 Felix Fietkau */ +#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 index 0000000000..881ec6b20b --- /dev/null +++ b/package/utils/minstrel-rcd/src/main.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 Felix Fietkau */ +#include +#include +#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 index 0000000000..39cc8504aa --- /dev/null +++ b/package/utils/minstrel-rcd/src/phy.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 Felix Fietkau */ +#include +#include +#include +#include +#include +#include +#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 index 0000000000..4a3c8a32a0 --- /dev/null +++ b/package/utils/minstrel-rcd/src/rcd.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 Felix Fietkau */ +#ifndef __MINSTREL_RCD_H +#define __MINSTREL_RCD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000000..b4c53fa45f --- /dev/null +++ b/package/utils/minstrel-rcd/src/server.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2021 Felix Fietkau */ +#include +#include +#include +#include +#include + +#include + +#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; +} -- 2.30.2