├── .gitignore ├── openwrt └── usteer │ ├── Makefile │ └── files │ └── etc │ ├── init.d │ └── usteer │ └── config │ └── usteer ├── README.md ├── event.h ├── CMakeLists.txt ├── timeout.h ├── utils.h ├── remote.h ├── node.h ├── band_steering.c ├── measurement.c ├── timeout.c ├── netifd.c ├── node.c ├── parse.c ├── monitor.c ├── main.c ├── sta.c ├── fakeap.c ├── event.c ├── usteer.h ├── nl80211.c ├── policy.c ├── remote.c ├── ubus.c └── local_node.c /.gitignore: -------------------------------------------------------------------------------- 1 | /Makefile 2 | CMakeCache.txt 3 | CMakeFiles 4 | *.cmake 5 | install_manifest.txt 6 | /usteerd 7 | /ap-monitor 8 | /fakeap 9 | .vscode/ 10 | *.patch 11 | *.orig 12 | 13 | -------------------------------------------------------------------------------- /openwrt/usteer/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | PKG_NAME:=usteer 4 | PKG_VERSION:=$(shell git show -s --format=%cd --date=short) 5 | PKG_RELEASE:=1 6 | 7 | PKG_BUILD_DEPENDS:=libpcap 8 | PKG_BUILD_PARALLEL:=1 9 | 10 | PKG_FILE_DEPENDS:=$(CURDIR)/../.. 11 | 12 | include $(INCLUDE_DIR)/package.mk 13 | include $(INCLUDE_DIR)/cmake.mk 14 | 15 | define Build/Prepare 16 | mkdir -p $(PKG_BUILD_DIR) 17 | ln -s $(CURDIR)/../../.git $(PKG_BUILD_DIR)/.git 18 | cd $(PKG_BUILD_DIR) && git checkout . 19 | endef 20 | 21 | define Package/usteer 22 | SECTION:=net 23 | CATEGORY:=Network 24 | DEPENDS:=+libubox +libubus +libblobmsg-json +libnl-tiny 25 | TITLE:=OpenWrt AP roaming assist daemon 26 | endef 27 | 28 | define Package/usteer/conffiles 29 | /etc/config/usteer 30 | endef 31 | 32 | define Package/usteer/install 33 | $(INSTALL_DIR) $(1)/sbin $(1)/etc/init.d $(1)/etc/config 34 | $(CP) ./files/* $(1)/ 35 | $(CP) $(PKG_BUILD_DIR)/usteerd $(1)/sbin/ 36 | endef 37 | 38 | $(eval $(call BuildPackage,usteer)) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # usteer 2 | 3 | usteer is a client steering daemon written for OpenWrt. 4 | 5 | Its goal is to optimize roaming behavior of wireless clients (STAs) in a ESS consisting of multiple BSS / APs. 6 | 7 | ## Functions 8 | 9 | - Synchronization of Neighbor Reports between multiple APs 10 | - Policy-based decisions for probe- / association- / authentication requests received from STAs 11 | - Requesting clients to roam to a different BSS based on SNR / signal-level 12 | - Channel-load based client steering to different BSS 13 | 14 | ## Installation 15 | 16 | usteer is available from the OpenWrt packages feed and can be installed on devices running OpenWrt 21.02+ using opkg: 17 | 18 | ``` 19 | opkg update; opkg install usteer 20 | ``` 21 | 22 | ## Submitting patches 23 | 24 | usteer patches are welcome on the openwrt-devel mailing list. 25 | 26 | Before submitting patches, check out OpenWrts guide on submission policies. 27 | 28 | Make sure to add a `usteer` subject prefix using the `--subject-prefix` option when exporting the patch with `git format-patch`. 29 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | #ifndef __USTEER_EVENT_H 2 | #define __USTEER_EVENT_H 3 | 4 | enum uevent_type { 5 | UEV_PROBE_REQ_ACCEPT, 6 | UEV_PROBE_REQ_DENY, 7 | UEV_AUTH_REQ_ACCEPT, 8 | UEV_AUTH_REQ_DENY, 9 | UEV_ASSOC_REQ_ACCEPT, 10 | UEV_ASSOC_REQ_DENY, 11 | UEV_LOAD_KICK_TRIGGER, 12 | UEV_LOAD_KICK_RESET, 13 | UEV_LOAD_KICK_MIN_CLIENTS, 14 | UEV_LOAD_KICK_NO_CLIENT, 15 | UEV_LOAD_KICK_CLIENT, 16 | UEV_SIGNAL_KICK, 17 | }; 18 | 19 | enum uevent_reason { 20 | UEV_REASON_NONE, 21 | UEV_REASON_RETRY_EXCEEDED, 22 | UEV_REASON_LOW_SIGNAL, 23 | UEV_REASON_CONNECT_DELAY, 24 | UEV_REASON_BETTER_CANDIDATE, 25 | }; 26 | 27 | enum uevent_select_reason { 28 | UEV_SELECT_REASON_NUM_ASSOC, 29 | UEV_SELECT_REASON_SIGNAL, 30 | UEV_SELECT_REASON_LOAD, 31 | }; 32 | 33 | #define UEV_SELECT_REASON_ALL ((1 << UEV_SELECT_REASON_NUM_ASSOC) | (1 << UEV_SELECT_REASON_SIGNAL) | (1 << UEV_SELECT_REASON_LOAD)) 34 | 35 | struct uevent { 36 | enum uevent_type type; 37 | enum uevent_reason reason; 38 | uint32_t select_reasons; 39 | 40 | struct usteer_node *node_local; 41 | struct sta *sta; 42 | 43 | struct sta_info *si_cur; 44 | struct sta_info *si_other; 45 | 46 | struct usteer_node *node_cur; 47 | struct usteer_node *node_other; 48 | 49 | unsigned int count; 50 | 51 | struct { 52 | int cur; 53 | int ref; 54 | } threshold; 55 | }; 56 | 57 | void usteer_event(struct uevent *ev); 58 | void config_set_event_log_types(struct blob_attr *attr); 59 | void config_get_event_log_types(struct blob_buf *buf); 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | INCLUDE (CheckIncludeFiles) 3 | INCLUDE(FindPkgConfig) 4 | 5 | PROJECT(usteerd C) 6 | 7 | IF("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND NOT NL_CFLAGS) 8 | FIND_PROGRAM(PKG_CONFIG pkg-config) 9 | IF(PKG_CONFIG) 10 | EXECUTE_PROCESS( 11 | COMMAND pkg-config --silence-errors --cflags libnl-tiny 12 | OUTPUT_VARIABLE NL_CFLAGS 13 | OUTPUT_STRIP_TRAILING_WHITESPACE) 14 | EXECUTE_PROCESS( 15 | COMMAND pkg-config --silence-errors --libs libnl-tiny 16 | OUTPUT_VARIABLE NL_LIBS 17 | OUTPUT_STRIP_TRAILING_WHITESPACE) 18 | ENDIF() 19 | ENDIF() 20 | 21 | CHECK_INCLUDE_FILES(pcap/pcap.h HAVE_PCAP_H) 22 | IF(NOT HAVE_PCAP_H) 23 | UNSET(HAVE_PCAP_H CACHE) 24 | MESSAGE(FATAL_ERROR "pcap/pcap.h is not found") 25 | ENDIF() 26 | 27 | SET(SOURCES main.c local_node.c node.c sta.c policy.c ubus.c remote.c parse.c netifd.c timeout.c event.c measurement.c band_steering.c) 28 | 29 | IF(NL_CFLAGS) 30 | ADD_DEFINITIONS(${NL_CFLAGS}) 31 | SET(SOURCES ${SOURCES} nl80211.c) 32 | ENDIF() 33 | 34 | ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations) 35 | 36 | FIND_LIBRARY(libjson NAMES json-c json) 37 | ADD_EXECUTABLE(usteerd ${SOURCES}) 38 | ADD_EXECUTABLE(fakeap fakeap.c timeout.c) 39 | 40 | TARGET_LINK_LIBRARIES(usteerd ubox ubus blobmsg_json 41 | ${LIBS_EXTRA} ${libjson} ${NL_LIBS}) 42 | TARGET_LINK_LIBRARIES(fakeap ubox ubus) 43 | 44 | ADD_EXECUTABLE(ap-monitor monitor.c parse.c) 45 | TARGET_LINK_LIBRARIES(ap-monitor ubox pcap blobmsg_json) 46 | 47 | SET(CMAKE_INSTALL_PREFIX /usr) 48 | 49 | INSTALL(TARGETS usteerd 50 | RUNTIME DESTINATION sbin 51 | ) 52 | -------------------------------------------------------------------------------- /timeout.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #ifndef __APMGR_TIMEOUT_H 21 | #define __APMGR_TIMEOUT_H 22 | 23 | #include 24 | #include 25 | 26 | struct usteer_timeout { 27 | struct avl_node node; 28 | }; 29 | 30 | struct usteer_timeout_queue { 31 | struct avl_tree tree; 32 | struct uloop_timeout timeout; 33 | void (*cb)(struct usteer_timeout_queue *q, struct usteer_timeout *t); 34 | }; 35 | 36 | static inline bool 37 | usteer_timeout_isset(struct usteer_timeout *t) 38 | { 39 | return t->node.list.prev != NULL; 40 | } 41 | 42 | void usteer_timeout_init(struct usteer_timeout_queue *q); 43 | void usteer_timeout_set(struct usteer_timeout_queue *q, struct usteer_timeout *t, 44 | int msecs); 45 | void usteer_timeout_cancel(struct usteer_timeout_queue *q, 46 | struct usteer_timeout *t); 47 | void usteer_timeout_flush(struct usteer_timeout_queue *q); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #ifndef __APMGR_UTILS_H 21 | #define __APMGR_UTILS_H 22 | 23 | #define MSG(_nr, _format, ...) debug_msg(MSG_##_nr, __func__, __LINE__, _format, ##__VA_ARGS__) 24 | #define MSG_CONT(_nr, _format, ...) debug_msg_cont(MSG_##_nr, _format, ##__VA_ARGS__) 25 | 26 | #define MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x" 27 | #define MAC_ADDR_DATA(_a) \ 28 | ((const uint8_t *)(_a))[0], \ 29 | ((const uint8_t *)(_a))[1], \ 30 | ((const uint8_t *)(_a))[2], \ 31 | ((const uint8_t *)(_a))[3], \ 32 | ((const uint8_t *)(_a))[4], \ 33 | ((const uint8_t *)(_a))[5] 34 | 35 | enum usteer_debug { 36 | MSG_FATAL, 37 | MSG_INFO, 38 | MSG_VERBOSE, 39 | MSG_DEBUG, 40 | MSG_NETWORK, 41 | MSG_DEBUG_ALL, 42 | }; 43 | 44 | extern void log_msg(char *msg); 45 | extern void debug_msg(int level, const char *func, int line, const char *format, ...); 46 | extern void debug_msg_cont(int level, const char *format, ...); 47 | 48 | #define __usteer_init __attribute__((constructor)) 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /remote.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #ifndef __APMGR_REMOTE_H 21 | #define __APMGR_REMOTE_H 22 | 23 | #include 24 | 25 | enum { 26 | APMSG_ID, 27 | APMSG_SEQ, 28 | APMSG_NODES, 29 | APMSG_HOST_INFO, 30 | __APMSG_MAX 31 | }; 32 | 33 | struct apmsg { 34 | uint32_t id; 35 | uint32_t seq; 36 | struct blob_attr *nodes; 37 | struct blob_attr *host_info; 38 | }; 39 | 40 | enum { 41 | APMSG_NODE_NAME, 42 | APMSG_NODE_FREQ, 43 | APMSG_NODE_N_ASSOC, 44 | APMSG_NODE_STATIONS, 45 | APMSG_NODE_NOISE, 46 | APMSG_NODE_LOAD, 47 | APMSG_NODE_SSID, 48 | APMSG_NODE_MAX_ASSOC, 49 | APMSG_NODE_RRM_NR, 50 | APMSG_NODE_NODE_INFO, 51 | APMSG_NODE_BSSID, 52 | APMSG_NODE_CHANNEL, 53 | APMSG_NODE_OP_CLASS, 54 | __APMSG_NODE_MAX 55 | }; 56 | 57 | struct apmsg_node { 58 | const char *name; 59 | const char *ssid; 60 | const char *bssid; 61 | int freq; 62 | int channel; 63 | int op_class; 64 | int n_assoc; 65 | int max_assoc; 66 | int noise; 67 | int load; 68 | struct blob_attr *stations; 69 | struct blob_attr *rrm_nr; 70 | struct blob_attr *node_info; 71 | }; 72 | 73 | enum { 74 | APMSG_STA_ADDR, 75 | APMSG_STA_SIGNAL, 76 | APMSG_STA_TIMEOUT, 77 | APMSG_STA_SEEN, 78 | APMSG_STA_CONNECTED, 79 | APMSG_STA_LAST_CONNECTED, 80 | __APMSG_STA_MAX 81 | }; 82 | 83 | struct apmsg_sta { 84 | uint8_t addr[6]; 85 | 86 | bool connected; 87 | int signal; 88 | int timeout; 89 | int seen; 90 | int last_connected; 91 | }; 92 | 93 | bool parse_apmsg(struct apmsg *msg, struct blob_attr *data); 94 | bool parse_apmsg_node(struct apmsg_node *msg, struct blob_attr *data); 95 | bool parse_apmsg_sta(struct apmsg_sta *msg, struct blob_attr *data); 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /node.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #ifndef __APMGR_NODE_H 21 | #define __APMGR_NODE_H 22 | 23 | #include "usteer.h" 24 | 25 | enum local_req_state { 26 | REQ_IDLE, 27 | REQ_CLIENTS, 28 | REQ_STATUS, 29 | REQ_RRM_SET_LIST, 30 | REQ_RRM_GET_OWN, 31 | __REQ_MAX 32 | }; 33 | 34 | struct usteer_local_node { 35 | struct usteer_node node; 36 | 37 | struct ubus_subscriber ev; 38 | struct uloop_timeout update; 39 | 40 | const char *iface; 41 | int ifindex; 42 | int wiphy; 43 | 44 | struct ubus_request req; 45 | struct uloop_timeout req_timer; 46 | int req_state; 47 | 48 | uint32_t obj_id; 49 | 50 | float load_ewma; 51 | int load_thr_count; 52 | 53 | uint64_t time, time_busy; 54 | 55 | struct kvlist node_info; 56 | 57 | struct uloop_timeout bss_tm_queries_timeout; 58 | struct list_head bss_tm_queries; 59 | 60 | int beacon_interval; 61 | 62 | uint16_t band_steering_interval; 63 | 64 | struct { 65 | bool present; 66 | struct uloop_timeout update; 67 | } nl80211; 68 | struct { 69 | struct ubus_request req; 70 | bool req_pending; 71 | bool status_complete; 72 | } netifd; 73 | 74 | unsigned int link_measurement_tries; 75 | }; 76 | 77 | struct interface; 78 | 79 | struct usteer_remote_host { 80 | struct avl_node avl; 81 | 82 | struct list_head nodes; 83 | struct blob_attr *host_info; 84 | char *addr; 85 | }; 86 | 87 | struct usteer_remote_node { 88 | struct list_head list; 89 | struct list_head host_list; 90 | const char *name; 91 | 92 | struct usteer_remote_host *host; 93 | struct usteer_node node; 94 | 95 | int check; 96 | }; 97 | 98 | extern struct avl_tree local_nodes; 99 | extern struct list_head remote_nodes; 100 | extern struct avl_tree remote_hosts; 101 | 102 | #define for_each_local_node(node) \ 103 | avl_for_each_element(&local_nodes, node, avl) \ 104 | if (!node->disabled) 105 | 106 | #define for_each_remote_node(rn) \ 107 | list_for_each_entry(rn, &remote_nodes, list) 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /band_steering.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2022 David Bauer 16 | */ 17 | 18 | #include "usteer.h" 19 | #include "node.h" 20 | 21 | void usteer_band_steering_sta_update(struct sta_info *si) 22 | { 23 | if (si->signal < usteer_snr_to_signal(si->node, config.band_steering_min_snr)) 24 | si->band_steering.below_snr = true; 25 | } 26 | 27 | bool usteer_band_steering_is_target(struct usteer_local_node *ln, struct usteer_node *node) 28 | { 29 | if (&ln->node == node) 30 | return false; 31 | 32 | if (strcmp(ln->node.ssid, node->ssid)) 33 | return false; 34 | 35 | if (node->freq < 4000) 36 | return false; 37 | 38 | if (!usteer_policy_node_below_max_assoc(node)) 39 | return false; 40 | 41 | /* ToDo: Skip nodes with active load-kick */ 42 | 43 | return true; 44 | } 45 | 46 | 47 | static bool usteer_band_steering_has_target_iface(struct usteer_local_node *ln) 48 | { 49 | struct usteer_node *node; 50 | 51 | for_each_local_node(node) { 52 | if (usteer_band_steering_is_target(ln, node)) 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | 59 | void usteer_band_steering_perform_steer(struct usteer_local_node *ln) 60 | { 61 | unsigned int min_count = DIV_ROUND_UP(config.band_steering_interval, config.local_sta_update); 62 | struct sta_info *si; 63 | 64 | if (!config.band_steering_interval) 65 | return; 66 | 67 | /* Band-Steering is only available on 2.4 GHz interfaces */ 68 | if (ln->node.freq > 4000) 69 | return; 70 | 71 | /* Check if we have an interface we can steer to */ 72 | if (!usteer_band_steering_has_target_iface(ln)) 73 | return; 74 | 75 | /* Only steer every interval */ 76 | if (ln->band_steering_interval < min_count) { 77 | ln->band_steering_interval++; 78 | return; 79 | } 80 | 81 | ln->band_steering_interval = 0; 82 | 83 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 84 | /* Check if client is eligable to be steerd */ 85 | if (!usteer_policy_can_perform_roam(si)) 86 | continue; 87 | 88 | /* Skip clients with insufficient SNR-state */ 89 | if (si->band_steering.below_snr) { 90 | si->band_steering.below_snr = false; 91 | continue; 92 | } 93 | 94 | if (si->bss_transition) 95 | usteer_ubus_band_steering_request(si); 96 | 97 | si->band_steering.below_snr = false; 98 | } 99 | } -------------------------------------------------------------------------------- /openwrt/usteer/files/etc/init.d/usteer: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # Copyright (C) 2013 OpenWrt.org 3 | 4 | START=50 5 | USE_PROCD=1 6 | 7 | NAME=usteer 8 | PROG=/sbin/usteerd 9 | 10 | . /lib/functions/network.sh 11 | . /usr/share/libubox/jshn.sh 12 | . /lib/functions.sh 13 | 14 | load_ifaces() { 15 | local network="$(uci get usteer.@usteer[-1].network)" 16 | for n in $network; do 17 | local device 18 | json_load "$(ifstatus $n)" 19 | json_get_var device l3_device 20 | echo -n "$device " 21 | done 22 | } 23 | 24 | _add_string() { 25 | json_add_string "" "$1" 26 | } 27 | 28 | uci_option_to_json_string_array() { 29 | local cfg="$1" 30 | local option="$2" 31 | 32 | json_add_array "$option" 33 | config_list_foreach "$cfg" "$option" _add_string 34 | json_close_array 35 | } 36 | 37 | uci_option_to_json_bool() { 38 | local cfg="$1" 39 | local option="$2" 40 | local val 41 | 42 | config_get_bool val "$cfg" $option 43 | [ -n "$val" ] && json_add_boolean $option $val 44 | } 45 | 46 | uci_option_to_json_string() { 47 | local cfg="$1" 48 | local option="$2" 49 | local val 50 | 51 | config_get val "$cfg" "$option" 52 | [ -n "$val" ] && json_add_string $option "$val" 53 | } 54 | 55 | uci_option_to_json() { 56 | local cfg="$1" 57 | local option="$2" 58 | local val 59 | 60 | config_get val "$cfg" $option 61 | [ -n "$val" ] && json_add_int $option $val 62 | } 63 | 64 | uci_usteer() { 65 | local cfg="$1" 66 | 67 | uci_option_to_json_bool "$cfg" syslog 68 | uci_option_to_json_bool "$cfg" ipv6 69 | uci_option_to_json_bool "$cfg" local_mode 70 | uci_option_to_json_bool "$cfg" load_kick_enabled 71 | uci_option_to_json_bool "$cfg" assoc_steering 72 | uci_option_to_json_string "$cfg" node_up_script 73 | uci_option_to_json_string_array "$cfg" ssid_list 74 | uci_option_to_json_string_array "$cfg" event_log_types 75 | 76 | for opt in \ 77 | debug_level \ 78 | sta_block_timeout local_sta_timeout local_sta_update \ 79 | max_neighbor_reports max_retry_band seen_policy_timeout \ 80 | measurement_report_timeout \ 81 | load_balancing_threshold band_steering_threshold \ 82 | remote_update_interval remote_node_timeout\ 83 | min_connect_snr min_snr min_snr_kick_delay signal_diff_threshold \ 84 | initial_connect_delay steer_reject_timeout roam_process_timeout\ 85 | roam_kick_delay roam_scan_tries roam_scan_timeout \ 86 | roam_scan_snr roam_scan_interval \ 87 | roam_trigger_snr roam_trigger_interval \ 88 | band_steering_interval band_steering_min_snr link_measurement_interval \ 89 | load_kick_threshold load_kick_delay load_kick_min_clients \ 90 | load_kick_reason_code 91 | do 92 | uci_option_to_json "$cfg" "$opt" 93 | done 94 | } 95 | 96 | 97 | load_config() { 98 | [ "$ENABLED" -gt 0 ] || return 99 | 100 | ubus -t 10 wait_for usteer 101 | 102 | json_init 103 | json_add_array interfaces 104 | for i in $(load_ifaces); do 105 | json_add_string "" "$i" 106 | done 107 | json_close_array 108 | 109 | config_load usteer 110 | config_foreach uci_usteer usteer 111 | 112 | ubus call usteer set_config "$(json_dump)" 113 | } 114 | 115 | reload_service() { 116 | start 117 | load_config 118 | } 119 | 120 | service_started() { 121 | load_config 122 | } 123 | 124 | service_triggers() { 125 | procd_add_reload_trigger usteer 126 | procd_add_raw_trigger "interface.*" 2000 /etc/init.d/usteer reload 127 | } 128 | 129 | start_service() 130 | { 131 | local network="$(uci -q get usteer.@usteer[-1].network)" 132 | ENABLED="$(uci -q get usteer.@usteer[-1].enabled)" 133 | ENABLED="${ENABLED:-1}" 134 | 135 | [ "$ENABLED" -gt 0 ] || return 136 | 137 | procd_open_instance 138 | procd_set_param command "$PROG" 139 | procd_close_instance 140 | } 141 | -------------------------------------------------------------------------------- /measurement.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2021 David Bauer 16 | */ 17 | 18 | #include "usteer.h" 19 | 20 | LIST_HEAD(measurements); 21 | static struct usteer_timeout_queue tq; 22 | 23 | int 24 | usteer_measurement_get_rssi(struct usteer_measurement_report *report) 25 | { 26 | /* Apple devices always set the RSNI to 0, while 27 | * it should be set to 255 in case RSNI is unavailable. 28 | * 29 | * For them, RCPI seems to be calculated as RCPI = 255 + (RSSI) 30 | */ 31 | 32 | if (!report->rsni) 33 | return report->rcpi - 255; 34 | 35 | return (report->rcpi / 2) - 110; 36 | } 37 | 38 | void 39 | usteer_measurement_report_node_cleanup(struct usteer_node *node) 40 | { 41 | struct usteer_measurement_report *mr, *tmp; 42 | 43 | list_for_each_entry_safe(mr, tmp, &node->measurements, node_list) 44 | usteer_measurement_report_del(mr); 45 | } 46 | 47 | void 48 | usteer_measurement_report_sta_cleanup(struct sta *sta) 49 | { 50 | struct usteer_measurement_report *mr, *tmp; 51 | 52 | list_for_each_entry_safe(mr, tmp, &sta->measurements, sta_list) 53 | usteer_measurement_report_del(mr); 54 | } 55 | 56 | struct usteer_measurement_report * 57 | usteer_measurement_report_get(struct sta *sta, struct usteer_node *node, bool create) 58 | { 59 | struct usteer_measurement_report *mr; 60 | 61 | list_for_each_entry(mr, &sta->measurements, sta_list) { 62 | if (mr->node == node) 63 | return mr; 64 | } 65 | 66 | if (!create) 67 | return NULL; 68 | 69 | mr = calloc(1, sizeof(*mr)); 70 | if (!mr) 71 | return NULL; 72 | 73 | /* Set node & add to nodes list */ 74 | mr->node = node; 75 | list_add(&mr->node_list, &node->measurements); 76 | 77 | /* Set sta & add to STAs list */ 78 | mr->sta = sta; 79 | list_add(&mr->sta_list, &sta->measurements); 80 | 81 | /* Add to Measurement list */ 82 | list_add(&mr->list, &measurements); 83 | 84 | /* Set measurement expiration */ 85 | usteer_timeout_set(&tq, &mr->timeout, config.measurement_report_timeout); 86 | 87 | return mr; 88 | } 89 | 90 | struct usteer_measurement_report * 91 | usteer_measurement_report_add(struct sta *sta, struct usteer_node *node, 92 | uint8_t rcpi, uint8_t rsni, uint64_t timestamp) 93 | { 94 | struct usteer_measurement_report *mr = usteer_measurement_report_get(sta, node, true); 95 | 96 | if (!mr) 97 | return NULL; 98 | 99 | mr->timestamp = timestamp; 100 | mr->rsni = rsni; 101 | mr->rcpi = rcpi; 102 | 103 | /* Reset timeout */ 104 | usteer_timeout_set(&tq, &mr->timeout, config.measurement_report_timeout); 105 | 106 | return mr; 107 | } 108 | 109 | void 110 | usteer_measurement_report_del(struct usteer_measurement_report *mr) 111 | { 112 | usteer_timeout_cancel(&tq, &mr->timeout); 113 | list_del(&mr->node_list); 114 | list_del(&mr->sta_list); 115 | list_del(&mr->list); 116 | free(mr); 117 | } 118 | 119 | static void 120 | usteer_measurement_timeout(struct usteer_timeout_queue *q, struct usteer_timeout *t) 121 | { 122 | struct usteer_measurement_report *mr = container_of(t, struct usteer_measurement_report, timeout); 123 | 124 | usteer_measurement_report_del(mr); 125 | } 126 | 127 | static void __usteer_init usteer_measurement_init(void) 128 | { 129 | usteer_timeout_init(&tq); 130 | tq.cb = usteer_measurement_timeout; 131 | } 132 | -------------------------------------------------------------------------------- /timeout.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "timeout.h" 25 | 26 | static int usteer_timeout_cmp(const void *k1, const void *k2, void *ptr) 27 | { 28 | uint32_t ref = (uint32_t) (intptr_t) ptr; 29 | int32_t t1 = (uint32_t) (intptr_t) k1 - ref; 30 | int32_t t2 = (uint32_t) (intptr_t) k2 - ref; 31 | 32 | if (t1 < t2) 33 | return -1; 34 | else if (t1 > t2) 35 | return 1; 36 | else 37 | return 0; 38 | } 39 | 40 | static int32_t usteer_timeout_delta(struct usteer_timeout *t, uint32_t time) 41 | { 42 | uint32_t val = (uint32_t) (intptr_t) t->node.key; 43 | return val - time; 44 | } 45 | 46 | static void usteer_timeout_recalc(struct usteer_timeout_queue *q, uint32_t time) 47 | { 48 | struct usteer_timeout *t; 49 | int32_t delta; 50 | 51 | if (avl_is_empty(&q->tree)) { 52 | uloop_timeout_cancel(&q->timeout); 53 | return; 54 | } 55 | 56 | t = avl_first_element(&q->tree, t, node); 57 | 58 | delta = usteer_timeout_delta(t, time); 59 | if (delta < 1) 60 | delta = 1; 61 | 62 | uloop_timeout_set(&q->timeout, delta); 63 | } 64 | 65 | static uint32_t ampgr_timeout_current_time(void) 66 | { 67 | struct timespec ts; 68 | uint32_t val; 69 | 70 | clock_gettime(CLOCK_MONOTONIC, &ts); 71 | val = ts.tv_sec * 1000; 72 | val += ts.tv_nsec / 1000000; 73 | 74 | return val; 75 | } 76 | 77 | static void usteer_timeout_cb(struct uloop_timeout *timeout) 78 | { 79 | struct usteer_timeout_queue *q; 80 | struct usteer_timeout *t, *tmp; 81 | bool found; 82 | uint32_t time; 83 | 84 | q = container_of(timeout, struct usteer_timeout_queue, timeout); 85 | do { 86 | found = false; 87 | time = ampgr_timeout_current_time(); 88 | 89 | avl_for_each_element_safe(&q->tree, t, node, tmp) { 90 | if (usteer_timeout_delta(t, time) > 0) 91 | break; 92 | 93 | usteer_timeout_cancel(q, t); 94 | if (q->cb) 95 | q->cb(q, t); 96 | found = true; 97 | } 98 | } while (found); 99 | 100 | usteer_timeout_recalc(q, time); 101 | } 102 | 103 | 104 | void usteer_timeout_init(struct usteer_timeout_queue *q) 105 | { 106 | avl_init(&q->tree, usteer_timeout_cmp, true, NULL); 107 | q->timeout.cb = usteer_timeout_cb; 108 | } 109 | 110 | static void __usteer_timeout_cancel(struct usteer_timeout_queue *q, 111 | struct usteer_timeout *t) 112 | { 113 | avl_delete(&q->tree, &t->node); 114 | } 115 | 116 | void usteer_timeout_set(struct usteer_timeout_queue *q, struct usteer_timeout *t, 117 | int msecs) 118 | { 119 | uint32_t time = ampgr_timeout_current_time(); 120 | uint32_t val = time + msecs; 121 | bool recalc = false; 122 | 123 | q->tree.cmp_ptr = (void *) (intptr_t) time; 124 | if (usteer_timeout_isset(t)) { 125 | if (avl_is_first(&q->tree, &t->node)) 126 | recalc = true; 127 | 128 | __usteer_timeout_cancel(q, t); 129 | } 130 | 131 | t->node.key = (void *) (intptr_t) val; 132 | avl_insert(&q->tree, &t->node); 133 | if (avl_is_first(&q->tree, &t->node)) 134 | recalc = true; 135 | 136 | if (recalc) 137 | usteer_timeout_recalc(q, time); 138 | } 139 | 140 | void usteer_timeout_cancel(struct usteer_timeout_queue *q, 141 | struct usteer_timeout *t) 142 | { 143 | if (!usteer_timeout_isset(t)) 144 | return; 145 | 146 | __usteer_timeout_cancel(q, t); 147 | memset(&t->node.list, 0, sizeof(t->node.list)); 148 | } 149 | 150 | void usteer_timeout_flush(struct usteer_timeout_queue *q) 151 | { 152 | struct usteer_timeout *t, *tmp; 153 | 154 | uloop_timeout_cancel(&q->timeout); 155 | avl_remove_all_elements(&q->tree, t, node, tmp) { 156 | memset(&t->node.list, 0, sizeof(t->node.list)); 157 | if (q->cb) 158 | q->cb(q, t); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /netifd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include "usteer.h" 21 | #include "node.h" 22 | 23 | static struct blob_buf b; 24 | 25 | static void 26 | netifd_parse_interface_config(struct usteer_local_node *ln, struct blob_attr *msg) 27 | { 28 | static const struct blobmsg_policy policy = { 29 | .name = "maxassoc", 30 | .type = BLOBMSG_TYPE_INT32, 31 | }; 32 | struct blob_attr *cur; 33 | int val = 0; 34 | 35 | blobmsg_parse(&policy, 1, &cur, blobmsg_data(msg), blobmsg_data_len(msg)); 36 | if (cur) 37 | val = blobmsg_get_u32(cur); 38 | 39 | ln->node.max_assoc = val; 40 | ln->netifd.status_complete = true; 41 | } 42 | 43 | static void 44 | netifd_parse_interface(struct usteer_local_node *ln, struct blob_attr *msg) 45 | { 46 | enum { 47 | N_IF_CONFIG, 48 | N_IF_NAME, 49 | __N_IF_MAX 50 | }; 51 | static const struct blobmsg_policy policy[__N_IF_MAX] = { 52 | [N_IF_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_TABLE }, 53 | [N_IF_NAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING }, 54 | }; 55 | struct blob_attr *tb[__N_IF_MAX]; 56 | 57 | if (blobmsg_type(msg) != BLOBMSG_TYPE_TABLE) 58 | return; 59 | 60 | blobmsg_parse(policy, __N_IF_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); 61 | if (!tb[N_IF_CONFIG] || !tb[N_IF_NAME]) 62 | return; 63 | 64 | if (strcmp(blobmsg_get_string(tb[N_IF_NAME]), ln->iface) != 0) 65 | return; 66 | 67 | netifd_parse_interface_config(ln, tb[N_IF_CONFIG]); 68 | } 69 | 70 | static void 71 | netifd_parse_radio(struct usteer_local_node *ln, struct blob_attr *msg) 72 | { 73 | static const struct blobmsg_policy policy = { 74 | .name = "interfaces", 75 | .type = BLOBMSG_TYPE_ARRAY, 76 | }; 77 | struct blob_attr *cur, *iface; 78 | int rem; 79 | 80 | if (blobmsg_type(msg) != BLOBMSG_TYPE_TABLE) 81 | return; 82 | 83 | blobmsg_parse(&policy, 1, &iface, blobmsg_data(msg), blobmsg_data_len(msg)); 84 | if (!iface) 85 | return; 86 | 87 | blobmsg_for_each_attr(cur, iface, rem) 88 | netifd_parse_interface(ln, cur); 89 | } 90 | 91 | static void 92 | netifd_status_cb(struct ubus_request *req, int type, struct blob_attr *msg) 93 | { 94 | struct usteer_local_node *ln; 95 | struct blob_attr *cur; 96 | int rem; 97 | 98 | ln = container_of(req, struct usteer_local_node, netifd.req); 99 | ln->netifd.req_pending = false; 100 | 101 | blobmsg_for_each_attr(cur, msg, rem) 102 | netifd_parse_radio(ln, cur); 103 | } 104 | 105 | static void netifd_update_node(struct usteer_node *node) 106 | { 107 | struct usteer_local_node *ln; 108 | uint32_t id; 109 | 110 | ln = container_of(node, struct usteer_local_node, node); 111 | if (ln->netifd.status_complete) 112 | return; 113 | 114 | if (ln->netifd.req_pending) 115 | ubus_abort_request(ubus_ctx, &ln->netifd.req); 116 | 117 | if (ubus_lookup_id(ubus_ctx, "network.wireless", &id)) 118 | return; 119 | 120 | blob_buf_init(&b, 0); 121 | ubus_invoke_async(ubus_ctx, id, "status", b.head, &ln->netifd.req); 122 | ln->netifd.req.data_cb = netifd_status_cb; 123 | ubus_complete_request_async(ubus_ctx, &ln->netifd.req); 124 | ln->netifd.req_pending = true; 125 | } 126 | 127 | static void netifd_init_node(struct usteer_node *node) 128 | { 129 | struct usteer_local_node *ln; 130 | 131 | ln = container_of(node, struct usteer_local_node, node); 132 | ln->netifd.status_complete = false; 133 | netifd_update_node(node); 134 | } 135 | 136 | static void netifd_free_node(struct usteer_node *node) 137 | { 138 | struct usteer_local_node *ln; 139 | 140 | ln = container_of(node, struct usteer_local_node, node); 141 | if (ln->netifd.req_pending) 142 | ubus_abort_request(ubus_ctx, &ln->netifd.req); 143 | } 144 | 145 | static struct usteer_node_handler netifd_handler = { 146 | .init_node = netifd_init_node, 147 | .update_node = netifd_update_node, 148 | .free_node = netifd_free_node, 149 | }; 150 | 151 | static void __usteer_init usteer_netifd_init(void) 152 | { 153 | list_add(&netifd_handler.list, &node_handlers); 154 | } 155 | -------------------------------------------------------------------------------- /node.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include "node.h" 21 | #include "usteer.h" 22 | 23 | struct usteer_remote_node *usteer_remote_node_by_bssid(uint8_t *bssid) { 24 | struct usteer_remote_node *rn; 25 | 26 | for_each_remote_node(rn) { 27 | if (!memcmp(rn->node.bssid, bssid, 6)) 28 | return rn; 29 | } 30 | 31 | return NULL; 32 | } 33 | 34 | struct usteer_node *usteer_node_by_bssid(uint8_t *bssid) { 35 | struct usteer_remote_node *rn; 36 | struct usteer_local_node *ln; 37 | 38 | rn = usteer_remote_node_by_bssid(bssid); 39 | if (rn) 40 | return &rn->node; 41 | 42 | ln = usteer_local_node_by_bssid(bssid); 43 | if (ln) 44 | return &ln->node; 45 | 46 | return NULL; 47 | } 48 | 49 | void usteer_node_set_blob(struct blob_attr **dest, struct blob_attr *val) 50 | { 51 | int new_len; 52 | int len; 53 | 54 | if (!val) { 55 | free(*dest); 56 | *dest = NULL; 57 | return; 58 | } 59 | 60 | len = *dest ? blob_pad_len(*dest) : 0; 61 | new_len = blob_pad_len(val); 62 | if (new_len != len) 63 | *dest = realloc(*dest, new_len); 64 | memcpy(*dest, val, new_len); 65 | } 66 | 67 | static struct usteer_node * 68 | usteer_node_higher_bssid(struct usteer_node *node1, struct usteer_node *node2) 69 | { 70 | int i; 71 | 72 | for (i = 0; i < 6; i++) { 73 | if (node1->bssid[i] == node2->bssid[i]) 74 | continue; 75 | if (node1->bssid[i] < node2->bssid[i]) 76 | return node2; 77 | 78 | break; 79 | } 80 | 81 | return node1; 82 | } 83 | 84 | static struct usteer_node * 85 | usteer_node_higher_roamability(struct usteer_node *node, struct usteer_node *ref) 86 | { 87 | uint64_t roamability_node, roamability_ref; 88 | 89 | roamability_node = ((uint64_t)(node->roam_events.source + node->roam_events.target)) * current_time / ((current_time - node->created) + 1); 90 | roamability_ref = ((uint64_t)(ref->roam_events.source + ref->roam_events.target)) * current_time / ((current_time - ref->created) + 1); 91 | 92 | if (roamability_node < roamability_ref) 93 | return ref; 94 | 95 | return node; 96 | } 97 | 98 | static struct usteer_node * 99 | usteer_node_better_neighbor(struct usteer_node *node, struct usteer_node *ref) 100 | { 101 | struct usteer_node *n1, *n2; 102 | 103 | /** 104 | * 1. Return one node if the other one is NULL 105 | * 2. Return the node with the higher roam events. 106 | * 3. Return the node with the higher BSSID. 107 | * 4. Return first method argument. 108 | */ 109 | 110 | if (!ref) 111 | return node; 112 | 113 | if (!node) 114 | return ref; 115 | 116 | n1 = usteer_node_higher_roamability(node, ref); 117 | n2 = usteer_node_higher_roamability(ref, node); 118 | if (n1 == n2) 119 | return n1; 120 | 121 | /* Identical roam interactions. Check BSSID */ 122 | n1 = usteer_node_higher_bssid(node, ref); 123 | n2 = usteer_node_higher_bssid(ref, node); 124 | if (n1 == n2) 125 | return n1; 126 | 127 | return node; 128 | } 129 | 130 | struct usteer_node * 131 | usteer_node_get_next_neighbor(struct usteer_node *current_node, struct usteer_node *last) 132 | { 133 | struct usteer_remote_node *rn; 134 | struct usteer_node *next = NULL, *n1, *n2; 135 | 136 | for_each_remote_node(rn) { 137 | if (next == &rn->node) 138 | continue; 139 | 140 | if (strcmp(current_node->ssid, rn->node.ssid)) 141 | continue; 142 | 143 | /* Skip nodes which can't handle additional STA */ 144 | if (rn->node.max_assoc && rn->node.n_assoc >= rn->node.max_assoc) 145 | continue; 146 | 147 | /* Check if this node is ranked lower than the last one */ 148 | n1 = usteer_node_better_neighbor(last, &rn->node); 149 | n2 = usteer_node_better_neighbor(&rn->node, last); 150 | if (n1 != n2) { 151 | /* Identical rank. Skip. */ 152 | continue; 153 | } else if (last && n1 == &rn->node) { 154 | /* Candidate rank is higher than the last neighbor. Skip. */ 155 | continue; 156 | } 157 | 158 | /* Check with current next candidate */ 159 | n1 = usteer_node_better_neighbor(next, &rn->node); 160 | n2 = usteer_node_better_neighbor(&rn->node, next); 161 | if (n1 != n2) { 162 | /* Identical rank. Skip. */ 163 | continue; 164 | } else if (n1 != &rn->node) { 165 | /* Next candidate ranked higher. */ 166 | continue; 167 | } 168 | 169 | next = n1; 170 | } 171 | 172 | return next; 173 | } 174 | -------------------------------------------------------------------------------- /openwrt/usteer/files/etc/config/usteer: -------------------------------------------------------------------------------- 1 | config usteer 2 | # The network interface for inter-AP communication 3 | option 'network' 'lan' 4 | 5 | # Log messages to syslog (0/1) 6 | option 'syslog' '1' 7 | 8 | # Disable network communication (0/1) 9 | option local_mode '0' 10 | 11 | # Use IPv6 for remote exchange 12 | option 'ipv6' '0' 13 | 14 | # Minimum level of logged messages 15 | # 0 = fatal 16 | # 1 = info 17 | # 2 = verbose 18 | # 3 = some debug messages 19 | # 4 = network packet information 20 | # 5 = all debug messages 21 | option 'debug_level' '2' 22 | 23 | # Maximum number of neighbor reports set for a node 24 | #option max_neighbor_reports 8 25 | 26 | # Maximum amount of time (ms) a station may be blocked due to policy decisions 27 | #option sta_block_timeout 30000 28 | 29 | # Maximum amount of time (ms) a local unconnected station is tracked 30 | #option local_sta_timeout 120000 31 | 32 | # Maximum amount of time (ms) a measurement report is stored 33 | #option measurement_report_timeout 120000 34 | 35 | # Local station information update interval (ms) 36 | #option local_sta_update 1000 37 | 38 | # Maximum number of consecutive times a station may be blocked by policy 39 | #option max_retry_band 5 40 | 41 | # Maximum idle time of a station entry (ms) to be considered for policy decisions 42 | #option seen_policy_timeout 30000 43 | 44 | # Minimum number of stations delta between APs before load balancing policy is active 45 | #option load_balancing_threshold 0 46 | 47 | # Minimum number of stations delta between bands before band steering policy is active 48 | #option band_steering_threshold 5 49 | 50 | # Interval (ms) between sending state updates to other APs 51 | #option remote_update_interval 1000 52 | 53 | # Number of remote update intervals after which a remote-node is deleted 54 | #option remote_node_timeout 10 55 | 56 | # Allow rejecting assoc requests for steering purposes (0/1) 57 | #option assoc_steering 0 58 | 59 | # Allow ignoring probe requests for steering purposes (0/1) 60 | #option probe_steering 0 61 | 62 | # Minimum signal-to-noise ratio or signal level (dBm) to allow connections 63 | #option min_connect_snr 0 64 | 65 | # Minimum signal-to-noise ratio or signal level (dBm) to remain connected 66 | #option min_snr 0 67 | 68 | # Timeout after which a station with snr < min_snr will be kicked 69 | #option min_snr_kick_delay 5000 70 | 71 | # Timeout (ms) for which a client will not be steered after rejecting a BSS-transition-request 72 | #option steer_reject_timeout 60000 73 | 74 | # Timeout (in ms) after which a association following a disassociation is not seen 75 | # as a roam 76 | #option roam_process_timeout 5000 77 | 78 | # Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger 79 | # client scans for roaming 80 | #option roam_scan_snr 0 81 | 82 | # Maximum number of client roaming scan trigger attempts 83 | #option roam_scan_tries 3 84 | 85 | # Retry scanning when roam_scan_tries is exceeded after this timeout (in ms) 86 | # In case this option is set to 0, the client is kicked instead 87 | #option roam_scan_timeout 0 88 | 89 | # Minimum time (ms) between client roaming scan trigger attempts 90 | #option roam_scan_interval 10000 91 | 92 | # Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger 93 | # forced client roaming 94 | #option roam_trigger_snr 0 95 | 96 | # Minimum time (ms) between client roaming trigger attempts 97 | #option roam_trigger_interval 60000 98 | 99 | # Timeout (ms) for client roam requests. usteer will kick the client after this times out. 100 | #option roam_kick_delay 10000 101 | 102 | # Minimum signal strength difference until AP steering policy is active 103 | #option signal_diff_threshold 0 104 | 105 | # Initial delay (ms) before responding to probe requests (to allow other APs to see packets as well) 106 | #option initial_connect_delay 0 107 | 108 | # Enable kicking client on excessive channel load (0/1) 109 | #option load_kick_enabled 0 110 | 111 | # Minimum channel load (%) before kicking clients 112 | #option load_kick_threshold 75 113 | 114 | # Minimum amount of time (ms) that channel load is above threshold before starting to kick clients 115 | #option load_kick_delay 10000 116 | 117 | # Minimum number of connected clients before kicking based on channel load 118 | #option load_kick_min_clients 10 119 | 120 | # Reason code on client kick based on channel load (default: WLAN_REASON_DISASSOC_AP_BUSY) 121 | #option load_kick_reason_code 5 122 | 123 | # Attempting to steer clients to a higher frequency-band every n ms. 124 | # A value of 0 disabled band-steering. 125 | #option band_steering_interval 120000 126 | 127 | # Minimal SNR or absolute signal a device has to maintain over band_steering_interval to be 128 | # steered to a higher frequency band 129 | #option band_steering_min_snr -60 130 | 131 | # Interval (ms) the device is sent a link-measurement request to help assess 132 | # the bi-directional link quality. Setting the interval to 0 disables link-measurements. 133 | #option link_measurement_interval 30000 134 | 135 | # Script to run after bringing up a node 136 | #option node_up_script '' 137 | 138 | # Message types to include in log 139 | # Available types: 140 | # - probe_req_accept 141 | # - probe_req_deny 142 | # - auth_req_accept 143 | # - auth_req_deny 144 | # - assoc_req_accept 145 | # - assoc_req_deny 146 | # - load_kick_trigger 147 | # - load_kick_reset 148 | # - load_kick_min_clients 149 | # - load_kick_no_client 150 | # - load_kick_client 151 | # - signal_kick 152 | #list event_log_types '' 153 | 154 | # List of SSIDs to enable steering on 155 | #list ssid_list '' 156 | -------------------------------------------------------------------------------- /parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include "usteer.h" 21 | #include "remote.h" 22 | 23 | bool parse_apmsg(struct apmsg *msg, struct blob_attr *data) 24 | { 25 | static const struct blob_attr_info policy[__APMSG_MAX] = { 26 | [APMSG_ID] = { .type = BLOB_ATTR_INT32 }, 27 | [APMSG_SEQ] = { .type = BLOB_ATTR_INT32 }, 28 | [APMSG_NODES] = { .type = BLOB_ATTR_NESTED }, 29 | [APMSG_HOST_INFO] = { .type = BLOB_ATTR_NESTED }, 30 | }; 31 | struct blob_attr *tb[__APMSG_MAX]; 32 | 33 | blob_parse(data, tb, policy, __APMSG_MAX); 34 | if (!tb[APMSG_ID] || !tb[APMSG_SEQ] || !tb[APMSG_NODES]) 35 | return false; 36 | 37 | msg->id = blob_get_int32(tb[APMSG_ID]); 38 | msg->seq = blob_get_int32(tb[APMSG_SEQ]); 39 | msg->nodes = tb[APMSG_NODES]; 40 | msg->host_info = tb[APMSG_HOST_INFO]; 41 | 42 | return true; 43 | } 44 | 45 | static int 46 | get_int32(struct blob_attr *attr) 47 | { 48 | if (!attr) 49 | return 0; 50 | 51 | return blob_get_int32(attr); 52 | } 53 | 54 | bool parse_apmsg_node(struct apmsg_node *msg, struct blob_attr *data) 55 | { 56 | static const struct blob_attr_info policy[__APMSG_NODE_MAX] = { 57 | [APMSG_NODE_NAME] = { .type = BLOB_ATTR_STRING }, 58 | [APMSG_NODE_BSSID] = { .type = BLOB_ATTR_BINARY }, 59 | [APMSG_NODE_FREQ] = { .type = BLOB_ATTR_INT32 }, 60 | [APMSG_NODE_N_ASSOC] = { .type = BLOB_ATTR_INT32 }, 61 | [APMSG_NODE_MAX_ASSOC] = { .type = BLOB_ATTR_INT32 }, 62 | [APMSG_NODE_STATIONS] = { .type = BLOB_ATTR_NESTED }, 63 | [APMSG_NODE_NOISE] = { .type = BLOB_ATTR_INT32 }, 64 | [APMSG_NODE_LOAD] = { .type = BLOB_ATTR_INT32 }, 65 | [APMSG_NODE_RRM_NR] = { .type = BLOB_ATTR_NESTED }, 66 | [APMSG_NODE_NODE_INFO] = { .type = BLOB_ATTR_NESTED }, 67 | [APMSG_NODE_CHANNEL] = { .type = BLOB_ATTR_INT32 }, 68 | [APMSG_NODE_OP_CLASS] = { .type = BLOB_ATTR_INT32 }, 69 | }; 70 | struct blob_attr *tb[__APMSG_NODE_MAX]; 71 | struct blob_attr *cur; 72 | 73 | blob_parse(data, tb, policy, __APMSG_NODE_MAX); 74 | if (!tb[APMSG_NODE_NAME] || 75 | !tb[APMSG_NODE_BSSID] || 76 | blob_len(tb[APMSG_NODE_BSSID]) != 6 || 77 | !tb[APMSG_NODE_FREQ] || 78 | !tb[APMSG_NODE_N_ASSOC] || 79 | !tb[APMSG_NODE_STATIONS] || 80 | !tb[APMSG_NODE_SSID]) 81 | return false; 82 | 83 | msg->name = blob_data(tb[APMSG_NODE_NAME]); 84 | msg->n_assoc = blob_get_int32(tb[APMSG_NODE_N_ASSOC]); 85 | msg->freq = blob_get_int32(tb[APMSG_NODE_FREQ]); 86 | msg->stations = tb[APMSG_NODE_STATIONS]; 87 | msg->ssid = blob_data(tb[APMSG_NODE_SSID]); 88 | msg->bssid = blob_data(tb[APMSG_NODE_BSSID]); 89 | 90 | msg->noise = get_int32(tb[APMSG_NODE_NOISE]); 91 | msg->load = get_int32(tb[APMSG_NODE_LOAD]); 92 | msg->max_assoc = get_int32(tb[APMSG_NODE_MAX_ASSOC]); 93 | msg->rrm_nr = NULL; 94 | 95 | if (tb[APMSG_NODE_CHANNEL] && tb[APMSG_NODE_OP_CLASS]) { 96 | msg->channel = blob_get_int32(tb[APMSG_NODE_CHANNEL]); 97 | msg->op_class = blob_get_int32(tb[APMSG_NODE_OP_CLASS]); 98 | } 99 | 100 | cur = tb[APMSG_NODE_RRM_NR]; 101 | if (cur && blob_len(cur) >= sizeof(struct blob_attr) && 102 | blob_len(cur) >= blob_pad_len(blob_data(cur))) { 103 | int rem; 104 | 105 | msg->rrm_nr = blob_data(cur); 106 | 107 | blobmsg_for_each_attr(cur, msg->rrm_nr, rem) { 108 | if (blobmsg_check_attr(cur, false)) 109 | continue; 110 | if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING) 111 | continue; 112 | msg->rrm_nr = NULL; 113 | break; 114 | } 115 | 116 | if (msg->rrm_nr && 117 | blobmsg_type(msg->rrm_nr) != BLOBMSG_TYPE_ARRAY) 118 | msg->rrm_nr = NULL; 119 | } 120 | 121 | msg->node_info = tb[APMSG_NODE_NODE_INFO]; 122 | 123 | return true; 124 | } 125 | 126 | bool parse_apmsg_sta(struct apmsg_sta *msg, struct blob_attr *data) 127 | { 128 | static const struct blob_attr_info policy[__APMSG_STA_MAX] = { 129 | [APMSG_STA_ADDR] = { .type = BLOB_ATTR_BINARY }, 130 | [APMSG_STA_SIGNAL] = { .type = BLOB_ATTR_INT32 }, 131 | [APMSG_STA_SEEN] = { .type = BLOB_ATTR_INT32 }, 132 | [APMSG_STA_TIMEOUT] = { .type = BLOB_ATTR_INT32 }, 133 | [APMSG_STA_CONNECTED] = { .type = BLOB_ATTR_INT8 }, 134 | [APMSG_STA_LAST_CONNECTED] = { .type = BLOB_ATTR_INT32 }, 135 | }; 136 | struct blob_attr *tb[__APMSG_STA_MAX]; 137 | 138 | blob_parse(data, tb, policy, __APMSG_STA_MAX); 139 | if (!tb[APMSG_STA_ADDR] || 140 | !tb[APMSG_STA_SIGNAL] || 141 | !tb[APMSG_STA_SEEN] || 142 | !tb[APMSG_STA_TIMEOUT] || 143 | !tb[APMSG_STA_CONNECTED] || 144 | !tb[APMSG_STA_LAST_CONNECTED]) 145 | return false; 146 | 147 | if (blob_len(tb[APMSG_STA_ADDR]) != sizeof(msg->addr)) 148 | return false; 149 | 150 | memcpy(msg->addr, blob_data(tb[APMSG_STA_ADDR]), sizeof(msg->addr)); 151 | msg->signal = blob_get_int32(tb[APMSG_STA_SIGNAL]); 152 | msg->seen = blob_get_int32(tb[APMSG_STA_SEEN]); 153 | msg->timeout = blob_get_int32(tb[APMSG_STA_TIMEOUT]); 154 | msg->connected = blob_get_int8(tb[APMSG_STA_CONNECTED]); 155 | msg->last_connected = blob_get_int32(tb[APMSG_STA_LAST_CONNECTED]); 156 | 157 | return true; 158 | } 159 | -------------------------------------------------------------------------------- /monitor.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | 28 | #include "usteer.h" 29 | #include "remote.h" 30 | 31 | static pcap_t *pcap; 32 | static int pkt_offset; 33 | 34 | /* IP header */ 35 | struct ip_header { 36 | uint8_t ip_vhl; /* version << 4 | header length >> 2 */ 37 | uint8_t ip_tos; /* type of service */ 38 | uint16_t ip_len; /* total length */ 39 | uint16_t ip_id; /* identification */ 40 | uint16_t ip_off; /* fragment offset field */ 41 | #define IP_RF 0x8000 /* reserved fragment flag */ 42 | #define IP_DF 0x4000 /* dont fragment flag */ 43 | #define IP_MF 0x2000 /* more fragments flag */ 44 | #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ 45 | uint8_t ip_ttl; /* time to live */ 46 | uint8_t ip_p; /* protocol */ 47 | uint16_t ip_sum; /* checksum */ 48 | struct in_addr ip_src, ip_dst; /* source and dest address */ 49 | }; 50 | #define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) 51 | #define IP_V(ip) (((ip)->ip_vhl) >> 4) 52 | 53 | struct udp_header { 54 | uint16_t uh_sport; /* source port */ 55 | uint16_t uh_dport; /* destination port */ 56 | uint16_t uh_ulen; /* udp length */ 57 | uint16_t uh_sum; /* udp checksum */ 58 | }; 59 | 60 | 61 | static void 62 | decode_sta(struct blob_attr *data) 63 | { 64 | struct apmsg_sta msg; 65 | 66 | if (!parse_apmsg_sta(&msg, data)) 67 | return; 68 | 69 | fprintf(stderr, "\t\tSta "MAC_ADDR_FMT" signal=%d connected=%d timeout=%d\n", 70 | MAC_ADDR_DATA(msg.addr), msg.signal, msg.connected, msg.timeout); 71 | } 72 | 73 | static void 74 | decode_node(struct blob_attr *data) 75 | { 76 | struct apmsg_node msg; 77 | struct blob_attr *cur; 78 | int rem; 79 | 80 | if (!parse_apmsg_node(&msg, data)) 81 | return; 82 | 83 | fprintf(stderr, "\tNode %s, freq=%d, n_assoc=%d, noise=%d load=%d max_assoc=%d\n", 84 | msg.name, msg.freq, msg.n_assoc, msg.noise, msg.load, msg.max_assoc); 85 | if (msg.rrm_nr) { 86 | fprintf(stderr, "\t\tRRM:"); 87 | blobmsg_for_each_attr(cur, msg.rrm_nr, rem) { 88 | if (!blobmsg_check_attr(cur, false)) 89 | continue; 90 | if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) 91 | continue; 92 | fprintf(stderr, " %s", blobmsg_get_string(cur)); 93 | } 94 | fprintf(stderr, "\n"); 95 | } 96 | 97 | if (msg.node_info) { 98 | char *data = blobmsg_format_json(msg.node_info, true); 99 | fprintf(stderr, "\t\tNode info: %s\n", data); 100 | free(data); 101 | } 102 | 103 | blob_for_each_attr(cur, msg.stations, rem) 104 | decode_sta(cur); 105 | } 106 | 107 | static void 108 | decode_packet(struct blob_attr *data) 109 | { 110 | struct apmsg msg; 111 | struct blob_attr *cur; 112 | int rem; 113 | 114 | if (!parse_apmsg(&msg, data)) { 115 | fprintf(stderr, "missing fields\n"); 116 | return; 117 | } 118 | 119 | fprintf(stderr, "id=%08x, seq=%d\n", msg.id, msg.seq); 120 | if (msg.host_info) { 121 | char *data = blobmsg_format_json(msg.host_info, true); 122 | fprintf(stderr, "\tHost info: %s\n", data); 123 | free(data); 124 | } 125 | 126 | blob_for_each_attr(cur, msg.nodes, rem) 127 | decode_node(cur); 128 | } 129 | 130 | static void 131 | recv_packet(unsigned char *user, const struct pcap_pkthdr *hdr, 132 | const unsigned char *packet) 133 | { 134 | char addr[INET_ADDRSTRLEN]; 135 | struct ip_header *ip; 136 | struct udp_header *uh; 137 | struct blob_attr *data; 138 | int len = hdr->caplen; 139 | int hdrlen; 140 | 141 | len -= pkt_offset; 142 | packet += pkt_offset; 143 | ip = (void *) packet; 144 | 145 | hdrlen = IP_HL(ip) * 4; 146 | if (hdrlen < 20 || hdrlen >= len) 147 | return; 148 | 149 | len -= hdrlen; 150 | packet += hdrlen; 151 | 152 | inet_ntop(AF_INET, &ip->ip_src, addr, sizeof(addr)); 153 | 154 | hdrlen = sizeof(*uh); 155 | if (len <= hdrlen) 156 | return; 157 | 158 | uh = (void *) packet; 159 | packet += hdrlen; 160 | len -= hdrlen; 161 | 162 | if (uh->uh_dport != htons(APMGR_PORT)) 163 | return; 164 | 165 | data = (void *) packet; 166 | 167 | fprintf(stderr, "[%s]: len=%d ", addr, len); 168 | 169 | if (len != blob_pad_len(data)) { 170 | fprintf(stderr, "invalid data\n"); 171 | return; 172 | } 173 | 174 | decode_packet(data); 175 | } 176 | 177 | int main(int argc, char **argv) 178 | { 179 | static char errbuf[PCAP_ERRBUF_SIZE]; 180 | struct bpf_program fp; 181 | 182 | if (argc != 2) { 183 | fprintf(stderr, "Usage: %s \n", argv[0]); 184 | return 1; 185 | } 186 | 187 | pcap = pcap_open_live(argv[1], APMGR_BUFLEN, 1, 1000, errbuf); 188 | if (!pcap) { 189 | fprintf(stderr, "Failed to open interface %s: %s\n", argv[1], errbuf); 190 | return 1; 191 | } 192 | 193 | pcap_compile(pcap, &fp, "port "APMGR_PORT_STR, 1, PCAP_NETMASK_UNKNOWN); 194 | pcap_setfilter(pcap, &fp); 195 | 196 | switch (pcap_datalink(pcap)) { 197 | case DLT_EN10MB: 198 | pkt_offset = 14; 199 | break; 200 | case DLT_RAW: 201 | pkt_offset = 0; 202 | break; 203 | default: 204 | fprintf(stderr, "Invalid link type\n"); 205 | return -1; 206 | } 207 | 208 | pcap_loop(pcap, 0, recv_packet, NULL); 209 | pcap_close(pcap); 210 | 211 | return 0; 212 | } 213 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "usteer.h" 27 | #include "event.h" 28 | #include "node.h" 29 | 30 | struct ubus_context *ubus_ctx; 31 | struct usteer_config config = {}; 32 | struct blob_attr *host_info_blob; 33 | uint64_t current_time; 34 | static int dump_time; 35 | 36 | LIST_HEAD(node_handlers); 37 | 38 | const char * const event_types[__EVENT_TYPE_MAX] = { 39 | [EVENT_TYPE_PROBE] = "probe", 40 | [EVENT_TYPE_AUTH] = "auth", 41 | [EVENT_TYPE_ASSOC] = "assoc", 42 | }; 43 | 44 | void log_msg(char *msg) 45 | { 46 | if (config.syslog) 47 | syslog(LOG_INFO, "%s\n", msg); 48 | else 49 | fprintf(stderr, "%s\n", msg); 50 | } 51 | 52 | void debug_msg(int level, const char *func, int line, const char *format, ...) 53 | { 54 | va_list ap; 55 | 56 | if (config.debug_level < level) 57 | return; 58 | 59 | if (!config.syslog) 60 | fprintf(stderr, "[%s:%d] ", func, line); 61 | 62 | va_start(ap, format); 63 | if (config.syslog) 64 | vsyslog(level >= MSG_DEBUG ? LOG_DEBUG : LOG_INFO, format, ap); 65 | else 66 | vfprintf(stderr, format, ap); 67 | va_end(ap); 68 | 69 | } 70 | 71 | void debug_msg_cont(int level, const char *format, ...) 72 | { 73 | va_list ap; 74 | 75 | if (config.debug_level < level) 76 | return; 77 | 78 | va_start(ap, format); 79 | vfprintf(stderr, format, ap); 80 | va_end(ap); 81 | } 82 | 83 | void usteer_init_defaults(void) 84 | { 85 | memset(&config, 0, sizeof(config)); 86 | 87 | config.sta_block_timeout = 30 * 1000; 88 | config.local_sta_timeout = 120 * 1000; 89 | config.measurement_report_timeout = 120 * 1000; 90 | config.local_sta_update = 1 * 1000; 91 | config.max_retry_band = 5; 92 | config.max_neighbor_reports = 8; 93 | config.seen_policy_timeout = 30 * 1000; 94 | config.band_steering_threshold = 5; 95 | config.load_balancing_threshold = 0; 96 | config.remote_update_interval = 1000; 97 | config.initial_connect_delay = 0; 98 | config.remote_node_timeout = 10; 99 | 100 | config.steer_reject_timeout = 60000; 101 | 102 | config.band_steering_interval = 120000; 103 | config.band_steering_min_snr = -60; 104 | 105 | config.link_measurement_interval = 30000; 106 | 107 | config.probe_steering = 0; 108 | 109 | config.roam_kick_delay = 10000; 110 | config.roam_process_timeout = 5 * 1000; 111 | config.roam_scan_tries = 3; 112 | config.roam_scan_timeout = 0; 113 | config.roam_scan_interval = 10 * 1000; 114 | config.roam_trigger_interval = 60 * 1000; 115 | 116 | config.min_snr_kick_delay = 5 * 1000; 117 | 118 | config.load_kick_enabled = false; 119 | config.load_kick_threshold = 75; 120 | config.load_kick_delay = 10 * 1000; 121 | config.load_kick_min_clients = 10; 122 | config.load_kick_reason_code = 5; /* WLAN_REASON_DISASSOC_AP_BUSY */ 123 | 124 | config.debug_level = MSG_FATAL; 125 | } 126 | 127 | void usteer_update_time(void) 128 | { 129 | struct timespec ts; 130 | 131 | clock_gettime(CLOCK_MONOTONIC, &ts); 132 | current_time = (uint64_t) ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 133 | } 134 | 135 | static int usage(const char *prog) 136 | { 137 | fprintf(stderr, "Usage: %s [options]\n" 138 | "Options:\n" 139 | " -v: Increase debug level (repeat for more messages):\n" 140 | " 1: info messages\n" 141 | " 2: debug messages\n" 142 | " 3: verbose debug messages\n" 143 | " 4: include network messages\n" 144 | " 5: include extra testing messages\n" 145 | " -i : Connect to other instances on interface \n" 146 | " -s: Output log messages via syslog instead of stderr\n" 147 | " -D : Do not daemonize, wait for seconds and print\n" 148 | " remote hosts and nodes\n" 149 | "\n", prog); 150 | return 1; 151 | } 152 | 153 | static void 154 | usteer_dump_timeout(struct uloop_timeout *t) 155 | { 156 | struct usteer_remote_host *host; 157 | struct usteer_remote_node *rn; 158 | struct blob_buf b = {}; 159 | char *str; 160 | void *c; 161 | 162 | blob_buf_init(&b, 0); 163 | 164 | c = blobmsg_open_table(&b, "hosts"); 165 | avl_for_each_element(&remote_hosts, host, avl) 166 | usteer_dump_host(&b, host); 167 | blobmsg_close_table(&b, c); 168 | 169 | c = blobmsg_open_table(&b, "nodes"); 170 | for_each_remote_node(rn) 171 | usteer_dump_node(&b, &rn->node); 172 | blobmsg_close_table(&b, c); 173 | 174 | str = blobmsg_format_json(b.head, true); 175 | blob_buf_free(&b); 176 | 177 | puts(str); 178 | free(str); 179 | 180 | uloop_end(); 181 | } 182 | 183 | int main(int argc, char **argv) 184 | { 185 | struct uloop_timeout dump_timer; 186 | int ch; 187 | 188 | usteer_init_defaults(); 189 | 190 | while ((ch = getopt(argc, argv, "D:i:sv")) != -1) { 191 | switch(ch) { 192 | case 'v': 193 | config.debug_level++; 194 | break; 195 | case 's': 196 | config.syslog = true; 197 | break; 198 | case 'i': 199 | usteer_interface_add(optarg); 200 | break; 201 | case 'D': 202 | dump_time = atoi(optarg); 203 | break; 204 | default: 205 | return usage(argv[0]); 206 | } 207 | } 208 | 209 | openlog("usteer", 0, LOG_USER); 210 | 211 | config_set_event_log_types(NULL); 212 | usteer_update_time(); 213 | uloop_init(); 214 | 215 | ubus_ctx = ubus_connect(NULL); 216 | if (!ubus_ctx) { 217 | fprintf(stderr, "Failed to connect to ubus\n"); 218 | return -1; 219 | } 220 | 221 | ubus_add_uloop(ubus_ctx); 222 | if (dump_time) { 223 | dump_timer.cb = usteer_dump_timeout; 224 | uloop_timeout_set(&dump_timer, dump_time * 1000); 225 | } else { 226 | usteer_ubus_init(ubus_ctx); 227 | usteer_local_nodes_init(ubus_ctx); 228 | } 229 | uloop_run(); 230 | 231 | uloop_done(); 232 | return 0; 233 | } 234 | -------------------------------------------------------------------------------- /sta.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include "usteer.h" 21 | 22 | static int 23 | avl_macaddr_cmp(const void *k1, const void *k2, void *ptr) 24 | { 25 | return memcmp(k1, k2, 6); 26 | } 27 | 28 | AVL_TREE(stations, avl_macaddr_cmp, false, NULL); 29 | static struct usteer_timeout_queue tq; 30 | 31 | static void 32 | usteer_sta_del(struct sta *sta) 33 | { 34 | MSG(DEBUG, "Delete station " MAC_ADDR_FMT "\n", 35 | MAC_ADDR_DATA(sta->addr)); 36 | 37 | avl_delete(&stations, &sta->avl); 38 | usteer_measurement_report_sta_cleanup(sta); 39 | free(sta); 40 | } 41 | 42 | static void 43 | usteer_sta_info_del(struct sta_info *si) 44 | { 45 | struct sta *sta = si->sta; 46 | 47 | MSG(DEBUG, "Delete station " MAC_ADDR_FMT " entry for node %s\n", 48 | MAC_ADDR_DATA(sta->addr), usteer_node_name(si->node)); 49 | 50 | usteer_timeout_cancel(&tq, &si->timeout); 51 | list_del(&si->list); 52 | list_del(&si->node_list); 53 | free(si); 54 | 55 | if (list_empty(&sta->nodes)) 56 | usteer_sta_del(sta); 57 | } 58 | 59 | void 60 | usteer_sta_node_cleanup(struct usteer_node *node) 61 | { 62 | struct sta_info *si, *tmp; 63 | 64 | free(node->rrm_nr); 65 | node->rrm_nr = NULL; 66 | 67 | list_for_each_entry_safe(si, tmp, &node->sta_info, node_list) 68 | usteer_sta_info_del(si); 69 | } 70 | 71 | static void 72 | usteer_sta_info_timeout(struct usteer_timeout_queue *q, struct usteer_timeout *t) 73 | { 74 | struct sta_info *si = container_of(t, struct sta_info, timeout); 75 | 76 | usteer_sta_info_del(si); 77 | } 78 | 79 | struct sta_info * 80 | usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create) 81 | { 82 | struct sta_info *si; 83 | 84 | list_for_each_entry(si, &sta->nodes, list) { 85 | if (si->node != node) 86 | continue; 87 | 88 | if (create) 89 | *create = false; 90 | 91 | return si; 92 | } 93 | 94 | if (!create) 95 | return NULL; 96 | 97 | MSG(DEBUG, "Create station " MAC_ADDR_FMT " entry for node %s\n", 98 | MAC_ADDR_DATA(sta->addr), usteer_node_name(node)); 99 | 100 | si = calloc(1, sizeof(*si)); 101 | si->node = node; 102 | si->sta = sta; 103 | list_add(&si->list, &sta->nodes); 104 | list_add(&si->node_list, &node->sta_info); 105 | si->created = current_time; 106 | *create = true; 107 | 108 | /* Node is by default not connected. */ 109 | usteer_sta_disconnected(si); 110 | 111 | return si; 112 | } 113 | 114 | 115 | void 116 | usteer_sta_info_update_timeout(struct sta_info *si, int timeout) 117 | { 118 | if (si->connected == STA_CONNECTED) 119 | usteer_timeout_cancel(&tq, &si->timeout); 120 | else if (timeout > 0) 121 | usteer_timeout_set(&tq, &si->timeout, timeout); 122 | else 123 | usteer_sta_info_del(si); 124 | } 125 | 126 | struct sta * 127 | usteer_sta_get(const uint8_t *addr, bool create) 128 | { 129 | struct sta *sta; 130 | 131 | sta = avl_find_element(&stations, addr, sta, avl); 132 | if (sta) 133 | return sta; 134 | 135 | if (!create) 136 | return NULL; 137 | 138 | MSG(DEBUG, "Create station entry " MAC_ADDR_FMT "\n", MAC_ADDR_DATA(addr)); 139 | sta = calloc(1, sizeof(*sta)); 140 | memcpy(sta->addr, addr, sizeof(sta->addr)); 141 | sta->avl.key = sta->addr; 142 | avl_insert(&stations, &sta->avl); 143 | INIT_LIST_HEAD(&sta->nodes); 144 | INIT_LIST_HEAD(&sta->measurements); 145 | 146 | return sta; 147 | } 148 | 149 | void usteer_sta_disconnected(struct sta_info *si) 150 | { 151 | si->connected = STA_NOT_CONNECTED; 152 | si->kick_time = 0; 153 | si->connected_since = 0; 154 | usteer_sta_info_update_timeout(si, config.local_sta_timeout); 155 | } 156 | 157 | void 158 | usteer_sta_info_update(struct sta_info *si, int signal, bool avg) 159 | { 160 | /* ignore probe request signal when connected */ 161 | if (si->connected == STA_CONNECTED && si->signal != NO_SIGNAL && !avg) 162 | signal = NO_SIGNAL; 163 | 164 | if (signal != NO_SIGNAL) { 165 | si->signal = signal; 166 | usteer_band_steering_sta_update(si); 167 | } 168 | 169 | si->seen = current_time; 170 | 171 | if (si->node->freq < 4000) 172 | si->sta->seen_2ghz = 1; 173 | else 174 | si->sta->seen_5ghz = 1; 175 | 176 | usteer_sta_info_update_timeout(si, config.local_sta_timeout); 177 | } 178 | 179 | bool 180 | usteer_handle_sta_event(struct usteer_node *node, const uint8_t *addr, 181 | enum usteer_event_type type, int freq, int signal) 182 | { 183 | struct sta *sta; 184 | struct sta_info *si; 185 | uint32_t diff; 186 | bool ret; 187 | bool create; 188 | 189 | sta = usteer_sta_get(addr, true); 190 | if (!sta) 191 | return -1; 192 | 193 | si = usteer_sta_info_get(sta, node, &create); 194 | usteer_sta_info_update(si, signal, false); 195 | si->stats[type].requests++; 196 | 197 | diff = si->stats[type].blocked_last_time - current_time; 198 | if (diff > config.sta_block_timeout) 199 | si->stats[type].blocked_cur = 0; 200 | 201 | ret = usteer_check_request(si, type); 202 | if (!ret) { 203 | si->stats[type].blocked_cur++; 204 | si->stats[type].blocked_total++; 205 | si->stats[type].blocked_last_time = current_time; 206 | } else { 207 | si->stats[type].blocked_cur = 0; 208 | } 209 | 210 | if (create) 211 | usteer_send_sta_update(si); 212 | 213 | return ret; 214 | } 215 | 216 | bool 217 | usteer_sta_supports_beacon_measurement_mode(struct sta_info *si, enum usteer_beacon_measurement_mode mode) 218 | { 219 | switch (mode) { 220 | case BEACON_MEASUREMENT_PASSIVE: 221 | return si->rrm & (1 << 4); 222 | case BEACON_MEASUREMENT_ACTIVE: 223 | return si->rrm & (1 << 5); 224 | case BEACON_MEASUREMENT_TABLE: 225 | return si->rrm & (1 << 6); 226 | } 227 | 228 | return false; 229 | } 230 | 231 | bool 232 | usteer_sta_supports_link_measurement(struct sta_info *si) 233 | { 234 | return si->rrm & (1 << 0); 235 | } 236 | 237 | static void __usteer_init usteer_sta_init(void) 238 | { 239 | usteer_timeout_init(&tq); 240 | tq.cb = usteer_sta_info_timeout; 241 | } 242 | -------------------------------------------------------------------------------- /fakeap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "utils.h" 25 | #include "timeout.h" 26 | 27 | static struct blob_buf b; 28 | static LIST_HEAD(stations); 29 | static struct usteer_timeout_queue tq; 30 | static FILE *r_fd; 31 | static struct ubus_object bss_obj; 32 | static struct ubus_context *ubus_ctx; 33 | static int freq = 2412; 34 | static int verbose; 35 | 36 | struct var { 37 | int cur; 38 | int min; 39 | int max; 40 | }; 41 | 42 | struct sta_data { 43 | struct list_head list; 44 | struct usteer_timeout probe_t; 45 | struct var probe; 46 | struct var signal; 47 | uint8_t addr[6]; 48 | }; 49 | 50 | static void gen_val(struct var *val) 51 | { 52 | int delta = val->max - val->min; 53 | uint8_t v; 54 | 55 | val->cur = val->min; 56 | if (!delta) 57 | return; 58 | 59 | if (fread(&v, sizeof(v), 1, r_fd) != sizeof(v)) 60 | fprintf(stderr, "short read\n"); 61 | val->cur += (((unsigned int) v) * delta) / 0xff; 62 | } 63 | 64 | static void 65 | blobmsg_add_macaddr(struct blob_buf *buf, const char *name, const uint8_t *addr) 66 | { 67 | char *s = blobmsg_alloc_string_buffer(buf, name, 20); 68 | sprintf(s, MAC_ADDR_FMT, MAC_ADDR_DATA(addr)); 69 | blobmsg_add_string_buffer(buf); 70 | } 71 | 72 | static void sta_send_probe(struct sta_data *sta) 73 | { 74 | const char *type = "probe"; 75 | int ret; 76 | int sig = -95 + sta->signal.cur; 77 | 78 | blob_buf_init(&b, 0); 79 | blobmsg_add_macaddr(&b, "address", sta->addr); 80 | blobmsg_add_u32(&b, "freq", freq); 81 | blobmsg_add_u32(&b, "signal", sig); 82 | ret = ubus_notify(ubus_ctx, &bss_obj, type, b.head, 100); 83 | if (verbose) 84 | fprintf(stderr, "STA "MAC_ADDR_FMT" probe: %d (%d ms, signal: %d)\n", 85 | MAC_ADDR_DATA(sta->addr), ret, sta->probe.cur, sig); 86 | } 87 | 88 | static void sta_schedule_probe(struct sta_data *sta) 89 | { 90 | gen_val(&sta->probe); 91 | gen_val(&sta->signal); 92 | usteer_timeout_set(&tq, &sta->probe_t, sta->probe.cur); 93 | } 94 | 95 | static void sta_probe(struct usteer_timeout_queue *q, struct usteer_timeout *t) 96 | { 97 | struct sta_data *sta = container_of(t, struct sta_data, probe_t); 98 | 99 | sta_send_probe(sta); 100 | sta_schedule_probe(sta); 101 | } 102 | 103 | static void init_station(struct sta_data *sta) 104 | { 105 | list_add_tail(&sta->list, &stations); 106 | if (fread(&sta->addr, sizeof(sta->addr), 1, r_fd) != sizeof(sta->addr)) 107 | fprintf(stderr, "short read\n"); 108 | sta->addr[0] &= ~1; 109 | 110 | sta_schedule_probe(sta); 111 | } 112 | 113 | static void create_stations(struct sta_data *ref, int n) 114 | { 115 | struct sta_data *sta; 116 | int i; 117 | 118 | tq.cb = sta_probe; 119 | sta = calloc(n, sizeof(*sta)); 120 | for (i = 0; i < n; i++) { 121 | memcpy(sta, ref, sizeof(*sta)); 122 | init_station(sta); 123 | sta++; 124 | } 125 | } 126 | 127 | static int usage(const char *prog) 128 | { 129 | fprintf(stderr, "Usage: %s \n" 130 | "Options:\n" 131 | " -p [-]: probing interval (fixed or min-max)\n" 132 | " -s [-]: rssi (signal strength) (fixed or min-max)\n" 133 | " -n : create stations\n" 134 | " -f : set operating frequency\n" 135 | " uses parameters set before this option\n" 136 | " -v: verbose\n" 137 | "\n", prog); 138 | return 1; 139 | } 140 | 141 | static bool parse_var(struct var *var, const char *str) 142 | { 143 | char *err; 144 | 145 | var->min = strtoul(str, &err, 0); 146 | var->max = var->min; 147 | if (!*err) 148 | return true; 149 | 150 | if (*err != ':') 151 | return false; 152 | 153 | var->max = strtoul(err + 1, &err, 0); 154 | if (!*err) 155 | return true; 156 | 157 | return false; 158 | } 159 | 160 | static int 161 | hostapd_bss_get_clients(struct ubus_context *ctx, struct ubus_object *obj, 162 | struct ubus_request_data *req, const char *method, 163 | struct blob_attr *msg) 164 | { 165 | blob_buf_init(&b, 0); 166 | ubus_send_reply(ctx, req, b.head); 167 | return 0; 168 | } 169 | 170 | static const struct ubus_method bss_methods[] = { 171 | UBUS_METHOD_NOARG("get_clients", hostapd_bss_get_clients), 172 | }; 173 | 174 | static struct ubus_object_type bss_object_type = 175 | UBUS_OBJECT_TYPE("hostapd_bss", bss_methods); 176 | 177 | static struct ubus_object bss_obj = { 178 | .name = "hostapd.wlan0", 179 | .type = &bss_object_type, 180 | .methods = bss_methods, 181 | .n_methods = ARRAY_SIZE(bss_methods), 182 | }; 183 | 184 | int main(int argc, char **argv) 185 | { 186 | struct sta_data sdata = { 187 | .signal = { 0, -30, -30 }, 188 | .probe = { 0, 1000, 30000 }, 189 | }; 190 | int ch; 191 | 192 | uloop_init(); 193 | 194 | r_fd = fopen("/dev/urandom", "r"); 195 | if (!r_fd) { 196 | perror("fopen"); 197 | return 1; 198 | } 199 | 200 | usteer_timeout_init(&tq); 201 | 202 | while ((ch = getopt(argc, argv, "p:s:f:n:v")) != -1) { 203 | switch(ch) { 204 | case 'p': 205 | if (!parse_var(&sdata.probe, optarg)) 206 | goto usage; 207 | break; 208 | case 's': 209 | if (!parse_var(&sdata.signal, optarg)) 210 | goto usage; 211 | break; 212 | case 'f': 213 | freq = atoi(optarg); 214 | break; 215 | case 'n': 216 | create_stations(&sdata, atoi(optarg)); 217 | break; 218 | case 'v': 219 | verbose++; 220 | break; 221 | default: 222 | goto usage; 223 | } 224 | } 225 | 226 | ubus_ctx = ubus_connect(NULL); 227 | if (!ubus_ctx) { 228 | fprintf(stderr, "Failed to connect to ubus\n"); 229 | return 1; 230 | } 231 | 232 | ubus_add_uloop(ubus_ctx); 233 | 234 | if (ubus_add_object(ubus_ctx, &bss_obj)) { 235 | fprintf(stderr, "Failed to register AP ubus object\n"); 236 | return 1; 237 | } 238 | uloop_run(); 239 | 240 | uloop_done(); 241 | return 0; 242 | usage: 243 | return usage(argv[0]); 244 | } 245 | -------------------------------------------------------------------------------- /event.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | #include "usteer.h" 20 | #include "event.h" 21 | 22 | #define UEV_LOG_MAXLEN 256 23 | 24 | static struct blob_buf b; 25 | static const char * const uev_name[] = { 26 | [UEV_PROBE_REQ_ACCEPT] = "probe_req_accept", 27 | [UEV_PROBE_REQ_DENY] = "probe_req_deny", 28 | [UEV_AUTH_REQ_ACCEPT] = "auth_req_accept", 29 | [UEV_AUTH_REQ_DENY] = "auth_req_deny", 30 | [UEV_ASSOC_REQ_ACCEPT] = "assoc_req_accept", 31 | [UEV_ASSOC_REQ_DENY] = "assoc_req_deny", 32 | [UEV_LOAD_KICK_TRIGGER] = "load_kick_trigger", 33 | [UEV_LOAD_KICK_RESET] = "load_kick_reset", 34 | [UEV_LOAD_KICK_MIN_CLIENTS] = "load_kick_min_clients", 35 | [UEV_LOAD_KICK_NO_CLIENT] = "load_kick_no_client", 36 | [UEV_LOAD_KICK_CLIENT] = "load_kick_client", 37 | [UEV_SIGNAL_KICK] = "signal_kick", 38 | 39 | }; 40 | static const char * const uev_reason[] = { 41 | [UEV_REASON_NONE] = "none", 42 | [UEV_REASON_RETRY_EXCEEDED] = "retry_exceeded", 43 | [UEV_REASON_LOW_SIGNAL] = "low_signal", 44 | [UEV_REASON_CONNECT_DELAY] = "connect_delay", 45 | [UEV_REASON_BETTER_CANDIDATE] = "better_candidate", 46 | }; 47 | 48 | static const char * const uev_select_reason[] = { 49 | [UEV_SELECT_REASON_NUM_ASSOC] = "n_assoc", 50 | [UEV_SELECT_REASON_SIGNAL] = "signal", 51 | [UEV_SELECT_REASON_LOAD] = "load", 52 | }; 53 | 54 | static void 55 | usteer_event_add_node_status(struct usteer_node *node) 56 | { 57 | blobmsg_add_u32(&b, "load", node->load); 58 | blobmsg_add_u32(&b, "assoc", node->n_assoc); 59 | } 60 | 61 | static void 62 | usteer_event_send_ubus(struct uevent *ev) 63 | { 64 | void *c; 65 | int i; 66 | 67 | if (!usteer_obj.has_subscribers) 68 | return; 69 | 70 | blob_buf_init(&b, 0); 71 | 72 | if (ev->node_local) 73 | blobmsg_add_string(&b, "node", usteer_node_name(ev->node_local)); 74 | 75 | if (ev->sta) 76 | blobmsg_printf(&b, "sta", MAC_ADDR_FMT, MAC_ADDR_DATA(ev->sta->addr)); 77 | 78 | if (ev->si_cur) 79 | blobmsg_add_u32(&b, "signal", (int32_t)ev->si_cur->signal); 80 | 81 | if (ev->reason) 82 | blobmsg_add_string(&b, "reason", uev_reason[ev->reason]); 83 | 84 | if (ev->threshold.ref) { 85 | c = blobmsg_open_array(&b, "threshold"); 86 | blobmsg_add_u32(&b, NULL, ev->threshold.cur); 87 | blobmsg_add_u32(&b, NULL, ev->threshold.ref); 88 | blobmsg_close_array(&b, c); 89 | } 90 | 91 | if (ev->select_reasons) { 92 | c = blobmsg_open_array(&b, "select_reason"); 93 | for (i = 0; i < ARRAY_SIZE(uev_select_reason); i++) { 94 | if (!(ev->select_reasons & (1 << i)) || 95 | !uev_select_reason[i]) 96 | continue; 97 | 98 | blobmsg_add_string(&b, NULL, uev_select_reason[i]); 99 | } 100 | blobmsg_close_array(&b, c); 101 | } 102 | 103 | if (ev->node_cur) { 104 | c = blobmsg_open_table(&b, "local"); 105 | usteer_event_add_node_status(ev->node_cur); 106 | blobmsg_close_table(&b, c); 107 | } 108 | 109 | if (ev->node_other) { 110 | c = blobmsg_open_table(&b, "remote"); 111 | blobmsg_add_string(&b, "name", usteer_node_name(ev->node_other)); 112 | if (ev->si_other) 113 | blobmsg_add_u32(&b, "signal", (int32_t)ev->si_other->signal); 114 | usteer_event_add_node_status(ev->node_other); 115 | blobmsg_close_table(&b, c); 116 | } 117 | 118 | if (ev->count) 119 | blobmsg_add_u32(&b, "count", ev->count); 120 | 121 | ubus_notify(ubus_ctx, &usteer_obj, uev_name[ev->type], b.head, -1); 122 | } 123 | 124 | static int 125 | usteer_event_log_node(char *buf, int len, const char *prefix, struct usteer_node *node) 126 | { 127 | char *cur = buf; 128 | char *end = buf + len; 129 | 130 | cur += snprintf(cur, end - cur, " %sassoc=%d %sload=%d", 131 | prefix, node->n_assoc, 132 | prefix, node->load); 133 | 134 | return cur - buf; 135 | } 136 | 137 | static void 138 | usteer_event_log(struct uevent *ev) 139 | { 140 | char *str, *cur, *end; 141 | int i; 142 | 143 | if (!(config.event_log_mask & (1 << ev->type))) 144 | return; 145 | 146 | blob_buf_init(&b, 0); 147 | cur = str = blobmsg_alloc_string_buffer(&b, NULL, UEV_LOG_MAXLEN); 148 | end = str + UEV_LOG_MAXLEN; 149 | cur += snprintf(cur, end - cur, "usteer event=%s", uev_name[ev->type]); 150 | if (ev->node_local) 151 | cur += snprintf(cur, end - cur, " node=%s", usteer_node_name(ev->node_local)); 152 | if (ev->sta) 153 | cur += snprintf(cur, end - cur, " sta=" MAC_ADDR_FMT, MAC_ADDR_DATA(ev->sta->addr)); 154 | if (ev->reason) 155 | cur += snprintf(cur, end - cur, " reason=%s", uev_reason[ev->reason]); 156 | if (ev->si_cur) 157 | cur += snprintf(cur, end - cur, " signal=%d", ev->si_cur->signal); 158 | if (ev->threshold.ref) 159 | cur += snprintf(cur, end - cur, " thr=%d/%d", ev->threshold.cur, ev->threshold.ref); 160 | if (ev->count) 161 | cur += snprintf(cur, end - cur, " count=%d", ev->count); 162 | if (ev->node_cur) 163 | cur += usteer_event_log_node(cur, end - cur, "", ev->node_cur); 164 | if (ev->select_reasons) { 165 | bool first = true; 166 | 167 | cur += snprintf(cur, end - cur, " select_reason"); 168 | for (i = 0; i < ARRAY_SIZE(uev_select_reason); i++) { 169 | if (!(ev->select_reasons & (1 << i)) || 170 | !uev_select_reason[i]) 171 | continue; 172 | 173 | cur += snprintf(cur, end - cur, "%c%s", first ? '=' : ',', 174 | uev_select_reason[i]); 175 | first = false; 176 | } 177 | } 178 | if (ev->node_other) { 179 | cur += snprintf(cur, end - cur, " remote=%s", usteer_node_name(ev->node_other)); 180 | if (ev->si_other) 181 | cur += snprintf(cur, end - cur, " remote_signal=%d", 182 | ev->si_other->signal); 183 | cur += usteer_event_log_node(cur, end - cur, "remote_", ev->node_other); 184 | } 185 | 186 | log_msg(str); 187 | } 188 | 189 | void usteer_event(struct uevent *ev) 190 | { 191 | if (ev->type >= ARRAY_SIZE(uev_name) || !uev_name[ev->type]) 192 | return; 193 | 194 | if (ev->reason >= ARRAY_SIZE(uev_reason) || !uev_reason[ev->reason]) 195 | return; 196 | 197 | if (ev->si_cur) { 198 | if (!ev->node_local) 199 | ev->node_local = ev->si_cur->node; 200 | if (!ev->sta) 201 | ev->sta = ev->si_cur->sta; 202 | } 203 | 204 | if (!ev->node_local && ev->node_cur) 205 | ev->node_local = ev->node_cur; 206 | 207 | if (ev->si_other && ev->node_cur && !ev->node_other) 208 | ev->node_other = ev->si_other->node; 209 | 210 | usteer_event_send_ubus(ev); 211 | usteer_event_log(ev); 212 | } 213 | 214 | void config_set_event_log_types(struct blob_attr *attr) 215 | { 216 | struct blob_attr *cur; 217 | int i, rem; 218 | 219 | config.event_log_mask = 0; 220 | if (!attr) { 221 | static const uint32_t default_log[] = { 222 | [MSG_INFO] = 223 | (1 << UEV_LOAD_KICK_CLIENT) | 224 | (1 << UEV_SIGNAL_KICK) | 225 | (1 << UEV_AUTH_REQ_DENY) | 226 | (1 << UEV_ASSOC_REQ_DENY), 227 | [MSG_VERBOSE] = 228 | (1 << UEV_PROBE_REQ_DENY), 229 | [MSG_DEBUG] = 230 | (1 << UEV_AUTH_REQ_ACCEPT) | 231 | (1 << UEV_ASSOC_REQ_ACCEPT) | 232 | (1 << UEV_LOAD_KICK_TRIGGER) | 233 | (1 << UEV_LOAD_KICK_RESET) | 234 | (1 << UEV_LOAD_KICK_MIN_CLIENTS) | 235 | (1 << UEV_LOAD_KICK_NO_CLIENT), 236 | }; 237 | 238 | if (config.debug_level >= MSG_DEBUG_ALL) { 239 | config.event_log_mask = ~0; 240 | return; 241 | } 242 | 243 | for (i = 0; i < ARRAY_SIZE(default_log) && i <= config.debug_level; i++) 244 | config.event_log_mask |= default_log[i]; 245 | 246 | return; 247 | } 248 | 249 | if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0) 250 | return; 251 | 252 | blobmsg_for_each_attr(cur, attr, rem) { 253 | const char *name = blobmsg_get_string(cur); 254 | 255 | for (i = 0; i < ARRAY_SIZE(uev_name); i++) { 256 | if (!uev_name[i] || strcmp(uev_name[i], name) != 0) 257 | continue; 258 | 259 | config.event_log_mask |= (1 << i); 260 | break; 261 | } 262 | } 263 | } 264 | 265 | void config_get_event_log_types(struct blob_buf *buf) 266 | { 267 | uint32_t mask = config.event_log_mask; 268 | void *c; 269 | int i; 270 | 271 | c = blobmsg_open_array(buf, "event_log_types"); 272 | for (i = 0; mask && i < ARRAY_SIZE(uev_name); i++) { 273 | bool cur = mask & 1; 274 | 275 | mask >>= 1; 276 | if (!cur) 277 | continue; 278 | 279 | blobmsg_add_string(buf, NULL, uev_name[i]); 280 | } 281 | blobmsg_close_array(buf, c); 282 | } 283 | -------------------------------------------------------------------------------- /usteer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #ifndef __APMGR_H 21 | #define __APMGR_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "utils.h" 30 | #include "timeout.h" 31 | 32 | #define NO_SIGNAL 0xff 33 | 34 | #define __STR(x) #x 35 | #define _STR(x) __STR(x) 36 | 37 | #define APMGR_V6_MCAST_GROUP "ff02::4150" 38 | 39 | #define APMGR_PORT 16720 /* AP */ 40 | #define APMGR_PORT_STR _STR(APMGR_PORT) 41 | #define APMGR_BUFLEN (64 * 1024) 42 | 43 | #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) 44 | 45 | enum usteer_event_type { 46 | EVENT_TYPE_PROBE, 47 | EVENT_TYPE_ASSOC, 48 | EVENT_TYPE_AUTH, 49 | __EVENT_TYPE_MAX, 50 | }; 51 | 52 | enum usteer_node_type { 53 | NODE_TYPE_LOCAL, 54 | NODE_TYPE_REMOTE, 55 | }; 56 | 57 | enum usteer_sta_connection_state { 58 | STA_NOT_CONNECTED = 0, 59 | STA_CONNECTED = 1, 60 | STA_DISCONNECTED = 2, 61 | }; 62 | 63 | enum usteer_beacon_measurement_mode { 64 | BEACON_MEASUREMENT_PASSIVE = 0, 65 | BEACON_MEASUREMENT_ACTIVE = 1, 66 | BEACON_MEASUREMENT_TABLE = 2, 67 | }; 68 | 69 | struct sta_info; 70 | struct usteer_local_node; 71 | struct usteer_remote_host; 72 | 73 | struct usteer_node { 74 | struct avl_node avl; 75 | struct list_head sta_info; 76 | struct list_head measurements; 77 | 78 | enum usteer_node_type type; 79 | 80 | struct blob_attr *rrm_nr; 81 | struct blob_attr *node_info; 82 | char ssid[33]; 83 | uint8_t bssid[6]; 84 | 85 | bool disabled; 86 | int freq; 87 | int channel; 88 | int op_class; 89 | int noise; 90 | int n_assoc; 91 | int max_assoc; 92 | int load; 93 | 94 | struct { 95 | int source; 96 | int target; 97 | } roam_events; 98 | 99 | uint64_t created; 100 | }; 101 | 102 | struct usteer_scan_request { 103 | int n_freq; 104 | int *freq; 105 | 106 | bool passive; 107 | }; 108 | 109 | struct usteer_scan_result { 110 | uint8_t bssid[6]; 111 | char ssid[33]; 112 | 113 | int freq; 114 | int signal; 115 | }; 116 | 117 | struct usteer_survey_data { 118 | uint16_t freq; 119 | int8_t noise; 120 | 121 | uint64_t time; 122 | uint64_t time_busy; 123 | }; 124 | 125 | struct usteer_freq_data { 126 | uint16_t freq; 127 | 128 | uint8_t txpower; 129 | bool dfs; 130 | }; 131 | 132 | struct usteer_node_handler { 133 | struct list_head list; 134 | 135 | void (*init_node)(struct usteer_node *); 136 | void (*free_node)(struct usteer_node *); 137 | void (*update_node)(struct usteer_node *); 138 | void (*update_sta)(struct usteer_node *, struct sta_info *); 139 | void (*get_survey)(struct usteer_node *, void *, 140 | void (*cb)(void *priv, struct usteer_survey_data *d)); 141 | void (*get_freqlist)(struct usteer_node *, void *, 142 | void (*cb)(void *priv, struct usteer_freq_data *f)); 143 | int (*scan)(struct usteer_node *, struct usteer_scan_request *, 144 | void *, void (*cb)(void *priv, struct usteer_scan_result *r)); 145 | }; 146 | 147 | struct usteer_config { 148 | bool syslog; 149 | uint32_t debug_level; 150 | 151 | bool ipv6; 152 | bool local_mode; 153 | 154 | uint32_t sta_block_timeout; 155 | uint32_t local_sta_timeout; 156 | uint32_t local_sta_update; 157 | 158 | uint32_t max_retry_band; 159 | uint32_t seen_policy_timeout; 160 | uint32_t measurement_report_timeout; 161 | 162 | bool assoc_steering; 163 | bool probe_steering; 164 | 165 | uint32_t max_neighbor_reports; 166 | 167 | uint32_t band_steering_threshold; 168 | uint32_t load_balancing_threshold; 169 | 170 | uint32_t remote_update_interval; 171 | uint32_t remote_node_timeout; 172 | 173 | int32_t min_snr; 174 | uint32_t min_snr_kick_delay; 175 | int32_t min_connect_snr; 176 | uint32_t signal_diff_threshold; 177 | 178 | uint32_t steer_reject_timeout; 179 | 180 | int32_t roam_scan_snr; 181 | uint32_t roam_process_timeout; 182 | 183 | uint32_t roam_scan_tries; 184 | uint32_t roam_scan_timeout; 185 | uint32_t roam_scan_interval; 186 | 187 | int32_t roam_trigger_snr; 188 | uint32_t roam_trigger_interval; 189 | 190 | uint32_t roam_kick_delay; 191 | 192 | uint32_t band_steering_interval; 193 | int32_t band_steering_min_snr; 194 | 195 | uint32_t link_measurement_interval; 196 | 197 | uint32_t initial_connect_delay; 198 | 199 | bool load_kick_enabled; 200 | uint32_t load_kick_threshold; 201 | uint32_t load_kick_delay; 202 | uint32_t load_kick_min_clients; 203 | uint32_t load_kick_reason_code; 204 | 205 | const char *node_up_script; 206 | uint32_t event_log_mask; 207 | 208 | struct blob_attr *ssid_list; 209 | }; 210 | 211 | struct usteer_bss_tm_query { 212 | struct list_head list; 213 | 214 | /* Can't use sta_info here, as the STA might already be deleted */ 215 | uint8_t sta_addr[6]; 216 | uint8_t dialog_token; 217 | }; 218 | 219 | struct sta_info_stats { 220 | uint32_t requests; 221 | uint32_t blocked_cur; 222 | uint32_t blocked_total; 223 | uint32_t blocked_last_time; 224 | }; 225 | 226 | enum roam_trigger_state { 227 | ROAM_TRIGGER_IDLE, 228 | ROAM_TRIGGER_SCAN, 229 | ROAM_TRIGGER_SCAN_DONE, 230 | }; 231 | 232 | struct sta_info { 233 | struct list_head list; 234 | struct list_head node_list; 235 | 236 | struct usteer_node *node; 237 | struct sta *sta; 238 | 239 | struct usteer_timeout timeout; 240 | 241 | struct sta_info_stats stats[__EVENT_TYPE_MAX]; 242 | uint64_t created; 243 | uint64_t seen; 244 | 245 | uint64_t connected_since; 246 | uint64_t last_connected; 247 | 248 | int signal; 249 | 250 | uint8_t rrm; 251 | bool bss_transition; 252 | bool mbo; 253 | 254 | enum roam_trigger_state roam_state; 255 | uint8_t roam_tries; 256 | uint64_t roam_event; 257 | uint64_t roam_kick; 258 | uint64_t roam_scan_start; 259 | uint64_t roam_scan_timeout_start; 260 | 261 | struct { 262 | uint8_t status_code; 263 | uint64_t timestamp; 264 | } bss_transition_response; 265 | 266 | struct { 267 | bool below_snr; 268 | } band_steering; 269 | 270 | uint64_t kick_time; 271 | 272 | int kick_count; 273 | 274 | uint32_t below_min_snr; 275 | 276 | uint8_t scan_band : 1; 277 | uint8_t connected : 2; 278 | }; 279 | 280 | struct sta { 281 | struct avl_node avl; 282 | struct list_head nodes; 283 | struct list_head measurements; 284 | 285 | uint8_t seen_2ghz : 1; 286 | uint8_t seen_5ghz : 1; 287 | 288 | uint8_t addr[6]; 289 | }; 290 | 291 | struct usteer_measurement_report { 292 | struct usteer_timeout timeout; 293 | 294 | struct list_head list; 295 | 296 | struct usteer_node *node; 297 | struct list_head node_list; 298 | 299 | struct sta *sta; 300 | struct list_head sta_list; 301 | 302 | uint64_t timestamp; 303 | 304 | uint8_t rcpi; 305 | uint8_t rsni; 306 | }; 307 | 308 | extern struct ubus_context *ubus_ctx; 309 | extern struct usteer_config config; 310 | extern struct list_head node_handlers; 311 | extern struct avl_tree stations; 312 | extern struct ubus_object usteer_obj; 313 | extern uint64_t current_time; 314 | extern const char * const event_types[__EVENT_TYPE_MAX]; 315 | extern struct blob_attr *host_info_blob; 316 | 317 | void usteer_update_time(void); 318 | void usteer_init_defaults(void); 319 | bool usteer_handle_sta_event(struct usteer_node *node, const uint8_t *addr, 320 | enum usteer_event_type type, int freq, int signal); 321 | 322 | int usteer_snr_to_signal(struct usteer_node *node, int snr); 323 | 324 | void usteer_local_nodes_init(struct ubus_context *ctx); 325 | void usteer_local_node_kick(struct usteer_local_node *ln); 326 | 327 | int usteer_local_node_get_beacon_interval(struct usteer_local_node *ln); 328 | 329 | bool usteer_policy_node_below_max_assoc(struct usteer_node *node); 330 | bool usteer_policy_can_perform_roam(struct sta_info *si); 331 | 332 | void usteer_band_steering_perform_steer(struct usteer_local_node *ln); 333 | void usteer_band_steering_sta_update(struct sta_info *si); 334 | bool usteer_band_steering_is_target(struct usteer_local_node *ln, struct usteer_node *node); 335 | 336 | void usteer_ubus_init(struct ubus_context *ctx); 337 | void usteer_ubus_kick_client(struct sta_info *si); 338 | int usteer_ubus_trigger_client_scan(struct sta_info *si); 339 | int usteer_ubus_band_steering_request(struct sta_info *si); 340 | int usteer_ubus_bss_transition_request(struct sta_info *si, 341 | uint8_t dialog_token, 342 | bool disassoc_imminent, 343 | bool abridged, 344 | uint8_t validity_period, 345 | struct usteer_node *target_node); 346 | 347 | struct sta *usteer_sta_get(const uint8_t *addr, bool create); 348 | struct sta_info *usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create); 349 | 350 | bool usteer_sta_supports_beacon_measurement_mode(struct sta_info *si, enum usteer_beacon_measurement_mode mode); 351 | bool usteer_sta_supports_link_measurement(struct sta_info *si); 352 | 353 | void usteer_sta_disconnected(struct sta_info *si); 354 | void usteer_sta_info_update_timeout(struct sta_info *si, int timeout); 355 | void usteer_sta_info_update(struct sta_info *si, int signal, bool avg); 356 | 357 | static inline const char *usteer_node_name(struct usteer_node *node) 358 | { 359 | return node->avl.key; 360 | } 361 | void usteer_node_set_blob(struct blob_attr **dest, struct blob_attr *val); 362 | 363 | struct usteer_local_node *usteer_local_node_by_bssid(uint8_t *bssid); 364 | struct usteer_remote_node *usteer_remote_node_by_bssid(uint8_t *bssid); 365 | struct usteer_node *usteer_node_by_bssid(uint8_t *bssid); 366 | 367 | struct usteer_node *usteer_node_get_next_neighbor(struct usteer_node *current_node, struct usteer_node *last); 368 | bool usteer_check_request(struct sta_info *si, enum usteer_event_type type); 369 | 370 | void config_set_interfaces(struct blob_attr *data); 371 | void config_get_interfaces(struct blob_buf *buf); 372 | 373 | void config_set_node_up_script(struct blob_attr *data); 374 | void config_get_node_up_script(struct blob_buf *buf); 375 | 376 | void config_set_ssid_list(struct blob_attr *data); 377 | void config_get_ssid_list(struct blob_buf *buf); 378 | 379 | int usteer_interface_init(void); 380 | void usteer_interface_add(const char *name); 381 | void usteer_sta_node_cleanup(struct usteer_node *node); 382 | void usteer_send_sta_update(struct sta_info *si); 383 | 384 | int usteer_lua_init(void); 385 | int usteer_lua_ubus_init(void); 386 | void usteer_run_hook(const char *name, const char *arg); 387 | 388 | void usteer_dump_node(struct blob_buf *buf, struct usteer_node *node); 389 | void usteer_dump_host(struct blob_buf *buf, struct usteer_remote_host *host); 390 | 391 | int usteer_measurement_get_rssi(struct usteer_measurement_report *report); 392 | 393 | struct usteer_measurement_report * usteer_measurement_report_get(struct sta *sta, struct usteer_node *node, bool create); 394 | void usteer_measurement_report_node_cleanup(struct usteer_node *node); 395 | void usteer_measurement_report_sta_cleanup(struct sta *sta); 396 | void usteer_measurement_report_del(struct usteer_measurement_report *mr); 397 | 398 | struct usteer_measurement_report * 399 | usteer_measurement_report_add(struct sta *sta, struct usteer_node *node, uint8_t rcpi, uint8_t rsni, uint64_t timestamp); 400 | 401 | 402 | int usteer_ubus_trigger_link_measurement(struct sta_info *si); 403 | #endif 404 | -------------------------------------------------------------------------------- /nl80211.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #define _GNU_SOURCE 21 | #include 22 | #include 23 | 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include "usteer.h" 37 | #include "node.h" 38 | 39 | static struct unl unl; 40 | static struct nlattr *tb[NL80211_ATTR_MAX + 1]; 41 | 42 | struct nl80211_survey_req { 43 | void (*cb)(void *priv, struct usteer_survey_data *d); 44 | void *priv; 45 | }; 46 | 47 | struct nl80211_scan_req { 48 | void (*cb)(void *priv, struct usteer_scan_result *r); 49 | void *priv; 50 | }; 51 | 52 | struct nl80211_freqlist_req { 53 | void (*cb)(void *priv, struct usteer_freq_data *f); 54 | void *priv; 55 | }; 56 | 57 | static int nl80211_survey_result(struct nl_msg *msg, void *arg) 58 | { 59 | static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = { 60 | [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 }, 61 | [NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 }, 62 | [NL80211_SURVEY_INFO_CHANNEL_TIME] = { .type = NLA_U64 }, 63 | [NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY] = { .type = NLA_U64 }, 64 | }; 65 | struct nlattr *tb[NL80211_ATTR_MAX + 1]; 66 | struct nlattr *tb_s[NL80211_SURVEY_INFO_MAX + 1]; 67 | struct nl80211_survey_req *req = arg; 68 | struct usteer_survey_data data = {}; 69 | struct genlmsghdr *gnlh; 70 | 71 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 72 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 73 | genlmsg_attrlen(gnlh, 0), NULL); 74 | 75 | if (!tb[NL80211_ATTR_SURVEY_INFO]) 76 | return NL_SKIP; 77 | 78 | if (nla_parse_nested(tb_s, NL80211_SURVEY_INFO_MAX, 79 | tb[NL80211_ATTR_SURVEY_INFO], survey_policy)) 80 | return NL_SKIP; 81 | 82 | if (!tb_s[NL80211_SURVEY_INFO_FREQUENCY]) 83 | return NL_SKIP; 84 | 85 | data.freq = nla_get_u32(tb_s[NL80211_SURVEY_INFO_FREQUENCY]); 86 | 87 | if (tb_s[NL80211_SURVEY_INFO_NOISE]) 88 | data.noise = (int8_t) nla_get_u8(tb_s[NL80211_SURVEY_INFO_NOISE]); 89 | 90 | if (tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME] && 91 | tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]) { 92 | data.time = nla_get_u64(tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME]); 93 | data.time_busy = nla_get_u64(tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]); 94 | } 95 | 96 | req->cb(req->priv, &data); 97 | 98 | return NL_SKIP; 99 | } 100 | 101 | static void nl80211_get_survey(struct usteer_node *node, void *priv, 102 | void (*cb)(void *priv, struct usteer_survey_data *d)) 103 | { 104 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 105 | struct nl80211_survey_req req = { 106 | .priv = priv, 107 | .cb = cb, 108 | }; 109 | struct nl_msg *msg; 110 | 111 | if (!ln->nl80211.present) 112 | return; 113 | 114 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_SURVEY, true); 115 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 116 | unl_genl_request(&unl, msg, nl80211_survey_result, &req); 117 | 118 | nla_put_failure: 119 | return; 120 | } 121 | 122 | static void nl80211_update_node_result(void *priv, struct usteer_survey_data *d) 123 | { 124 | struct usteer_local_node *ln = priv; 125 | uint32_t delta = 0, delta_busy = 0; 126 | 127 | if (d->freq != ln->node.freq) 128 | return; 129 | 130 | if (d->noise) 131 | ln->node.noise = d->noise; 132 | 133 | if (ln->time) { 134 | delta = d->time - ln->time; 135 | delta_busy = d->time_busy - ln->time_busy; 136 | } 137 | 138 | ln->time = d->time; 139 | ln->time_busy = d->time_busy; 140 | 141 | if (delta) { 142 | float cur = (100 * delta_busy) / delta; 143 | 144 | if (ln->load_ewma < 0) 145 | ln->load_ewma = cur; 146 | else 147 | ln->load_ewma = 0.85 * ln->load_ewma + 0.15 * cur; 148 | 149 | ln->node.load = ln->load_ewma; 150 | } 151 | } 152 | 153 | static void nl80211_update_node(struct uloop_timeout *t) 154 | { 155 | struct usteer_local_node *ln = container_of(t, struct usteer_local_node, nl80211.update); 156 | 157 | uloop_timeout_set(t, 1000); 158 | ln->ifindex = if_nametoindex(ln->iface); 159 | nl80211_get_survey(&ln->node, ln, nl80211_update_node_result); 160 | } 161 | 162 | static void nl80211_init_node(struct usteer_node *node) 163 | { 164 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 165 | struct genlmsghdr *gnlh; 166 | static bool _init = false; 167 | struct nl_msg *msg; 168 | 169 | if (node->type != NODE_TYPE_LOCAL) 170 | return; 171 | 172 | ln->nl80211.present = false; 173 | ln->wiphy = -1; 174 | 175 | if (!ln->ifindex) { 176 | MSG(INFO, "No ifindex found for node %s\n", usteer_node_name(node)); 177 | return; 178 | } 179 | 180 | if (!_init) { 181 | if (unl_genl_init(&unl, "nl80211") < 0) { 182 | unl_free(&unl); 183 | MSG(INFO, "nl80211 init failed\n"); 184 | return; 185 | } 186 | 187 | _init = true; 188 | } 189 | 190 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_INTERFACE, false); 191 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 192 | unl_genl_request_single(&unl, msg, &msg); 193 | if (!msg) 194 | return; 195 | 196 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 197 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 198 | genlmsg_attrlen(gnlh, 0), NULL); 199 | 200 | if (!tb[NL80211_ATTR_WIPHY]) 201 | goto nla_put_failure; 202 | 203 | if (!tb[NL80211_ATTR_MAC]) 204 | goto nla_put_failure; 205 | 206 | ln->wiphy = nla_get_u32(tb[NL80211_ATTR_WIPHY]); 207 | 208 | memcpy(node->bssid, nla_data(tb[NL80211_ATTR_MAC]), ETH_ALEN); 209 | 210 | if (tb[NL80211_ATTR_SSID]) { 211 | int len = nla_len(tb[NL80211_ATTR_SSID]); 212 | 213 | if (len >= sizeof(node->ssid)) 214 | len = sizeof(node->ssid) - 1; 215 | 216 | memcpy(node->ssid, nla_data(tb[NL80211_ATTR_SSID]), len); 217 | node->ssid[len] = 0; 218 | } 219 | 220 | MSG(INFO, "Found nl80211 phy on wdev %s, ssid=%s\n", usteer_node_name(node), node->ssid); 221 | ln->load_ewma = -1; 222 | ln->nl80211.present = true; 223 | ln->nl80211.update.cb = nl80211_update_node; 224 | nl80211_update_node(&ln->nl80211.update); 225 | 226 | nla_put_failure: 227 | nlmsg_free(msg); 228 | return; 229 | } 230 | 231 | static void nl80211_free_node(struct usteer_node *node) 232 | { 233 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 234 | 235 | if (!ln->nl80211.present) 236 | return; 237 | 238 | uloop_timeout_cancel(&ln->nl80211.update); 239 | } 240 | 241 | static void nl80211_update_sta(struct usteer_node *node, struct sta_info *si) 242 | { 243 | struct nlattr *tb_sta[NL80211_STA_INFO_MAX + 1]; 244 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 245 | struct genlmsghdr *gnlh; 246 | struct nl_msg *msg; 247 | int signal = NO_SIGNAL; 248 | 249 | if (!ln->nl80211.present) 250 | return; 251 | 252 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_STATION, false); 253 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 254 | NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, si->sta->addr); 255 | unl_genl_request_single(&unl, msg, &msg); 256 | if (!msg) 257 | return; 258 | 259 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 260 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 261 | genlmsg_attrlen(gnlh, 0), NULL); 262 | 263 | if (!tb[NL80211_ATTR_STA_INFO]) 264 | goto nla_put_failure; 265 | 266 | if (nla_parse_nested(tb_sta, NL80211_STA_INFO_MAX, 267 | tb[NL80211_ATTR_STA_INFO], NULL)) 268 | goto nla_put_failure; 269 | 270 | if (tb_sta[NL80211_STA_INFO_SIGNAL_AVG]) 271 | signal = (int8_t) nla_get_u8(tb_sta[NL80211_STA_INFO_SIGNAL_AVG]); 272 | 273 | if (tb_sta[NL80211_STA_INFO_CONNECTED_TIME]) 274 | si->connected_since = current_time - (nla_get_u32(tb_sta[NL80211_STA_INFO_CONNECTED_TIME]) * 1000); 275 | 276 | usteer_sta_info_update(si, signal, true); 277 | 278 | nla_put_failure: 279 | nlmsg_free(msg); 280 | return; 281 | } 282 | 283 | static int nl80211_scan_result(struct nl_msg *msg, void *arg) 284 | { 285 | static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = { 286 | [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 }, 287 | [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 }, 288 | [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 }, 289 | }; 290 | struct nlattr *tb[NL80211_ATTR_MAX + 1]; 291 | struct nlattr *bss[NL80211_BSS_MAX + 1]; 292 | struct nl80211_scan_req *req = arg; 293 | struct usteer_scan_result data = { 294 | .signal = -127, 295 | }; 296 | struct genlmsghdr *gnlh; 297 | struct nlattr *ie_attr; 298 | int ielen = 0; 299 | uint8_t *ie; 300 | 301 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 302 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 303 | genlmsg_attrlen(gnlh, 0), NULL); 304 | 305 | if (!tb[NL80211_ATTR_BSS]) 306 | return NL_SKIP; 307 | 308 | if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], 309 | bss_policy)) 310 | return NL_SKIP; 311 | 312 | if (!bss[NL80211_BSS_BSSID] || 313 | !bss[NL80211_BSS_FREQUENCY]) 314 | return NL_SKIP; 315 | 316 | data.freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); 317 | memcpy(data.bssid, nla_data(bss[NL80211_BSS_BSSID]), sizeof(data.bssid)); 318 | 319 | if (bss[NL80211_BSS_SIGNAL_MBM]) { 320 | int32_t signal = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]); 321 | data.signal = signal / 100; 322 | } 323 | 324 | ie_attr = bss[NL80211_BSS_INFORMATION_ELEMENTS]; 325 | if (!ie_attr) 326 | ie_attr = bss[NL80211_BSS_BEACON_IES]; 327 | 328 | if (!ie_attr) 329 | goto skip_ie; 330 | 331 | ie = (uint8_t *) nla_data(ie_attr); 332 | ielen = nla_len(ie_attr); 333 | for (; ielen >= 2 && ielen >= ie[1]; 334 | ielen -= ie[1] + 2, ie += ie[1] + 2) { 335 | if (ie[0] == 0) { /* SSID */ 336 | if (ie[1] > 32) 337 | continue; 338 | 339 | memcpy(data.ssid, ie + 2, ie[1]); 340 | } 341 | } 342 | 343 | skip_ie: 344 | req->cb(req->priv, &data); 345 | 346 | return NL_SKIP; 347 | } 348 | 349 | static int nl80211_scan_event_cb(struct nl_msg *msg, void *data) 350 | { 351 | struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); 352 | 353 | switch (gnlh->cmd) { 354 | case NL80211_CMD_NEW_SCAN_RESULTS: 355 | case NL80211_CMD_SCAN_ABORTED: 356 | unl_loop_done(&unl); 357 | break; 358 | } 359 | 360 | return NL_SKIP; 361 | } 362 | 363 | static int nl80211_scan(struct usteer_node *node, struct usteer_scan_request *req, 364 | void *priv, void (*cb)(void *priv, struct usteer_scan_result *r)) 365 | { 366 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 367 | struct nl80211_scan_req reqdata = { 368 | .priv = priv, 369 | .cb = cb, 370 | }; 371 | struct nl_msg *msg; 372 | struct nlattr *cur; 373 | int i, ret; 374 | 375 | if (!ln->nl80211.present) 376 | return -ENODEV; 377 | 378 | msg = unl_genl_msg(&unl, NL80211_CMD_TRIGGER_SCAN, false); 379 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 380 | 381 | if (!req->passive) { 382 | cur = nla_nest_start(msg, NL80211_ATTR_SCAN_SSIDS); 383 | NLA_PUT(msg, 1, 0, ""); 384 | nla_nest_end(msg, cur); 385 | } 386 | 387 | NLA_PUT_U32(msg, NL80211_ATTR_SCAN_FLAGS, NL80211_SCAN_FLAG_AP); 388 | 389 | if (req->n_freq) { 390 | cur = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES); 391 | for (i = 0; i < req->n_freq; i++) 392 | NLA_PUT_U32(msg, i, req->freq[i]); 393 | nla_nest_end(msg, cur); 394 | } 395 | 396 | unl_genl_subscribe(&unl, "scan"); 397 | ret = unl_genl_request(&unl, msg, NULL, NULL); 398 | if (ret < 0) 399 | goto done; 400 | 401 | unl_genl_loop(&unl, nl80211_scan_event_cb, NULL); 402 | 403 | done: 404 | unl_genl_unsubscribe(&unl, "scan"); 405 | if (ret < 0) 406 | return ret; 407 | 408 | if (!cb) 409 | return 0; 410 | 411 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_SCAN, true); 412 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 413 | unl_genl_request(&unl, msg, nl80211_scan_result, &reqdata); 414 | 415 | return 0; 416 | 417 | nla_put_failure: 418 | nlmsg_free(msg); 419 | return -ENOMEM; 420 | } 421 | 422 | static int nl80211_wiphy_result(struct nl_msg *msg, void *arg) 423 | { 424 | struct nl80211_freqlist_req *req = arg; 425 | struct nlattr *tb[NL80211_ATTR_MAX + 1]; 426 | struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; 427 | struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; 428 | struct nlattr *nl_band; 429 | struct nlattr *nl_freq; 430 | struct nlattr *cur; 431 | struct genlmsghdr *gnlh; 432 | int rem_band; 433 | int rem_freq; 434 | 435 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 436 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 437 | genlmsg_attrlen(gnlh, 0), NULL); 438 | 439 | if (!tb[NL80211_ATTR_WIPHY_BANDS]) 440 | return NL_SKIP; 441 | 442 | nla_for_each_nested(nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { 443 | nla_parse(tb_band, NL80211_BAND_ATTR_MAX, nla_data(nl_band), 444 | nla_len(nl_band), NULL); 445 | 446 | if (!tb_band[NL80211_BAND_ATTR_FREQS]) 447 | continue; 448 | 449 | nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], 450 | rem_freq) { 451 | struct usteer_freq_data f = {}; 452 | 453 | nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, 454 | nla_data(nl_freq), nla_len(nl_freq), NULL); 455 | 456 | if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) 457 | continue; 458 | 459 | if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IR]) 460 | continue; 461 | 462 | cur = tb_freq[NL80211_FREQUENCY_ATTR_FREQ]; 463 | if (!cur) 464 | continue; 465 | 466 | f.freq = nla_get_u32(cur); 467 | f.dfs = !!tb_freq[NL80211_FREQUENCY_ATTR_RADAR]; 468 | 469 | cur = tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER]; 470 | if (cur) 471 | f.txpower = nla_get_u32(cur) / 100; 472 | 473 | req->cb(req->priv, &f); 474 | } 475 | } 476 | 477 | return NL_SKIP; 478 | } 479 | 480 | static void nl80211_get_freqlist(struct usteer_node *node, void *priv, 481 | void (*cb)(void *priv, struct usteer_freq_data *f)) 482 | { 483 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 484 | struct nl80211_freqlist_req req = { 485 | .priv = priv, 486 | .cb = cb 487 | }; 488 | struct nl_msg *msg; 489 | 490 | if (!ln->nl80211.present) 491 | return; 492 | 493 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_WIPHY, false); 494 | 495 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, ln->wiphy); 496 | NLA_PUT_FLAG(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP); 497 | 498 | unl_genl_request(&unl, msg, nl80211_wiphy_result, &req); 499 | 500 | return; 501 | 502 | nla_put_failure: 503 | nlmsg_free(msg); 504 | } 505 | 506 | static struct usteer_node_handler nl80211_handler = { 507 | .init_node = nl80211_init_node, 508 | .free_node = nl80211_free_node, 509 | .update_sta = nl80211_update_sta, 510 | .get_survey = nl80211_get_survey, 511 | .get_freqlist = nl80211_get_freqlist, 512 | .scan = nl80211_scan, 513 | }; 514 | 515 | static void __usteer_init usteer_nl80211_init(void) 516 | { 517 | list_add(&nl80211_handler.list, &node_handlers); 518 | } 519 | -------------------------------------------------------------------------------- /policy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include "usteer.h" 21 | #include "node.h" 22 | #include "event.h" 23 | 24 | static bool 25 | below_assoc_threshold(struct usteer_node *node_cur, struct usteer_node *node_new) 26 | { 27 | int n_assoc_cur = node_cur->n_assoc; 28 | int n_assoc_new = node_new->n_assoc; 29 | bool ref_5g = node_cur->freq > 4000; 30 | bool node_5g = node_new->freq > 4000; 31 | 32 | if (!config.load_balancing_threshold) 33 | return false; 34 | 35 | if (ref_5g && !node_5g) 36 | n_assoc_new += config.band_steering_threshold; 37 | else if (!ref_5g && node_5g) 38 | n_assoc_cur += config.band_steering_threshold; 39 | 40 | n_assoc_new += config.load_balancing_threshold; 41 | 42 | return n_assoc_new <= n_assoc_cur; 43 | } 44 | 45 | static bool 46 | better_signal_strength(int signal_cur, int signal_new) 47 | { 48 | const bool is_better = signal_new - signal_cur 49 | > (int) config.signal_diff_threshold; 50 | 51 | if (!config.signal_diff_threshold) 52 | return false; 53 | 54 | return is_better; 55 | } 56 | 57 | static bool 58 | below_load_threshold(struct usteer_node *node) 59 | { 60 | return node->n_assoc >= config.load_kick_min_clients && 61 | node->load > config.load_kick_threshold; 62 | } 63 | 64 | static bool 65 | has_better_load(struct usteer_node *node_cur, struct usteer_node *node_new) 66 | { 67 | return !below_load_threshold(node_cur) && below_load_threshold(node_new); 68 | } 69 | 70 | bool 71 | usteer_policy_node_below_max_assoc(struct usteer_node *node) 72 | { 73 | return !node->max_assoc || node->n_assoc < node->max_assoc; 74 | } 75 | 76 | static bool 77 | over_min_signal(struct usteer_node *node, int signal) 78 | { 79 | if (config.min_snr && signal < usteer_snr_to_signal(node, config.min_snr)) 80 | return false; 81 | 82 | if (config.roam_trigger_snr && signal < usteer_snr_to_signal(node, config.roam_trigger_snr)) 83 | return false; 84 | 85 | return true; 86 | } 87 | 88 | static uint32_t 89 | is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new) 90 | { 91 | struct usteer_node *current_node = si_cur->node; 92 | struct usteer_node *new_node = si_new->node; 93 | int current_signal = si_cur->signal; 94 | int new_signal = si_new->signal; 95 | uint32_t reasons = 0; 96 | 97 | if (!usteer_policy_node_below_max_assoc(new_node)) 98 | return 0; 99 | 100 | if (!over_min_signal(new_node, new_signal)) 101 | return 0; 102 | 103 | if (below_assoc_threshold(current_node, new_node) && 104 | !below_assoc_threshold(new_node, current_node)) 105 | reasons |= (1 << UEV_SELECT_REASON_NUM_ASSOC); 106 | 107 | if (better_signal_strength(current_signal, new_signal)) 108 | reasons |= (1 << UEV_SELECT_REASON_SIGNAL); 109 | 110 | if (has_better_load(current_node, new_node) && 111 | !has_better_load(current_node, new_node)) 112 | reasons |= (1 << UEV_SELECT_REASON_LOAD); 113 | 114 | return reasons; 115 | } 116 | 117 | static struct sta_info * 118 | find_better_candidate(struct sta_info *si_ref, struct uevent *ev, uint32_t required_criteria, uint64_t max_age) 119 | { 120 | struct sta_info *si, *candidate = NULL; 121 | struct sta *sta = si_ref->sta; 122 | uint32_t reasons; 123 | 124 | list_for_each_entry(si, &sta->nodes, list) { 125 | if (si == si_ref) 126 | continue; 127 | 128 | if (current_time - si->seen > config.seen_policy_timeout) 129 | continue; 130 | 131 | if (strcmp(si->node->ssid, si_ref->node->ssid) != 0) 132 | continue; 133 | 134 | if (max_age && max_age < current_time - si->seen) 135 | continue; 136 | 137 | reasons = is_better_candidate(si_ref, si); 138 | if (!reasons) 139 | continue; 140 | 141 | if (!(reasons & required_criteria)) 142 | continue; 143 | 144 | if (ev) { 145 | ev->si_other = si; 146 | ev->select_reasons = reasons; 147 | } 148 | 149 | if (!candidate || si->signal > candidate->signal) 150 | candidate = si; 151 | } 152 | 153 | return candidate; 154 | } 155 | 156 | int 157 | usteer_snr_to_signal(struct usteer_node *node, int snr) 158 | { 159 | int noise = -95; 160 | 161 | if (snr < 0) 162 | return snr; 163 | 164 | if (node->noise) 165 | noise = node->noise; 166 | 167 | return noise + snr; 168 | } 169 | 170 | bool 171 | usteer_check_request(struct sta_info *si, enum usteer_event_type type) 172 | { 173 | struct uevent ev = { 174 | .si_cur = si, 175 | }; 176 | int min_signal; 177 | bool ret = true; 178 | 179 | if (type == EVENT_TYPE_PROBE && !config.probe_steering) 180 | goto out; 181 | 182 | if (type == EVENT_TYPE_AUTH) 183 | goto out; 184 | 185 | if (type == EVENT_TYPE_ASSOC) { 186 | /* Check if assoc request has lower signal than min_signal. 187 | * If this is the case, block assoc even when assoc steering is enabled. 188 | * 189 | * Otherwise, the client potentially ends up in a assoc - kick loop. 190 | */ 191 | if (config.min_snr && si->signal < usteer_snr_to_signal(si->node, config.min_snr)) { 192 | ev.reason = UEV_REASON_LOW_SIGNAL; 193 | ev.threshold.cur = si->signal; 194 | ev.threshold.ref = usteer_snr_to_signal(si->node, config.min_snr); 195 | ret = false; 196 | goto out; 197 | } else if (!config.assoc_steering) { 198 | goto out; 199 | } 200 | } 201 | 202 | min_signal = usteer_snr_to_signal(si->node, config.min_connect_snr); 203 | if (si->signal < min_signal) { 204 | ev.reason = UEV_REASON_LOW_SIGNAL; 205 | ev.threshold.cur = si->signal; 206 | ev.threshold.ref = min_signal; 207 | ret = false; 208 | goto out; 209 | } 210 | 211 | if (current_time - si->created < config.initial_connect_delay) { 212 | ev.reason = UEV_REASON_CONNECT_DELAY; 213 | ev.threshold.cur = current_time - si->created; 214 | ev.threshold.ref = config.initial_connect_delay; 215 | ret = false; 216 | goto out; 217 | } 218 | 219 | if (!find_better_candidate(si, &ev, UEV_SELECT_REASON_ALL, 0)) 220 | goto out; 221 | 222 | ev.reason = UEV_REASON_BETTER_CANDIDATE; 223 | ev.node_cur = si->node; 224 | ret = false; 225 | 226 | out: 227 | switch (type) { 228 | case EVENT_TYPE_PROBE: 229 | ev.type = ret ? UEV_PROBE_REQ_ACCEPT : UEV_PROBE_REQ_DENY; 230 | break; 231 | case EVENT_TYPE_ASSOC: 232 | ev.type = ret ? UEV_ASSOC_REQ_ACCEPT : UEV_ASSOC_REQ_DENY; 233 | break; 234 | case EVENT_TYPE_AUTH: 235 | ev.type = ret ? UEV_AUTH_REQ_ACCEPT : UEV_AUTH_REQ_DENY; 236 | break; 237 | default: 238 | break; 239 | } 240 | 241 | if (!ret && si->stats[type].blocked_cur >= config.max_retry_band) { 242 | ev.reason = UEV_REASON_RETRY_EXCEEDED; 243 | ev.threshold.cur = si->stats[type].blocked_cur; 244 | ev.threshold.ref = config.max_retry_band; 245 | } 246 | usteer_event(&ev); 247 | 248 | return ret; 249 | } 250 | 251 | static bool 252 | is_more_kickable(struct sta_info *si_cur, struct sta_info *si_new) 253 | { 254 | if (!si_cur) 255 | return true; 256 | 257 | if (si_new->kick_count > si_cur->kick_count) 258 | return false; 259 | 260 | return si_cur->signal > si_new->signal; 261 | } 262 | 263 | static void 264 | usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state, 265 | struct uevent *ev) 266 | { 267 | /* NOP in case we remain idle */ 268 | if (si->roam_state == state && si->roam_state == ROAM_TRIGGER_IDLE) { 269 | si->roam_tries = 0; 270 | return; 271 | } 272 | 273 | si->roam_event = current_time; 274 | 275 | if (si->roam_state == state) { 276 | si->roam_tries++; 277 | } else { 278 | si->roam_tries = 0; 279 | } 280 | 281 | si->roam_state = state; 282 | usteer_event(ev); 283 | } 284 | 285 | static void 286 | usteer_roam_sm_start_scan(struct sta_info *si, struct uevent *ev) 287 | { 288 | /* Start scanning in case we are not timeout-constrained or timeout has expired */ 289 | if (!config.roam_scan_timeout || 290 | current_time > si->roam_scan_timeout_start + config.roam_scan_timeout) { 291 | usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, ev); 292 | return; 293 | } 294 | 295 | /* We are currently in scan timeout / cooldown. 296 | * Check if we are in ROAM_TRIGGER_IDLE state. Enter this state if not. 297 | */ 298 | if (si->roam_state == ROAM_TRIGGER_IDLE) 299 | return; 300 | 301 | /* Enter idle state */ 302 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev); 303 | } 304 | 305 | static struct sta_info * 306 | usteer_roam_sm_found_better_node(struct sta_info *si, struct uevent *ev, enum roam_trigger_state next_state) 307 | { 308 | uint64_t max_age = 2 * config.roam_scan_interval; 309 | struct sta_info *candidate; 310 | 311 | if (max_age > current_time - si->roam_scan_start) 312 | max_age = current_time - si->roam_scan_start; 313 | 314 | candidate = find_better_candidate(si, ev, (1 << UEV_SELECT_REASON_SIGNAL), max_age); 315 | if (candidate) 316 | usteer_roam_set_state(si, next_state, ev); 317 | 318 | return candidate; 319 | } 320 | 321 | static bool 322 | usteer_roam_trigger_sm(struct usteer_local_node *ln, struct sta_info *si) 323 | { 324 | struct sta_info *candidate; 325 | struct uevent ev = { 326 | .si_cur = si, 327 | }; 328 | 329 | switch (si->roam_state) { 330 | case ROAM_TRIGGER_SCAN: 331 | if (!si->roam_tries) { 332 | si->roam_scan_start = current_time; 333 | } 334 | 335 | /* Check if we've found a better node regardless of the scan-interval */ 336 | if (usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE)) 337 | break; 338 | 339 | /* Only scan every scan-interval */ 340 | if (current_time - si->roam_event < config.roam_scan_interval) 341 | break; 342 | 343 | /* Check if no node was found within roam_scan_tries tries */ 344 | if (config.roam_scan_tries && si->roam_tries >= config.roam_scan_tries) { 345 | if (!config.roam_scan_timeout) { 346 | /* Prepare to kick client */ 347 | usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE, &ev); 348 | } else { 349 | /* Kick in scan timeout */ 350 | si->roam_scan_timeout_start = current_time; 351 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 352 | } 353 | break; 354 | } 355 | 356 | /* Send beacon-request to client */ 357 | usteer_ubus_trigger_client_scan(si); 358 | usteer_roam_sm_start_scan(si, &ev); 359 | break; 360 | 361 | case ROAM_TRIGGER_IDLE: 362 | usteer_roam_sm_start_scan(si, &ev); 363 | break; 364 | 365 | case ROAM_TRIGGER_SCAN_DONE: 366 | candidate = usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE); 367 | /* Kick back in case no better node is found */ 368 | if (!candidate) { 369 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 370 | break; 371 | } 372 | 373 | usteer_ubus_bss_transition_request(si, 1, false, false, 100, candidate->node); 374 | si->kick_time = current_time + config.roam_kick_delay; 375 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 376 | break; 377 | } 378 | 379 | return false; 380 | } 381 | 382 | bool usteer_policy_can_perform_roam(struct sta_info *si) 383 | { 384 | /* Only trigger for connected STAs */ 385 | if (si->connected != STA_CONNECTED) 386 | return false; 387 | 388 | /* Skip on pending kick */ 389 | if (si->kick_time) 390 | return false; 391 | 392 | /* Skip on rejected transition */ 393 | if (si->bss_transition_response.status_code && current_time - si->bss_transition_response.timestamp < config.steer_reject_timeout) 394 | return false; 395 | 396 | /* Skip on previous kick attempt */ 397 | if (current_time - si->roam_kick < config.roam_trigger_interval) 398 | return false; 399 | 400 | /* Skip if connection is established shorter than the trigger-interval */ 401 | if (current_time - si->connected_since < config.roam_trigger_interval) 402 | return false; 403 | 404 | return true; 405 | } 406 | 407 | static bool 408 | usteer_local_node_roam_sm_active(struct sta_info *si, int min_signal) 409 | { 410 | if (!usteer_policy_can_perform_roam(si)) 411 | return false; 412 | 413 | /* Signal has to be below scan / roam threshold */ 414 | if (si->signal >= min_signal) 415 | return false; 416 | 417 | return true; 418 | } 419 | 420 | static void 421 | usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev) 422 | { 423 | struct sta_info *si; 424 | int min_signal; 425 | 426 | if (config.roam_scan_snr) 427 | min_signal = config.roam_scan_snr; 428 | else if (config.roam_trigger_snr) 429 | min_signal = config.roam_trigger_snr; 430 | else 431 | return; 432 | 433 | usteer_update_time(); 434 | min_signal = usteer_snr_to_signal(&ln->node, min_signal); 435 | 436 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 437 | if (!usteer_local_node_roam_sm_active(si, min_signal)) { 438 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev); 439 | continue; 440 | } 441 | 442 | /* 443 | * If the state machine kicked a client, other clients should wait 444 | * until the next turn 445 | */ 446 | if (usteer_roam_trigger_sm(ln, si)) 447 | return; 448 | } 449 | } 450 | 451 | static void 452 | usteer_local_node_snr_kick(struct usteer_local_node *ln) 453 | { 454 | unsigned int min_count = DIV_ROUND_UP(config.min_snr_kick_delay, config.local_sta_update); 455 | struct uevent ev = { 456 | .node_local = &ln->node, 457 | }; 458 | struct sta_info *si; 459 | int min_signal; 460 | 461 | if (!config.min_snr) 462 | return; 463 | 464 | min_signal = usteer_snr_to_signal(&ln->node, config.min_snr); 465 | ev.threshold.ref = min_signal; 466 | 467 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 468 | if (si->connected != STA_CONNECTED) 469 | continue; 470 | 471 | if (si->signal >= min_signal) { 472 | si->below_min_snr = 0; 473 | continue; 474 | } else { 475 | si->below_min_snr++; 476 | } 477 | 478 | if (si->below_min_snr <= min_count) 479 | continue; 480 | 481 | ev.type = UEV_SIGNAL_KICK; 482 | ev.threshold.cur = si->signal; 483 | ev.count = si->kick_count; 484 | usteer_event(&ev); 485 | 486 | usteer_ubus_kick_client(si); 487 | return; 488 | } 489 | } 490 | 491 | static void 492 | usteer_local_node_load_kick(struct usteer_local_node *ln) 493 | { 494 | struct usteer_node *node = &ln->node; 495 | struct sta_info *kick1 = NULL, *kick2 = NULL; 496 | struct sta_info *candidate = NULL; 497 | struct sta_info *si; 498 | struct uevent ev = { 499 | .node_local = &ln->node, 500 | }; 501 | unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update); 502 | 503 | if (!config.load_kick_enabled || !config.load_kick_threshold || 504 | !config.load_kick_delay) 505 | return; 506 | 507 | if (node->load < config.load_kick_threshold) { 508 | if (!ln->load_thr_count) 509 | return; 510 | 511 | ln->load_thr_count = 0; 512 | ev.type = UEV_LOAD_KICK_RESET; 513 | ev.threshold.cur = node->load; 514 | ev.threshold.ref = config.load_kick_threshold; 515 | goto out; 516 | } 517 | 518 | if (++ln->load_thr_count <= min_count) { 519 | if (ln->load_thr_count > 1) 520 | return; 521 | 522 | ev.type = UEV_LOAD_KICK_TRIGGER; 523 | ev.threshold.cur = node->load; 524 | ev.threshold.ref = config.load_kick_threshold; 525 | goto out; 526 | } 527 | 528 | ln->load_thr_count = 0; 529 | if (node->n_assoc < config.load_kick_min_clients) { 530 | ev.type = UEV_LOAD_KICK_MIN_CLIENTS; 531 | ev.threshold.cur = node->n_assoc; 532 | ev.threshold.ref = config.load_kick_min_clients; 533 | goto out; 534 | } 535 | 536 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 537 | struct sta_info *tmp; 538 | 539 | if (si->connected != STA_CONNECTED) 540 | continue; 541 | 542 | if (is_more_kickable(kick1, si)) 543 | kick1 = si; 544 | 545 | tmp = find_better_candidate(si, NULL, (1 << UEV_SELECT_REASON_LOAD), 0); 546 | if (!tmp) 547 | continue; 548 | 549 | if (is_more_kickable(kick2, si)) { 550 | kick2 = si; 551 | candidate = tmp; 552 | } 553 | } 554 | 555 | if (!kick1) { 556 | ev.type = UEV_LOAD_KICK_NO_CLIENT; 557 | goto out; 558 | } 559 | 560 | if (kick2) 561 | kick1 = kick2; 562 | 563 | kick1->kick_count++; 564 | 565 | ev.type = UEV_LOAD_KICK_CLIENT; 566 | ev.si_cur = kick1; 567 | ev.si_other = candidate; 568 | ev.count = kick1->kick_count; 569 | 570 | usteer_ubus_kick_client(kick1); 571 | 572 | out: 573 | usteer_event(&ev); 574 | } 575 | 576 | static void 577 | usteer_local_node_perform_kick(struct usteer_local_node *ln) 578 | { 579 | struct sta_info *si; 580 | 581 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 582 | if (!si->kick_time || si->kick_time > current_time) 583 | continue; 584 | 585 | usteer_ubus_kick_client(si); 586 | } 587 | } 588 | 589 | void 590 | usteer_local_node_kick(struct usteer_local_node *ln) 591 | { 592 | struct uevent ev = { 593 | .node_local = &ln->node, 594 | }; 595 | 596 | usteer_local_node_perform_kick(ln); 597 | 598 | usteer_local_node_snr_kick(ln); 599 | usteer_local_node_load_kick(ln); 600 | usteer_local_node_roam_check(ln, &ev); 601 | } 602 | -------------------------------------------------------------------------------- /remote.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #define _GNU_SOURCE 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include "usteer.h" 34 | #include "remote.h" 35 | #include "node.h" 36 | 37 | static uint32_t local_id; 38 | static struct uloop_fd remote_fd; 39 | static struct uloop_timeout remote_timer; 40 | static struct uloop_timeout reload_timer; 41 | 42 | static struct blob_buf buf; 43 | static uint32_t msg_seq; 44 | 45 | struct interface { 46 | struct vlist_node node; 47 | int ifindex; 48 | }; 49 | 50 | static void 51 | interfaces_update_cb(struct vlist_tree *tree, 52 | struct vlist_node *node_new, 53 | struct vlist_node *node_old); 54 | 55 | static int remote_host_cmp(const void *k1, const void *k2, void *ptr) 56 | { 57 | unsigned long v1 = (unsigned long) k1; 58 | unsigned long v2 = (unsigned long) k2; 59 | 60 | return v2 - v1; 61 | } 62 | 63 | static VLIST_TREE(interfaces, avl_strcmp, interfaces_update_cb, true, true); 64 | LIST_HEAD(remote_nodes); 65 | AVL_TREE(remote_hosts, remote_host_cmp, false, NULL); 66 | 67 | static const char * 68 | interface_name(struct interface *iface) 69 | { 70 | return iface->node.avl.key; 71 | } 72 | 73 | static void 74 | interface_check(struct interface *iface) 75 | { 76 | iface->ifindex = if_nametoindex(interface_name(iface)); 77 | uloop_timeout_set(&reload_timer, 1); 78 | } 79 | 80 | static void 81 | interface_init(struct interface *iface) 82 | { 83 | interface_check(iface); 84 | } 85 | 86 | static void 87 | interface_free(struct interface *iface) 88 | { 89 | avl_delete(&interfaces.avl, &iface->node.avl); 90 | free(iface); 91 | } 92 | 93 | static void 94 | interfaces_update_cb(struct vlist_tree *tree, 95 | struct vlist_node *node_new, 96 | struct vlist_node *node_old) 97 | { 98 | struct interface *iface; 99 | 100 | if (node_new && node_old) { 101 | iface = container_of(node_new, struct interface, node); 102 | free(iface); 103 | iface = container_of(node_old, struct interface, node); 104 | interface_check(iface); 105 | } else if (node_old) { 106 | iface = container_of(node_old, struct interface, node); 107 | interface_free(iface); 108 | } else { 109 | iface = container_of(node_new, struct interface, node); 110 | interface_init(iface); 111 | } 112 | } 113 | 114 | void usteer_interface_add(const char *name) 115 | { 116 | struct interface *iface; 117 | char *name_buf; 118 | 119 | iface = calloc_a(sizeof(*iface), &name_buf, strlen(name) + 1); 120 | strcpy(name_buf, name); 121 | vlist_add(&interfaces, &iface->node, name_buf); 122 | } 123 | 124 | void config_set_interfaces(struct blob_attr *data) 125 | { 126 | struct blob_attr *cur; 127 | int rem; 128 | 129 | if (!data) 130 | return; 131 | 132 | if (!blobmsg_check_attr_list(data, BLOBMSG_TYPE_STRING)) 133 | return; 134 | 135 | vlist_update(&interfaces); 136 | blobmsg_for_each_attr(cur, data, rem) { 137 | usteer_interface_add(blobmsg_data(cur)); 138 | } 139 | vlist_flush(&interfaces); 140 | } 141 | 142 | void config_get_interfaces(struct blob_buf *buf) 143 | { 144 | struct interface *iface; 145 | void *c; 146 | 147 | c = blobmsg_open_array(buf, "interfaces"); 148 | vlist_for_each_element(&interfaces, iface, node) { 149 | blobmsg_add_string(buf, NULL, interface_name(iface)); 150 | } 151 | blobmsg_close_array(buf, c); 152 | } 153 | 154 | static void 155 | interface_add_station(struct usteer_remote_node *node, struct blob_attr *data) 156 | { 157 | struct sta *sta; 158 | struct sta_info *si, *local_si; 159 | struct apmsg_sta msg; 160 | struct usteer_node *local_node; 161 | bool create; 162 | bool connect_change; 163 | 164 | if (!parse_apmsg_sta(&msg, data)) { 165 | MSG(DEBUG, "Cannot parse station in message\n"); 166 | return; 167 | } 168 | 169 | if (msg.timeout <= 0) { 170 | MSG(DEBUG, "Refuse to add an already expired station entry\n"); 171 | return; 172 | } 173 | 174 | sta = usteer_sta_get(msg.addr, true); 175 | if (!sta) 176 | return; 177 | 178 | si = usteer_sta_info_get(sta, &node->node, &create); 179 | if (!si) 180 | return; 181 | 182 | connect_change = si->connected != msg.connected; 183 | si->connected = msg.connected; 184 | si->signal = msg.signal; 185 | si->seen = current_time - msg.seen; 186 | si->last_connected = current_time - msg.last_connected; 187 | 188 | /* Check if client roamed to this foreign node */ 189 | if ((connect_change || create) && si->connected == STA_CONNECTED) { 190 | for_each_local_node(local_node) { 191 | local_si = usteer_sta_info_get(sta, local_node, NULL); 192 | if (!local_si) 193 | continue; 194 | 195 | if (current_time - local_si->last_connected < config.roam_process_timeout) { 196 | node->node.roam_events.target++; 197 | break; 198 | } 199 | } 200 | } 201 | 202 | usteer_sta_info_update_timeout(si, msg.timeout); 203 | } 204 | 205 | static void 206 | remote_node_free(struct usteer_remote_node *node) 207 | { 208 | struct usteer_remote_host *host = node->host; 209 | 210 | list_del(&node->list); 211 | list_del(&node->host_list); 212 | usteer_sta_node_cleanup(&node->node); 213 | usteer_measurement_report_node_cleanup(&node->node); 214 | free(node); 215 | 216 | if (!list_empty(&host->nodes)) 217 | return; 218 | 219 | avl_delete(&remote_hosts, &host->avl); 220 | free(host->addr); 221 | free(host); 222 | } 223 | 224 | static struct usteer_remote_host * 225 | interface_get_host(const char *addr, unsigned long id) 226 | { 227 | struct usteer_remote_host *host; 228 | 229 | host = avl_find_element(&remote_hosts, (void *)id, host, avl); 230 | if (host) 231 | goto out; 232 | 233 | host = calloc(1, sizeof(*host)); 234 | host->avl.key = (void *)id; 235 | INIT_LIST_HEAD(&host->nodes); 236 | avl_insert(&remote_hosts, &host->avl); 237 | 238 | out: 239 | if (host->addr && !strcmp(host->addr, addr)) 240 | return host; 241 | 242 | free(host->addr); 243 | host->addr = strdup(addr); 244 | 245 | return host; 246 | } 247 | 248 | static struct usteer_remote_node * 249 | interface_get_node(struct usteer_remote_host *host, const char *name) 250 | { 251 | struct usteer_remote_node *node; 252 | int addr_len = strlen(host->addr); 253 | char *buf; 254 | 255 | list_for_each_entry(node, &host->nodes, host_list) 256 | if (!strcmp(node->name, name)) 257 | return node; 258 | 259 | node = calloc_a(sizeof(*node), &buf, addr_len + 1 + strlen(name) + 1); 260 | node->node.type = NODE_TYPE_REMOTE; 261 | node->node.created = current_time; 262 | 263 | sprintf(buf, "%s#%s", host->addr, name); 264 | node->node.avl.key = buf; 265 | node->name = buf + addr_len + 1; 266 | node->host = host; 267 | INIT_LIST_HEAD(&node->node.sta_info); 268 | INIT_LIST_HEAD(&node->node.measurements); 269 | 270 | list_add_tail(&node->list, &remote_nodes); 271 | list_add_tail(&node->host_list, &host->nodes); 272 | 273 | return node; 274 | } 275 | 276 | static void 277 | interface_add_node(struct usteer_remote_host *host, struct blob_attr *data) 278 | { 279 | struct usteer_remote_node *node; 280 | struct apmsg_node msg; 281 | struct blob_attr *cur; 282 | int rem; 283 | 284 | if (!parse_apmsg_node(&msg, data)) { 285 | MSG(DEBUG, "Cannot parse node in message\n"); 286 | return; 287 | } 288 | 289 | node = interface_get_node(host, msg.name); 290 | node->check = 0; 291 | node->node.freq = msg.freq; 292 | node->node.channel = msg.channel; 293 | node->node.op_class = msg.op_class; 294 | node->node.n_assoc = msg.n_assoc; 295 | node->node.max_assoc = msg.max_assoc; 296 | node->node.noise = msg.noise; 297 | node->node.load = msg.load; 298 | 299 | memcpy(node->node.bssid, msg.bssid, sizeof(node->node.bssid)); 300 | 301 | snprintf(node->node.ssid, sizeof(node->node.ssid), "%s", msg.ssid); 302 | usteer_node_set_blob(&node->node.rrm_nr, msg.rrm_nr); 303 | usteer_node_set_blob(&node->node.node_info, msg.node_info); 304 | 305 | blob_for_each_attr(cur, msg.stations, rem) 306 | interface_add_station(node, cur); 307 | } 308 | 309 | static void 310 | interface_recv_msg(struct interface *iface, char *addr_str, void *buf, int len) 311 | { 312 | struct usteer_remote_host *host; 313 | struct blob_attr *data = buf; 314 | struct apmsg msg; 315 | struct blob_attr *cur; 316 | int rem; 317 | 318 | if (config.local_mode) 319 | return; 320 | 321 | if (blob_pad_len(data) != len) { 322 | MSG(DEBUG, "Invalid message length (header: %d, real: %d)\n", blob_pad_len(data), len); 323 | return; 324 | } 325 | 326 | if (!parse_apmsg(&msg, data)) { 327 | MSG(DEBUG, "Missing fields in message\n"); 328 | return; 329 | } 330 | 331 | if (msg.id == local_id) 332 | return; 333 | 334 | MSG(NETWORK, "Received message on %s (id=%08x->%08x seq=%d len=%d)\n", 335 | interface_name(iface), msg.id, local_id, msg.seq, len); 336 | 337 | host = interface_get_host(addr_str, msg.id); 338 | usteer_node_set_blob(&host->host_info, msg.host_info); 339 | 340 | blob_for_each_attr(cur, msg.nodes, rem) 341 | interface_add_node(host, cur); 342 | } 343 | 344 | static struct interface * 345 | interface_find_by_ifindex(int index) 346 | { 347 | struct interface *iface; 348 | 349 | vlist_for_each_element(&interfaces, iface, node) { 350 | if (iface->ifindex == index) 351 | return iface; 352 | } 353 | 354 | return NULL; 355 | } 356 | 357 | static void 358 | interface_recv_v4(struct uloop_fd *u, unsigned int events) 359 | { 360 | static char buf[APMGR_BUFLEN]; 361 | static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1]; 362 | static struct sockaddr_in sin; 363 | char addr_str[INET_ADDRSTRLEN]; 364 | static struct iovec iov = { 365 | .iov_base = buf, 366 | .iov_len = sizeof(buf) 367 | }; 368 | static struct msghdr msg = { 369 | .msg_name = &sin, 370 | .msg_namelen = sizeof(sin), 371 | .msg_iov = &iov, 372 | .msg_iovlen = 1, 373 | .msg_control = cmsg_buf, 374 | .msg_controllen = sizeof(cmsg_buf), 375 | }; 376 | struct cmsghdr *cmsg; 377 | int len; 378 | 379 | do { 380 | struct in_pktinfo *pkti = NULL; 381 | struct interface *iface; 382 | 383 | len = recvmsg(u->fd, &msg, 0); 384 | if (len < 0) { 385 | switch (errno) { 386 | case EAGAIN: 387 | return; 388 | case EINTR: 389 | continue; 390 | default: 391 | perror("recvmsg"); 392 | uloop_fd_delete(u); 393 | return; 394 | } 395 | } 396 | 397 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { 398 | if (cmsg->cmsg_type != IP_PKTINFO) 399 | continue; 400 | 401 | pkti = (struct in_pktinfo *) CMSG_DATA(cmsg); 402 | } 403 | 404 | if (!pkti) { 405 | MSG(DEBUG, "Received packet without ifindex\n"); 406 | continue; 407 | } 408 | 409 | iface = interface_find_by_ifindex(pkti->ipi_ifindex); 410 | if (!iface) { 411 | MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi_ifindex); 412 | continue; 413 | } 414 | 415 | inet_ntop(AF_INET, &sin.sin_addr, addr_str, sizeof(addr_str)); 416 | 417 | interface_recv_msg(iface, addr_str, buf, len); 418 | } while (1); 419 | } 420 | 421 | 422 | static void interface_recv_v6(struct uloop_fd *u, unsigned int events){ 423 | static char buf[APMGR_BUFLEN]; 424 | static char cmsg_buf[( CMSG_SPACE(sizeof(struct in6_pktinfo)) + sizeof(int)) + 1]; 425 | static struct sockaddr_in6 sin; 426 | static struct iovec iov = { 427 | .iov_base = buf, 428 | .iov_len = sizeof(buf) 429 | }; 430 | static struct msghdr msg = { 431 | .msg_name = &sin, 432 | .msg_namelen = sizeof(sin), 433 | .msg_iov = &iov, 434 | .msg_iovlen = 1, 435 | .msg_control = cmsg_buf, 436 | .msg_controllen = sizeof(cmsg_buf), 437 | }; 438 | struct cmsghdr *cmsg; 439 | char addr_str[INET6_ADDRSTRLEN]; 440 | int len; 441 | 442 | do { 443 | struct in6_pktinfo *pkti = NULL; 444 | struct interface *iface; 445 | 446 | len = recvmsg(u->fd, &msg, 0); 447 | if (len < 0) { 448 | switch (errno) { 449 | case EAGAIN: 450 | return; 451 | case EINTR: 452 | continue; 453 | default: 454 | perror("recvmsg"); 455 | uloop_fd_delete(u); 456 | return; 457 | } 458 | } 459 | 460 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { 461 | if (cmsg->cmsg_type != IPV6_PKTINFO) 462 | continue; 463 | 464 | pkti = (struct in6_pktinfo *) CMSG_DATA(cmsg); 465 | } 466 | 467 | if (!pkti) { 468 | MSG(DEBUG, "Received packet without ifindex\n"); 469 | continue; 470 | } 471 | 472 | iface = interface_find_by_ifindex(pkti->ipi6_ifindex); 473 | if (!iface) { 474 | MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi6_ifindex); 475 | continue; 476 | } 477 | 478 | inet_ntop(AF_INET6, &sin.sin6_addr, addr_str, sizeof(addr_str)); 479 | if (sin.sin6_addr.s6_addr[0] == 0) { 480 | /* IPv4 mapped address. Ignore. */ 481 | continue; 482 | } 483 | 484 | interface_recv_msg(iface, addr_str, buf, len); 485 | } while (1); 486 | } 487 | 488 | static void interface_send_msg_v4(struct interface *iface, struct blob_attr *data) 489 | { 490 | static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1]; 491 | static struct sockaddr_in a; 492 | static struct iovec iov; 493 | static struct msghdr m = { 494 | .msg_name = (struct sockaddr *) &a, 495 | .msg_namelen = sizeof(a), 496 | .msg_iov = &iov, 497 | .msg_iovlen = 1, 498 | .msg_control = cmsg_data, 499 | .msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)), 500 | }; 501 | struct in_pktinfo *pkti; 502 | struct cmsghdr *cmsg; 503 | 504 | a.sin_family = AF_INET; 505 | a.sin_port = htons(APMGR_PORT); 506 | a.sin_addr.s_addr = ~0; 507 | 508 | memset(cmsg_data, 0, sizeof(cmsg_data)); 509 | cmsg = CMSG_FIRSTHDR(&m); 510 | cmsg->cmsg_len = m.msg_controllen; 511 | cmsg->cmsg_level = IPPROTO_IP; 512 | cmsg->cmsg_type = IP_PKTINFO; 513 | 514 | pkti = (struct in_pktinfo *) CMSG_DATA(cmsg); 515 | pkti->ipi_ifindex = iface->ifindex; 516 | 517 | iov.iov_base = data; 518 | iov.iov_len = blob_pad_len(data); 519 | 520 | if (sendmsg(remote_fd.fd, &m, 0) < 0) 521 | perror("sendmsg"); 522 | } 523 | 524 | 525 | static void interface_send_msg_v6(struct interface *iface, struct blob_attr *data) { 526 | static struct sockaddr_in6 groupSock = {}; 527 | 528 | groupSock.sin6_family = AF_INET6; 529 | inet_pton(AF_INET6, APMGR_V6_MCAST_GROUP, &groupSock.sin6_addr); 530 | groupSock.sin6_port = htons(APMGR_PORT); 531 | 532 | setsockopt(remote_fd.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex)); 533 | 534 | if (sendto(remote_fd.fd, data, blob_pad_len(data), 0, (const struct sockaddr *)&groupSock, sizeof(groupSock)) < 0) 535 | perror("sendmsg"); 536 | } 537 | 538 | static void interface_send_msg(struct interface *iface, struct blob_attr *data){ 539 | if (config.ipv6) { 540 | interface_send_msg_v6(iface, data); 541 | } else { 542 | interface_send_msg_v4(iface, data); 543 | } 544 | } 545 | 546 | static void usteer_send_sta_info(struct sta_info *sta) 547 | { 548 | int seen = current_time - sta->seen; 549 | int last_connected = !!sta->connected ? 0 : current_time - sta->last_connected; 550 | void *c; 551 | 552 | c = blob_nest_start(&buf, 0); 553 | blob_put(&buf, APMSG_STA_ADDR, sta->sta->addr, 6); 554 | blob_put_int8(&buf, APMSG_STA_CONNECTED, !!sta->connected); 555 | blob_put_int32(&buf, APMSG_STA_SIGNAL, sta->signal); 556 | blob_put_int32(&buf, APMSG_STA_SEEN, seen); 557 | blob_put_int32(&buf, APMSG_STA_LAST_CONNECTED, last_connected); 558 | blob_put_int32(&buf, APMSG_STA_TIMEOUT, config.local_sta_timeout - seen); 559 | blob_nest_end(&buf, c); 560 | } 561 | 562 | static void usteer_send_node(struct usteer_node *node, struct sta_info *sta) 563 | { 564 | void *c, *s, *r; 565 | 566 | c = blob_nest_start(&buf, 0); 567 | 568 | blob_put_string(&buf, APMSG_NODE_NAME, usteer_node_name(node)); 569 | blob_put_string(&buf, APMSG_NODE_SSID, node->ssid); 570 | blob_put_int32(&buf, APMSG_NODE_FREQ, node->freq); 571 | blob_put_int32(&buf, APMSG_NODE_NOISE, node->noise); 572 | blob_put_int32(&buf, APMSG_NODE_LOAD, node->load); 573 | blob_put_int32(&buf, APMSG_NODE_N_ASSOC, node->n_assoc); 574 | blob_put_int32(&buf, APMSG_NODE_MAX_ASSOC, node->max_assoc); 575 | blob_put_int32(&buf, APMSG_NODE_OP_CLASS, node->op_class); 576 | blob_put_int32(&buf, APMSG_NODE_CHANNEL, node->channel); 577 | blob_put(&buf, APMSG_NODE_BSSID, node->bssid, sizeof(node->bssid)); 578 | if (node->rrm_nr) { 579 | r = blob_nest_start(&buf, APMSG_NODE_RRM_NR); 580 | blobmsg_add_field(&buf, BLOBMSG_TYPE_ARRAY, "", 581 | blobmsg_data(node->rrm_nr), 582 | blobmsg_data_len(node->rrm_nr)); 583 | blob_nest_end(&buf, r); 584 | } 585 | 586 | if (node->node_info) 587 | blob_put(&buf, APMSG_NODE_NODE_INFO, 588 | blob_data(node->node_info), 589 | blob_len(node->node_info)); 590 | 591 | s = blob_nest_start(&buf, APMSG_NODE_STATIONS); 592 | 593 | if (sta) { 594 | usteer_send_sta_info(sta); 595 | } else { 596 | list_for_each_entry(sta, &node->sta_info, node_list) 597 | usteer_send_sta_info(sta); 598 | } 599 | 600 | blob_nest_end(&buf, s); 601 | 602 | blob_nest_end(&buf, c); 603 | } 604 | 605 | static void 606 | usteer_check_timeout(void) 607 | { 608 | struct usteer_remote_node *node, *tmp; 609 | int timeout = config.remote_node_timeout; 610 | 611 | list_for_each_entry_safe(node, tmp, &remote_nodes, list) { 612 | if (config.local_mode || node->check++ > timeout) 613 | remote_node_free(node); 614 | } 615 | } 616 | 617 | static void * 618 | usteer_update_init(void) 619 | { 620 | blob_buf_init(&buf, 0); 621 | blob_put_int32(&buf, APMSG_ID, local_id); 622 | blob_put_int32(&buf, APMSG_SEQ, ++msg_seq); 623 | if (host_info_blob) 624 | blob_put(&buf, APMSG_HOST_INFO, 625 | blob_data(host_info_blob), 626 | blob_len(host_info_blob)); 627 | 628 | return blob_nest_start(&buf, APMSG_NODES); 629 | } 630 | 631 | static void 632 | usteer_update_send(void *c) 633 | { 634 | struct interface *iface; 635 | 636 | blob_nest_end(&buf, c); 637 | 638 | vlist_for_each_element(&interfaces, iface, node) 639 | interface_send_msg(iface, buf.head); 640 | } 641 | 642 | void 643 | usteer_send_sta_update(struct sta_info *si) 644 | { 645 | void *c = usteer_update_init(); 646 | usteer_send_node(si->node, si); 647 | usteer_update_send(c); 648 | } 649 | 650 | static void 651 | usteer_send_update_timer(struct uloop_timeout *t) 652 | { 653 | struct usteer_node *node; 654 | void *c; 655 | 656 | usteer_update_time(); 657 | uloop_timeout_set(t, config.remote_update_interval); 658 | 659 | if (!config.local_mode && 660 | (!avl_is_empty(&local_nodes) || host_info_blob)) { 661 | c = usteer_update_init(); 662 | for_each_local_node(node) 663 | usteer_send_node(node, NULL); 664 | 665 | usteer_update_send(c); 666 | } 667 | usteer_check_timeout(); 668 | } 669 | 670 | static int 671 | usteer_init_local_id(void) 672 | { 673 | FILE *f; 674 | 675 | f = fopen("/dev/urandom", "r"); 676 | if (!f) { 677 | perror("fopen(/dev/urandom)"); 678 | return -1; 679 | } 680 | 681 | if (fread(&local_id, sizeof(local_id), 1, f) < 1) { 682 | fclose(f); 683 | return -1; 684 | } 685 | 686 | fclose(f); 687 | return 0; 688 | } 689 | 690 | static int usteer_create_v4_socket() { 691 | int yes = 1; 692 | int fd; 693 | 694 | fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK | 695 | USOCK_NUMERIC | USOCK_IPV4ONLY, 696 | "0.0.0.0", APMGR_PORT_STR); 697 | if (fd < 0) { 698 | perror("usock"); 699 | return - 1; 700 | } 701 | 702 | if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0) 703 | perror("setsockopt(IP_PKTINFO)"); 704 | 705 | if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0) 706 | perror("setsockopt(SO_BROADCAST)"); 707 | 708 | return fd; 709 | } 710 | 711 | 712 | static int usteer_create_v6_socket() { 713 | struct interface *iface; 714 | struct ipv6_mreq group; 715 | int yes = 1; 716 | int fd; 717 | 718 | fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK | 719 | USOCK_NUMERIC | USOCK_IPV6ONLY, 720 | "::", APMGR_PORT_STR); 721 | if (fd < 0) { 722 | perror("usock"); 723 | return fd; 724 | } 725 | 726 | if (!inet_pton(AF_INET6, APMGR_V6_MCAST_GROUP, &group.ipv6mr_multiaddr.s6_addr)) 727 | perror("inet_pton(AF_INET6)"); 728 | 729 | /* Membership has to be added for every interface we listen on. */ 730 | vlist_for_each_element(&interfaces, iface, node) { 731 | group.ipv6mr_interface = iface->ifindex; 732 | if(setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&group, sizeof group) < 0) 733 | perror("setsockopt(IPV6_ADD_MEMBERSHIP)"); 734 | } 735 | 736 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes)) < 0) 737 | perror("setsockopt(IPV6_RECVPKTINFO)"); 738 | 739 | if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0) 740 | perror("setsockopt(SO_BROADCAST)"); 741 | 742 | return fd; 743 | } 744 | 745 | static void usteer_reload_timer(struct uloop_timeout *t) { 746 | /* Remove uloop descriptor */ 747 | if (remote_fd.fd && remote_fd.registered) { 748 | uloop_fd_delete(&remote_fd); 749 | close(remote_fd.fd); 750 | } 751 | 752 | if (config.ipv6) { 753 | remote_fd.fd = usteer_create_v6_socket(); 754 | remote_fd.cb = interface_recv_v6; 755 | } else { 756 | remote_fd.fd = usteer_create_v4_socket(); 757 | remote_fd.cb = interface_recv_v4; 758 | } 759 | 760 | if (remote_fd.fd < 0) 761 | return; 762 | 763 | uloop_fd_add(&remote_fd, ULOOP_READ); 764 | } 765 | 766 | int usteer_interface_init(void) 767 | { 768 | if (usteer_init_local_id()) 769 | return -1; 770 | 771 | remote_timer.cb = usteer_send_update_timer; 772 | remote_timer.cb(&remote_timer); 773 | 774 | reload_timer.cb = usteer_reload_timer; 775 | reload_timer.cb(&reload_timer); 776 | 777 | return 0; 778 | } 779 | -------------------------------------------------------------------------------- /ubus.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #ifdef linux 24 | #include 25 | #endif 26 | 27 | #include "usteer.h" 28 | #include "node.h" 29 | #include "event.h" 30 | 31 | static struct blob_buf b; 32 | static KVLIST(host_info, kvlist_blob_len); 33 | 34 | static void * 35 | blobmsg_open_table_mac(struct blob_buf *buf, uint8_t *addr) 36 | { 37 | char str[20]; 38 | sprintf(str, MAC_ADDR_FMT, MAC_ADDR_DATA(addr)); 39 | return blobmsg_open_table(buf, str); 40 | } 41 | 42 | static int 43 | usteer_ubus_get_clients(struct ubus_context *ctx, struct ubus_object *obj, 44 | struct ubus_request_data *req, const char *method, 45 | struct blob_attr *msg) 46 | { 47 | struct sta_info *si; 48 | struct sta *sta; 49 | void *_s, *_cur_n; 50 | 51 | blob_buf_init(&b, 0); 52 | avl_for_each_element(&stations, sta, avl) { 53 | _s = blobmsg_open_table_mac(&b, sta->addr); 54 | list_for_each_entry(si, &sta->nodes, list) { 55 | _cur_n = blobmsg_open_table(&b, usteer_node_name(si->node)); 56 | blobmsg_add_u8(&b, "connected", si->connected); 57 | blobmsg_add_u32(&b, "signal", si->signal); 58 | blobmsg_close_table(&b, _cur_n); 59 | } 60 | blobmsg_close_table(&b, _s); 61 | } 62 | ubus_send_reply(ctx, req, b.head); 63 | return 0; 64 | } 65 | 66 | static struct blobmsg_policy client_arg[] = { 67 | { .name = "address", .type = BLOBMSG_TYPE_STRING, }, 68 | }; 69 | 70 | static void 71 | usteer_ubus_add_stats(struct sta_info_stats *stats, const char *name) 72 | { 73 | void *s; 74 | 75 | s = blobmsg_open_table(&b, name); 76 | blobmsg_add_u32(&b, "requests", stats->requests); 77 | blobmsg_add_u32(&b, "blocked_cur", stats->blocked_cur); 78 | blobmsg_add_u32(&b, "blocked_total", stats->blocked_total); 79 | blobmsg_close_table(&b, s); 80 | } 81 | 82 | static int 83 | usteer_ubus_get_client_info(struct ubus_context *ctx, struct ubus_object *obj, 84 | struct ubus_request_data *req, const char *method, 85 | struct blob_attr *msg) 86 | { 87 | struct sta_info *si; 88 | struct sta *sta; 89 | struct blob_attr *mac_str; 90 | uint8_t *mac; 91 | void *_n, *_cur_n, *_s; 92 | int i; 93 | 94 | blobmsg_parse(client_arg, 1, &mac_str, blob_data(msg), blob_len(msg)); 95 | if (!mac_str) 96 | return UBUS_STATUS_INVALID_ARGUMENT; 97 | 98 | mac = (uint8_t *) ether_aton(blobmsg_data(mac_str)); 99 | if (!mac) 100 | return UBUS_STATUS_INVALID_ARGUMENT; 101 | 102 | sta = usteer_sta_get(mac, false); 103 | if (!sta) 104 | return UBUS_STATUS_NOT_FOUND; 105 | 106 | blob_buf_init(&b, 0); 107 | blobmsg_add_u8(&b, "2ghz", sta->seen_2ghz); 108 | blobmsg_add_u8(&b, "5ghz", sta->seen_5ghz); 109 | _n = blobmsg_open_table(&b, "nodes"); 110 | list_for_each_entry(si, &sta->nodes, list) { 111 | _cur_n = blobmsg_open_table(&b, usteer_node_name(si->node)); 112 | blobmsg_add_u8(&b, "connected", si->connected); 113 | blobmsg_add_u32(&b, "signal", si->signal); 114 | _s = blobmsg_open_table(&b, "stats"); 115 | for (i = 0; i < __EVENT_TYPE_MAX; i++) 116 | usteer_ubus_add_stats(&si->stats[EVENT_TYPE_PROBE], event_types[i]); 117 | blobmsg_close_table(&b, _s); 118 | blobmsg_close_table(&b, _cur_n); 119 | } 120 | blobmsg_close_table(&b, _n); 121 | 122 | ubus_send_reply(ctx, req, b.head); 123 | 124 | return 0; 125 | } 126 | 127 | enum cfg_type { 128 | CFG_BOOL, 129 | CFG_I32, 130 | CFG_U32, 131 | CFG_ARRAY_CB, 132 | CFG_STRING_CB, 133 | }; 134 | 135 | struct cfg_item { 136 | enum cfg_type type; 137 | union { 138 | bool *BOOL; 139 | uint32_t *U32; 140 | int32_t *I32; 141 | struct { 142 | void (*set)(struct blob_attr *data); 143 | void (*get)(struct blob_buf *buf); 144 | } CB; 145 | } ptr; 146 | }; 147 | 148 | #define __config_items \ 149 | _cfg(BOOL, syslog), \ 150 | _cfg(U32, debug_level), \ 151 | _cfg(BOOL, ipv6), \ 152 | _cfg(BOOL, local_mode), \ 153 | _cfg(U32, sta_block_timeout), \ 154 | _cfg(U32, local_sta_timeout), \ 155 | _cfg(U32, local_sta_update), \ 156 | _cfg(U32, max_neighbor_reports), \ 157 | _cfg(U32, max_retry_band), \ 158 | _cfg(U32, seen_policy_timeout), \ 159 | _cfg(U32, measurement_report_timeout), \ 160 | _cfg(U32, load_balancing_threshold), \ 161 | _cfg(U32, band_steering_threshold), \ 162 | _cfg(U32, remote_update_interval), \ 163 | _cfg(U32, remote_node_timeout), \ 164 | _cfg(BOOL, assoc_steering), \ 165 | _cfg(I32, min_connect_snr), \ 166 | _cfg(I32, min_snr), \ 167 | _cfg(U32, min_snr_kick_delay), \ 168 | _cfg(U32, steer_reject_timeout), \ 169 | _cfg(U32, roam_process_timeout), \ 170 | _cfg(I32, roam_scan_snr), \ 171 | _cfg(U32, roam_scan_tries), \ 172 | _cfg(U32, roam_scan_timeout), \ 173 | _cfg(U32, roam_scan_interval), \ 174 | _cfg(I32, roam_trigger_snr), \ 175 | _cfg(U32, roam_trigger_interval), \ 176 | _cfg(U32, roam_kick_delay), \ 177 | _cfg(U32, signal_diff_threshold), \ 178 | _cfg(U32, initial_connect_delay), \ 179 | _cfg(BOOL, load_kick_enabled), \ 180 | _cfg(U32, load_kick_threshold), \ 181 | _cfg(U32, load_kick_delay), \ 182 | _cfg(U32, load_kick_min_clients), \ 183 | _cfg(U32, load_kick_reason_code), \ 184 | _cfg(U32, band_steering_interval), \ 185 | _cfg(I32, band_steering_min_snr), \ 186 | _cfg(U32, link_measurement_interval), \ 187 | _cfg(ARRAY_CB, interfaces), \ 188 | _cfg(STRING_CB, node_up_script), \ 189 | _cfg(ARRAY_CB, event_log_types), \ 190 | _cfg(ARRAY_CB, ssid_list) 191 | 192 | enum cfg_items { 193 | #define _cfg(_type, _name) CFG_##_name 194 | __config_items, 195 | #undef _cfg 196 | __CFG_MAX, 197 | }; 198 | 199 | static const struct blobmsg_policy config_policy[__CFG_MAX] = { 200 | #define _cfg_policy(_type, _name) [CFG_##_name] = { .name = #_name, .type = BLOBMSG_TYPE_ ## _type } 201 | #define _cfg_policy_BOOL(_name) _cfg_policy(BOOL, _name) 202 | #define _cfg_policy_U32(_name) _cfg_policy(INT32, _name) 203 | #define _cfg_policy_I32(_name) _cfg_policy(INT32, _name) 204 | #define _cfg_policy_ARRAY_CB(_name) _cfg_policy(ARRAY, _name) 205 | #define _cfg_policy_STRING_CB(_name) _cfg_policy(STRING, _name) 206 | #define _cfg(_type, _name) _cfg_policy_##_type(_name) 207 | __config_items, 208 | #undef _cfg 209 | }; 210 | 211 | static const struct cfg_item config_data[__CFG_MAX] = { 212 | #define _cfg_data_BOOL(_name) .ptr.BOOL = &config._name 213 | #define _cfg_data_U32(_name) .ptr.U32 = &config._name 214 | #define _cfg_data_I32(_name) .ptr.I32 = &config._name 215 | #define _cfg_data_ARRAY_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name } 216 | #define _cfg_data_STRING_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name } 217 | #define _cfg(_type, _name) [CFG_##_name] = { .type = CFG_##_type, _cfg_data_##_type(_name) } 218 | __config_items, 219 | #undef _cfg 220 | }; 221 | 222 | static int 223 | usteer_ubus_get_config(struct ubus_context *ctx, struct ubus_object *obj, 224 | struct ubus_request_data *req, const char *method, 225 | struct blob_attr *msg) 226 | { 227 | int i; 228 | 229 | blob_buf_init(&b, 0); 230 | for (i = 0; i < __CFG_MAX; i++) { 231 | switch(config_data[i].type) { 232 | case CFG_BOOL: 233 | blobmsg_add_u8(&b, config_policy[i].name, 234 | *config_data[i].ptr.BOOL); 235 | break; 236 | case CFG_I32: 237 | case CFG_U32: 238 | blobmsg_add_u32(&b, config_policy[i].name, 239 | *config_data[i].ptr.U32); 240 | break; 241 | case CFG_ARRAY_CB: 242 | case CFG_STRING_CB: 243 | config_data[i].ptr.CB.get(&b); 244 | break; 245 | } 246 | } 247 | ubus_send_reply(ctx, req, b.head); 248 | return 0; 249 | } 250 | 251 | static int 252 | usteer_ubus_set_config(struct ubus_context *ctx, struct ubus_object *obj, 253 | struct ubus_request_data *req, const char *method, 254 | struct blob_attr *msg) 255 | { 256 | struct blob_attr *tb[__CFG_MAX]; 257 | int i; 258 | 259 | if (!strcmp(method, "set_config")) 260 | usteer_init_defaults(); 261 | 262 | blobmsg_parse(config_policy, __CFG_MAX, tb, blob_data(msg), blob_len(msg)); 263 | for (i = 0; i < __CFG_MAX; i++) { 264 | switch(config_data[i].type) { 265 | case CFG_BOOL: 266 | if (!tb[i]) 267 | continue; 268 | 269 | *config_data[i].ptr.BOOL = blobmsg_get_u8(tb[i]); 270 | break; 271 | case CFG_I32: 272 | case CFG_U32: 273 | if (!tb[i]) 274 | continue; 275 | 276 | *config_data[i].ptr.U32 = blobmsg_get_u32(tb[i]); 277 | break; 278 | case CFG_ARRAY_CB: 279 | case CFG_STRING_CB: 280 | config_data[i].ptr.CB.set(tb[i]); 281 | break; 282 | } 283 | } 284 | 285 | usteer_interface_init(); 286 | 287 | return 0; 288 | } 289 | 290 | void usteer_dump_node(struct blob_buf *buf, struct usteer_node *node) 291 | { 292 | void *c, *roam_events; 293 | 294 | c = blobmsg_open_table(buf, usteer_node_name(node)); 295 | blobmsg_printf(buf, "bssid", MAC_ADDR_FMT, MAC_ADDR_DATA(node->bssid)); 296 | blobmsg_add_string(buf, "ssid", node->ssid); 297 | blobmsg_add_u32(buf, "freq", node->freq); 298 | blobmsg_add_u32(buf, "n_assoc", node->n_assoc); 299 | blobmsg_add_u32(buf, "noise", node->noise); 300 | blobmsg_add_u32(buf, "load", node->load); 301 | blobmsg_add_u32(buf, "max_assoc", node->max_assoc); 302 | 303 | roam_events = blobmsg_open_table(buf, "roam_events"); 304 | blobmsg_add_u32(buf, "source", node->roam_events.source); 305 | blobmsg_add_u32(buf, "target", node->roam_events.target); 306 | blobmsg_close_table(buf, roam_events); 307 | 308 | if (node->rrm_nr) 309 | blobmsg_add_field(buf, BLOBMSG_TYPE_ARRAY, "rrm_nr", 310 | blobmsg_data(node->rrm_nr), 311 | blobmsg_data_len(node->rrm_nr)); 312 | if (node->node_info) 313 | blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "node_info", 314 | blob_data(node->node_info), 315 | blob_len(node->node_info)); 316 | 317 | blobmsg_close_table(buf, c); 318 | } 319 | 320 | void usteer_dump_host(struct blob_buf *buf, struct usteer_remote_host *host) 321 | { 322 | void *c; 323 | 324 | c = blobmsg_open_table(buf, host->addr); 325 | blobmsg_add_u32(buf, "id", (uint32_t)(uintptr_t)host->avl.key); 326 | if (host->host_info) 327 | blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "host_info", 328 | blobmsg_data(host->host_info), 329 | blobmsg_len(host->host_info)); 330 | blobmsg_close_table(buf, c); 331 | } 332 | 333 | static int 334 | usteer_ubus_local_info(struct ubus_context *ctx, struct ubus_object *obj, 335 | struct ubus_request_data *req, const char *method, 336 | struct blob_attr *msg) 337 | { 338 | struct usteer_node *node; 339 | 340 | blob_buf_init(&b, 0); 341 | 342 | for_each_local_node(node) 343 | usteer_dump_node(&b, node); 344 | 345 | ubus_send_reply(ctx, req, b.head); 346 | 347 | return 0; 348 | } 349 | 350 | static int 351 | usteer_ubus_remote_hosts(struct ubus_context *ctx, struct ubus_object *obj, 352 | struct ubus_request_data *req, const char *method, 353 | struct blob_attr *msg) 354 | { 355 | struct usteer_remote_host *host; 356 | 357 | blob_buf_init(&b, 0); 358 | 359 | avl_for_each_element(&remote_hosts, host, avl) 360 | usteer_dump_host(&b, host); 361 | 362 | ubus_send_reply(ctx, req, b.head); 363 | 364 | return 0; 365 | } 366 | 367 | static int 368 | usteer_ubus_remote_info(struct ubus_context *ctx, struct ubus_object *obj, 369 | struct ubus_request_data *req, const char *method, 370 | struct blob_attr *msg) 371 | { 372 | struct usteer_remote_node *rn; 373 | 374 | blob_buf_init(&b, 0); 375 | 376 | for_each_remote_node(rn) 377 | usteer_dump_node(&b, &rn->node); 378 | 379 | ubus_send_reply(ctx, req, b.head); 380 | 381 | return 0; 382 | } 383 | 384 | static const char *usteer_get_roam_sm_name(enum roam_trigger_state state) 385 | { 386 | switch (state) { 387 | case ROAM_TRIGGER_IDLE: 388 | return "ROAM_TRIGGER_IDLE"; 389 | case ROAM_TRIGGER_SCAN: 390 | return "ROAM_TRIGGER_SCAN"; 391 | case ROAM_TRIGGER_SCAN_DONE: 392 | return "ROAM_TRIGGER_SCAN_DONE"; 393 | } 394 | return "N/A"; 395 | } 396 | 397 | static int 398 | usteer_ubus_get_connected_clients(struct ubus_context *ctx, struct ubus_object *obj, 399 | struct ubus_request_data *req, const char *method, 400 | struct blob_attr *msg) 401 | { 402 | struct usteer_measurement_report *mr; 403 | struct usteer_node *node; 404 | struct sta_info *si; 405 | void *n, *s, *t, *a; 406 | 407 | blob_buf_init(&b, 0); 408 | 409 | for_each_local_node(node) { 410 | n = blobmsg_open_table(&b, usteer_node_name(node)); 411 | 412 | list_for_each_entry(si, &node->sta_info, node_list) { 413 | if (si->connected != STA_CONNECTED) 414 | continue; 415 | 416 | s = blobmsg_open_table_mac(&b, si->sta->addr); 417 | blobmsg_add_u32(&b, "signal", si->signal); 418 | blobmsg_add_u64(&b, "created", current_time - si->created); 419 | blobmsg_add_u64(&b, "connected", current_time - si->connected_since); 420 | 421 | t = blobmsg_open_table(&b, "snr-kick"); 422 | blobmsg_add_u32(&b, "seen-below", si->below_min_snr); 423 | blobmsg_close_table(&b, t); 424 | 425 | t = blobmsg_open_table(&b, "roam-state-machine"); 426 | blobmsg_add_string(&b, "state",usteer_get_roam_sm_name(si->roam_state)); 427 | blobmsg_add_u32(&b, "tries", si->roam_tries); 428 | blobmsg_add_u64(&b, "event", si->roam_event ? current_time - si->roam_event : 0); 429 | blobmsg_add_u32(&b, "kick-count", si->kick_count); 430 | blobmsg_add_u64(&b, "last-kick", si->roam_kick ? current_time - si->roam_kick : 0); 431 | blobmsg_add_u64(&b, "scan_start", si->roam_scan_start ? current_time - si->roam_scan_start : 0); 432 | blobmsg_add_u64(&b, "scan_timeout_start", si->roam_scan_timeout_start ? current_time - si->roam_scan_timeout_start : 0); 433 | blobmsg_close_table(&b, t); 434 | 435 | t = blobmsg_open_table(&b, "bss-transition-response"); 436 | blobmsg_add_u32(&b, "status-code", si->bss_transition_response.status_code); 437 | blobmsg_add_u64(&b, "age", si->bss_transition_response.timestamp ? current_time - si->bss_transition_response.timestamp : 0); 438 | blobmsg_close_table(&b, t); 439 | 440 | /* Beacon measurement modes */ 441 | a = blobmsg_open_array(&b, "beacon-measurement-modes"); 442 | if (usteer_sta_supports_beacon_measurement_mode(si, BEACON_MEASUREMENT_PASSIVE)) 443 | blobmsg_add_string(&b, "", "PASSIVE"); 444 | if (usteer_sta_supports_beacon_measurement_mode(si, BEACON_MEASUREMENT_ACTIVE)) 445 | blobmsg_add_string(&b, "", "ACTIVE"); 446 | if (usteer_sta_supports_beacon_measurement_mode(si, BEACON_MEASUREMENT_TABLE)) 447 | blobmsg_add_string(&b, "", "TABLE"); 448 | blobmsg_close_array(&b, a); 449 | 450 | /* Link-Measurement support */ 451 | blobmsg_add_u8(&b, "link-measurement", usteer_sta_supports_link_measurement(si)); 452 | 453 | /* BSS-Transition support */ 454 | blobmsg_add_u8(&b, "bss-transition-management", si->bss_transition); 455 | 456 | /* MBO support */ 457 | blobmsg_add_u8(&b, "multi-band-operation", si->mbo); 458 | 459 | /* Measurements */ 460 | a = blobmsg_open_array(&b, "measurements"); 461 | list_for_each_entry(mr, &si->sta->measurements, sta_list) { 462 | t = blobmsg_open_table(&b, ""); 463 | blobmsg_add_string(&b, "node", usteer_node_name(mr->node)); 464 | blobmsg_add_u32(&b, "rcpi", mr->rcpi); 465 | blobmsg_add_u32(&b, "rsni", mr->rsni); 466 | blobmsg_add_u32(&b, "rssi", usteer_measurement_get_rssi(mr)); 467 | blobmsg_add_u64(&b, "age", current_time - mr->timestamp); 468 | blobmsg_close_table(&b, t); 469 | } 470 | blobmsg_close_array(&b, a); 471 | 472 | blobmsg_close_table(&b, s); 473 | } 474 | 475 | blobmsg_close_table(&b, n); 476 | } 477 | 478 | ubus_send_reply(ctx, req, b.head); 479 | 480 | return 0; 481 | } 482 | 483 | enum { 484 | NODE_DATA_NODE, 485 | NODE_DATA_VALUES, 486 | __NODE_DATA_MAX, 487 | }; 488 | 489 | static const struct blobmsg_policy set_node_data_policy[] = { 490 | [NODE_DATA_NODE] = { "node", BLOBMSG_TYPE_STRING }, 491 | [NODE_DATA_VALUES] = { "data", BLOBMSG_TYPE_TABLE }, 492 | }; 493 | 494 | static const struct blobmsg_policy del_node_data_policy[] = { 495 | [NODE_DATA_NODE] = { "node", BLOBMSG_TYPE_STRING }, 496 | [NODE_DATA_VALUES] = { "names", BLOBMSG_TYPE_ARRAY }, 497 | }; 498 | 499 | static void 500 | usteer_update_kvlist_data(struct kvlist *kv, struct blob_attr *data, 501 | bool delete) 502 | { 503 | struct blob_attr *cur; 504 | int rem; 505 | 506 | blobmsg_for_each_attr(cur, data, rem) { 507 | if (delete) 508 | kvlist_delete(kv, blobmsg_get_string(cur)); 509 | else 510 | kvlist_set(kv, blobmsg_name(cur), cur); 511 | } 512 | } 513 | 514 | static void 515 | usteer_update_kvlist_blob(struct blob_attr **dest, struct kvlist *kv) 516 | { 517 | struct blob_attr *val; 518 | const char *name; 519 | 520 | blob_buf_init(&b, 0); 521 | kvlist_for_each(kv, name, val) 522 | blobmsg_add_field(&b, blobmsg_type(val), name, 523 | blobmsg_data(val), blobmsg_len(val)); 524 | 525 | val = b.head; 526 | if (!blobmsg_len(val)) 527 | val = NULL; 528 | 529 | usteer_node_set_blob(dest, val); 530 | } 531 | 532 | static int 533 | usteer_ubus_update_node_data(struct ubus_context *ctx, struct ubus_object *obj, 534 | struct ubus_request_data *req, const char *method, 535 | struct blob_attr *msg) 536 | { 537 | const struct blobmsg_policy *policy; 538 | struct blob_attr *tb[__NODE_DATA_MAX]; 539 | struct usteer_local_node *ln; 540 | struct blob_attr *val; 541 | const char *name; 542 | bool delete; 543 | 544 | delete = !strncmp(method, "del", 3); 545 | policy = delete ? del_node_data_policy : set_node_data_policy; 546 | 547 | blobmsg_parse(policy, __NODE_DATA_MAX, tb, blob_data(msg), blob_len(msg)); 548 | if (!tb[NODE_DATA_NODE] || !tb[NODE_DATA_VALUES]) 549 | return UBUS_STATUS_INVALID_ARGUMENT; 550 | 551 | name = blobmsg_get_string(tb[NODE_DATA_NODE]); 552 | val = tb[NODE_DATA_VALUES]; 553 | if (delete && blobmsg_check_array(val, BLOBMSG_TYPE_STRING) < 0) 554 | return UBUS_STATUS_INVALID_ARGUMENT; 555 | 556 | if (strcmp(name, "*") != 0) { 557 | ln = avl_find_element(&local_nodes, name, ln, node.avl); 558 | if (!ln) 559 | return UBUS_STATUS_NOT_FOUND; 560 | 561 | usteer_update_kvlist_data(&ln->node_info, val, delete); 562 | usteer_update_kvlist_blob(&ln->node.node_info, &ln->node_info); 563 | 564 | return 0; 565 | } 566 | 567 | usteer_update_kvlist_data(&host_info, val, delete); 568 | usteer_update_kvlist_blob(&host_info_blob, &host_info); 569 | 570 | return 0; 571 | } 572 | 573 | static const struct ubus_method usteer_methods[] = { 574 | UBUS_METHOD_NOARG("local_info", usteer_ubus_local_info), 575 | UBUS_METHOD_NOARG("remote_hosts", usteer_ubus_remote_hosts), 576 | UBUS_METHOD_NOARG("remote_info", usteer_ubus_remote_info), 577 | UBUS_METHOD_NOARG("connected_clients", usteer_ubus_get_connected_clients), 578 | UBUS_METHOD_NOARG("get_clients", usteer_ubus_get_clients), 579 | UBUS_METHOD("get_client_info", usteer_ubus_get_client_info, client_arg), 580 | UBUS_METHOD_NOARG("get_config", usteer_ubus_get_config), 581 | UBUS_METHOD("set_config", usteer_ubus_set_config, config_policy), 582 | UBUS_METHOD("update_config", usteer_ubus_set_config, config_policy), 583 | UBUS_METHOD("set_node_data", usteer_ubus_update_node_data, set_node_data_policy), 584 | UBUS_METHOD("delete_node_data", usteer_ubus_update_node_data, del_node_data_policy), 585 | }; 586 | 587 | static struct ubus_object_type usteer_obj_type = 588 | UBUS_OBJECT_TYPE("usteer", usteer_methods); 589 | 590 | struct ubus_object usteer_obj = { 591 | .name = "usteer", 592 | .type = &usteer_obj_type, 593 | .methods = usteer_methods, 594 | .n_methods = ARRAY_SIZE(usteer_methods), 595 | }; 596 | 597 | static bool 598 | usteer_add_nr_entry(struct usteer_node *ln, struct usteer_node *node) 599 | { 600 | struct blobmsg_policy policy[3] = { 601 | { .type = BLOBMSG_TYPE_STRING }, 602 | { .type = BLOBMSG_TYPE_STRING }, 603 | { .type = BLOBMSG_TYPE_STRING }, 604 | }; 605 | struct blob_attr *tb[3]; 606 | 607 | if (!node->rrm_nr) 608 | return false; 609 | 610 | if (strcmp(ln->ssid, node->ssid) != 0) 611 | return false; 612 | 613 | if (!usteer_policy_node_below_max_assoc(node)) 614 | return false; 615 | 616 | blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb, 617 | blobmsg_data(node->rrm_nr), 618 | blobmsg_data_len(node->rrm_nr)); 619 | if (!tb[2]) 620 | return false; 621 | 622 | blobmsg_add_field(&b, BLOBMSG_TYPE_STRING, "", 623 | blobmsg_data(tb[2]), 624 | blobmsg_data_len(tb[2])); 625 | 626 | return true; 627 | } 628 | 629 | static void 630 | usteer_ubus_disassoc_add_neighbor(struct sta_info *si, struct usteer_node *node) 631 | { 632 | void *c; 633 | 634 | c = blobmsg_open_array(&b, "neighbors"); 635 | usteer_add_nr_entry(si->node, node); 636 | blobmsg_close_array(&b, c); 637 | } 638 | 639 | static void 640 | usteer_ubus_disassoc_add_neighbors(struct sta_info *si) 641 | { 642 | struct usteer_node *node, *last_remote_neighbor = NULL; 643 | int i = 0; 644 | void *c; 645 | 646 | c = blobmsg_open_array(&b, "neighbors"); 647 | for_each_local_node(node) { 648 | if (i >= config.max_neighbor_reports) 649 | break; 650 | if (si->node == node) 651 | continue; 652 | if (usteer_add_nr_entry(si->node, node)) 653 | i++; 654 | } 655 | 656 | while (i < config.max_neighbor_reports) { 657 | node = usteer_node_get_next_neighbor(si->node, last_remote_neighbor); 658 | if (!node) { 659 | /* No more nodes available */ 660 | break; 661 | } 662 | 663 | last_remote_neighbor = node; 664 | if (usteer_add_nr_entry(si->node, node)) 665 | i++; 666 | } 667 | blobmsg_close_array(&b, c); 668 | } 669 | 670 | int usteer_ubus_bss_transition_request(struct sta_info *si, 671 | uint8_t dialog_token, 672 | bool disassoc_imminent, 673 | bool abridged, 674 | uint8_t validity_period, 675 | struct usteer_node *target_node) 676 | { 677 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 678 | 679 | blob_buf_init(&b, 0); 680 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 681 | blobmsg_add_u32(&b, "dialog_token", dialog_token); 682 | blobmsg_add_u8(&b, "disassociation_imminent", disassoc_imminent); 683 | blobmsg_add_u8(&b, "abridged", abridged); 684 | blobmsg_add_u32(&b, "validity_period", validity_period); 685 | if (!target_node) { 686 | usteer_ubus_disassoc_add_neighbors(si); 687 | } else { 688 | usteer_ubus_disassoc_add_neighbor(si, target_node); 689 | } 690 | return ubus_invoke(ubus_ctx, ln->obj_id, "bss_transition_request", b.head, NULL, 0, 100); 691 | } 692 | 693 | int usteer_ubus_band_steering_request(struct sta_info *si) 694 | { 695 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 696 | struct usteer_node *node; 697 | void *c; 698 | 699 | blob_buf_init(&b, 0); 700 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 701 | blobmsg_add_u32(&b, "dialog_token", 0); 702 | blobmsg_add_u8(&b, "disassociation_imminent", false); 703 | blobmsg_add_u8(&b, "abridged", false); 704 | blobmsg_add_u32(&b, "validity_period", 100); 705 | 706 | c = blobmsg_open_array(&b, "neighbors"); 707 | for_each_local_node(node) { 708 | if (!usteer_band_steering_is_target(ln, node)) 709 | continue; 710 | 711 | usteer_add_nr_entry(si->node, node); 712 | } 713 | blobmsg_close_array(&b, c); 714 | 715 | return ubus_invoke(ubus_ctx, ln->obj_id, "bss_transition_request", b.head, NULL, 0, 100); 716 | } 717 | 718 | int usteer_ubus_trigger_link_measurement(struct sta_info *si) 719 | { 720 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 721 | 722 | if (!usteer_sta_supports_link_measurement(si)) 723 | return 0; 724 | 725 | blob_buf_init(&b, 0); 726 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 727 | blobmsg_add_u32(&b, "tx-power-used", 5); 728 | blobmsg_add_u32(&b, "tx-power-max", 10); 729 | return ubus_invoke(ubus_ctx, ln->obj_id, "link_measurement_req", b.head, NULL, 0, 100); 730 | } 731 | 732 | int usteer_ubus_trigger_client_scan(struct sta_info *si) 733 | { 734 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 735 | 736 | if (!usteer_sta_supports_beacon_measurement_mode(si, BEACON_MEASUREMENT_ACTIVE)) { 737 | MSG(DEBUG, "STA does not support beacon measurement sta=" MAC_ADDR_FMT "\n", MAC_ADDR_DATA(si->sta->addr)); 738 | return 0; 739 | } 740 | 741 | si->scan_band = !si->scan_band; 742 | 743 | blob_buf_init(&b, 0); 744 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 745 | blobmsg_add_string(&b, "ssid", si->node->ssid); 746 | blobmsg_add_u32(&b, "mode", BEACON_MEASUREMENT_ACTIVE); 747 | blobmsg_add_u32(&b, "duration", config.roam_scan_interval / 100); 748 | blobmsg_add_u32(&b, "channel", 0); 749 | blobmsg_add_u32(&b, "op_class", si->scan_band ? 1 : 12); 750 | return ubus_invoke(ubus_ctx, ln->obj_id, "rrm_beacon_req", b.head, NULL, 0, 100); 751 | } 752 | 753 | void usteer_ubus_kick_client(struct sta_info *si) 754 | { 755 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 756 | 757 | blob_buf_init(&b, 0); 758 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 759 | blobmsg_add_u32(&b, "reason", config.load_kick_reason_code); 760 | blobmsg_add_u8(&b, "deauth", 1); 761 | ubus_invoke(ubus_ctx, ln->obj_id, "del_client", b.head, NULL, 0, 100); 762 | usteer_sta_disconnected(si); 763 | si->kick_count++; 764 | si->roam_kick = current_time; 765 | } 766 | 767 | void usteer_ubus_init(struct ubus_context *ctx) 768 | { 769 | ubus_add_object(ctx, &usteer_obj); 770 | } 771 | -------------------------------------------------------------------------------- /local_node.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software; you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation; either version 2 of the License. 5 | * 6 | * This program is distributed in the hope that it will be useful, 7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | * GNU General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU General Public License 12 | * along with this program; if not, write to the Free Software 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 14 | * 15 | * Copyright (C) 2020 embedd.ch 16 | * Copyright (C) 2020 Felix Fietkau 17 | * Copyright (C) 2020 John Crispin 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #ifdef linux 24 | #include 25 | #endif 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include "usteer.h" 32 | #include "node.h" 33 | 34 | AVL_TREE(local_nodes, avl_strcmp, false, NULL); 35 | static struct blob_buf b; 36 | static char *node_up_script; 37 | 38 | static void 39 | usteer_local_node_state_reset(struct usteer_local_node *ln) 40 | { 41 | if (ln->req_state == REQ_IDLE) 42 | return; 43 | 44 | ubus_abort_request(ubus_ctx, &ln->req); 45 | uloop_timeout_cancel(&ln->req_timer); 46 | ln->req_state = REQ_IDLE; 47 | } 48 | 49 | static void 50 | usteer_local_node_pending_bss_tm_free(struct usteer_local_node *ln) 51 | { 52 | struct usteer_bss_tm_query *query, *tmp; 53 | 54 | list_for_each_entry_safe(query, tmp, &ln->bss_tm_queries, list) { 55 | list_del(&query->list); 56 | free(query); 57 | } 58 | } 59 | 60 | static void 61 | usteer_free_node(struct ubus_context *ctx, struct usteer_local_node *ln) 62 | { 63 | struct usteer_node_handler *h; 64 | 65 | list_for_each_entry(h, &node_handlers, list) { 66 | if (!h->free_node) 67 | continue; 68 | h->free_node(&ln->node); 69 | } 70 | 71 | usteer_local_node_pending_bss_tm_free(ln); 72 | usteer_local_node_state_reset(ln); 73 | usteer_sta_node_cleanup(&ln->node); 74 | usteer_measurement_report_node_cleanup(&ln->node); 75 | uloop_timeout_cancel(&ln->update); 76 | uloop_timeout_cancel(&ln->bss_tm_queries_timeout); 77 | avl_delete(&local_nodes, &ln->node.avl); 78 | ubus_unregister_subscriber(ctx, &ln->ev); 79 | kvlist_free(&ln->node_info); 80 | free(ln); 81 | } 82 | 83 | struct usteer_local_node *usteer_local_node_by_bssid(uint8_t *bssid) { 84 | struct usteer_local_node *ln; 85 | struct usteer_node *n; 86 | 87 | for_each_local_node(n) { 88 | ln = container_of(n, struct usteer_local_node, node); 89 | if (!memcmp(n->bssid, bssid, 6)) 90 | return ln; 91 | } 92 | 93 | return NULL; 94 | } 95 | 96 | static void 97 | usteer_handle_remove(struct ubus_context *ctx, struct ubus_subscriber *s, 98 | uint32_t id) 99 | { 100 | struct usteer_local_node *ln = container_of(s, struct usteer_local_node, ev); 101 | 102 | usteer_free_node(ctx, ln); 103 | } 104 | 105 | static int 106 | usteer_handle_bss_tm_query(struct usteer_local_node *ln, struct blob_attr *msg) 107 | { 108 | enum { 109 | BSS_TM_QUERY_ADDRESS, 110 | BSS_TM_QUERY_DIALOG_TOKEN, 111 | BSS_TM_QUERY_CANDIDATE_LIST, 112 | __BSS_TM_QUERY_MAX 113 | }; 114 | struct blobmsg_policy policy[__BSS_TM_QUERY_MAX] = { 115 | [BSS_TM_QUERY_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 116 | [BSS_TM_QUERY_DIALOG_TOKEN] = { .name = "dialog-token", .type = BLOBMSG_TYPE_INT8 }, 117 | [BSS_TM_QUERY_CANDIDATE_LIST] = { .name = "candidate-list", .type = BLOBMSG_TYPE_STRING }, 118 | }; 119 | struct blob_attr *tb[__BSS_TM_QUERY_MAX]; 120 | struct usteer_bss_tm_query *query; 121 | uint8_t *sta_addr; 122 | 123 | blobmsg_parse(policy, __BSS_TM_QUERY_MAX, tb, blob_data(msg), blob_len(msg)); 124 | 125 | if (!tb[BSS_TM_QUERY_ADDRESS] || !tb[BSS_TM_QUERY_DIALOG_TOKEN]) 126 | return 0; 127 | 128 | query = calloc(1, sizeof(*query)); 129 | if (!query) 130 | return 0; 131 | 132 | query->dialog_token = blobmsg_get_u8(tb[BSS_TM_QUERY_DIALOG_TOKEN]); 133 | 134 | sta_addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BSS_TM_QUERY_ADDRESS])); 135 | if (!sta_addr) 136 | return 0; 137 | 138 | memcpy(query->sta_addr, sta_addr, 6); 139 | 140 | list_add(&query->list, &ln->bss_tm_queries); 141 | uloop_timeout_set(&ln->bss_tm_queries_timeout, 1); 142 | 143 | return 1; 144 | } 145 | 146 | static int 147 | usteer_handle_bss_tm_response(struct usteer_local_node *ln, struct blob_attr *msg) 148 | { 149 | enum { 150 | BSS_TM_RESPONSE_ADDRESS, 151 | BSS_TM_RESPONSE_STATUS_CODE, 152 | __BSS_TM_RESPONSE_MAX 153 | }; 154 | struct blobmsg_policy policy[__BSS_TM_RESPONSE_MAX] = { 155 | [BSS_TM_RESPONSE_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 156 | [BSS_TM_RESPONSE_STATUS_CODE] = { .name = "status-code", .type = BLOBMSG_TYPE_INT8 }, 157 | }; 158 | struct blob_attr *tb[__BSS_TM_RESPONSE_MAX]; 159 | struct sta_info *si; 160 | struct sta *sta; 161 | uint8_t *sta_addr; 162 | 163 | blobmsg_parse(policy, __BSS_TM_RESPONSE_MAX, tb, blob_data(msg), blob_len(msg)); 164 | 165 | if (!tb[BSS_TM_RESPONSE_ADDRESS] || !tb[BSS_TM_RESPONSE_STATUS_CODE]) 166 | return 0; 167 | 168 | sta_addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BSS_TM_RESPONSE_ADDRESS])); 169 | if (!sta_addr) 170 | return 0; 171 | 172 | sta = usteer_sta_get(sta_addr, false); 173 | if (!sta) 174 | return 0; 175 | 176 | si = usteer_sta_info_get(sta, &ln->node, false); 177 | if (!si) 178 | return 0; 179 | 180 | si->bss_transition_response.status_code = blobmsg_get_u8(tb[BSS_TM_RESPONSE_STATUS_CODE]); 181 | si->bss_transition_response.timestamp = current_time; 182 | 183 | if (si->bss_transition_response.status_code) { 184 | /* Cancel imminent kick in case BSS transition was rejected */ 185 | si->kick_time = 0; 186 | } 187 | 188 | return 0; 189 | } 190 | 191 | static int 192 | usteer_local_node_handle_beacon_report(struct usteer_local_node *ln, struct blob_attr *msg) 193 | { 194 | enum { 195 | BR_ADDRESS, 196 | BR_BSSID, 197 | BR_RCPI, 198 | BR_RSNI, 199 | __BR_MAX 200 | }; 201 | struct blobmsg_policy policy[__BR_MAX] = { 202 | [BR_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 203 | [BR_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING }, 204 | [BR_RCPI] = { .name = "rcpi", .type = BLOBMSG_TYPE_INT16 }, 205 | [BR_RSNI] = { .name = "rsni", .type = BLOBMSG_TYPE_INT16 }, 206 | }; 207 | struct blob_attr *tb[__BR_MAX]; 208 | struct usteer_node *node; 209 | uint8_t *addr; 210 | struct sta *sta; 211 | 212 | blobmsg_parse(policy, __BR_MAX, tb, blob_data(msg), blob_len(msg)); 213 | if (!tb[BR_ADDRESS] || !tb[BR_BSSID] || !tb[BR_RCPI] || !tb[BR_RSNI]) 214 | return 0; 215 | 216 | addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BR_ADDRESS])); 217 | if (!addr) 218 | return 0; 219 | 220 | sta = usteer_sta_get(addr, false); 221 | if (!sta) 222 | return 0; 223 | 224 | addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BR_BSSID])); 225 | if (!addr) 226 | return 0; 227 | 228 | node = usteer_node_by_bssid(addr); 229 | if (!node) 230 | return 0; 231 | 232 | usteer_measurement_report_add(sta, node, 233 | (uint8_t)blobmsg_get_u16(tb[BR_RCPI]), 234 | (uint8_t)blobmsg_get_u16(tb[BR_RSNI]), 235 | current_time); 236 | return 0; 237 | } 238 | 239 | static int 240 | usteer_local_node_handle_link_measurement_report(struct usteer_local_node *ln, struct blob_attr *msg) 241 | { 242 | enum { 243 | LMR_ADDRESS, 244 | LMR_RCPI, 245 | LMR_RSNI, 246 | __LMR_MAX 247 | }; 248 | struct blobmsg_policy policy[__LMR_MAX] = { 249 | [LMR_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 250 | [LMR_RCPI] = { .name = "rcpi", .type = BLOBMSG_TYPE_INT16 }, 251 | [LMR_RSNI] = { .name = "rsni", .type = BLOBMSG_TYPE_INT16 }, 252 | }; 253 | struct blob_attr *tb[__LMR_MAX]; 254 | uint8_t *addr; 255 | struct sta *sta; 256 | 257 | blobmsg_parse(policy, __LMR_MAX, tb, blob_data(msg), blob_len(msg)); 258 | if (!tb[LMR_ADDRESS] || !tb[LMR_RCPI] || !tb[LMR_RSNI]) 259 | return 0; 260 | 261 | addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[LMR_ADDRESS])); 262 | if (!addr) 263 | return 0; 264 | 265 | sta = usteer_sta_get(addr, false); 266 | if (!sta) 267 | return 0; 268 | 269 | usteer_measurement_report_add(sta, &ln->node, 270 | (uint8_t)blobmsg_get_u16(tb[LMR_RCPI]), 271 | (uint8_t)blobmsg_get_u16(tb[LMR_RSNI]), 272 | current_time); 273 | return 0; 274 | } 275 | 276 | static int 277 | usteer_handle_event(struct ubus_context *ctx, struct ubus_object *obj, 278 | struct ubus_request_data *req, const char *method, 279 | struct blob_attr *msg) 280 | { 281 | enum { 282 | EVENT_ADDR, 283 | EVENT_SIGNAL, 284 | EVENT_TARGET, 285 | EVENT_FREQ, 286 | __EVENT_MAX 287 | }; 288 | struct blobmsg_policy policy[__EVENT_MAX] = { 289 | [EVENT_ADDR] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 290 | [EVENT_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 }, 291 | [EVENT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, 292 | [EVENT_FREQ] = { .name = "freq", .type = BLOBMSG_TYPE_INT32 }, 293 | }; 294 | enum usteer_event_type ev_type = __EVENT_TYPE_MAX; 295 | struct blob_attr *tb[__EVENT_MAX]; 296 | struct usteer_local_node *ln; 297 | struct usteer_node *node; 298 | int signal = NO_SIGNAL; 299 | int freq = 0; 300 | const char *addr_str; 301 | const uint8_t *addr; 302 | int i; 303 | bool ret; 304 | 305 | usteer_update_time(); 306 | 307 | ln = container_of(obj, struct usteer_local_node, ev.obj); 308 | 309 | if(!strcmp(method, "bss-transition-query")) { 310 | return usteer_handle_bss_tm_query(ln, msg); 311 | } else if(!strcmp(method, "bss-transition-response")) { 312 | return usteer_handle_bss_tm_response(ln, msg); 313 | } else if(!strcmp(method, "beacon-report")) { 314 | return usteer_local_node_handle_beacon_report(ln, msg); 315 | } else if(!strcmp(method, "link-measurement-report")) { 316 | return usteer_local_node_handle_link_measurement_report(ln, msg); 317 | } 318 | 319 | for (i = 0; i < ARRAY_SIZE(event_types); i++) { 320 | if (strcmp(method, event_types[i]) != 0) 321 | continue; 322 | 323 | ev_type = i; 324 | break; 325 | } 326 | 327 | ln = container_of(obj, struct usteer_local_node, ev.obj); 328 | node = &ln->node; 329 | blobmsg_parse(policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg)); 330 | if (!tb[EVENT_ADDR] || !tb[EVENT_FREQ]) 331 | return UBUS_STATUS_INVALID_ARGUMENT; 332 | 333 | if (tb[EVENT_SIGNAL]) 334 | signal = (int32_t) blobmsg_get_u32(tb[EVENT_SIGNAL]); 335 | 336 | if (tb[EVENT_FREQ]) 337 | freq = blobmsg_get_u32(tb[EVENT_FREQ]); 338 | 339 | addr_str = blobmsg_data(tb[EVENT_ADDR]); 340 | addr = (uint8_t *) ether_aton(addr_str); 341 | if (!addr) 342 | return UBUS_STATUS_INVALID_ARGUMENT; 343 | 344 | ret = usteer_handle_sta_event(node, addr, ev_type, freq, signal); 345 | 346 | MSG(DEBUG, "received %s event from %s, signal=%d, freq=%d, handled:%s\n", 347 | method, addr_str, signal, freq, ret ? "true" : "false"); 348 | 349 | return ret ? 0 : 17 /* WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA */; 350 | } 351 | 352 | static void 353 | usteer_local_node_assoc_update(struct sta_info *si, struct blob_attr *data) 354 | { 355 | enum { 356 | MSG_ASSOC, 357 | __MSG_MAX, 358 | }; 359 | static struct blobmsg_policy policy[__MSG_MAX] = { 360 | [MSG_ASSOC] = { "assoc", BLOBMSG_TYPE_BOOL }, 361 | }; 362 | struct blob_attr *tb[__MSG_MAX]; 363 | struct usteer_remote_node *rn; 364 | struct sta_info *remote_si; 365 | 366 | blobmsg_parse(policy, __MSG_MAX, tb, blobmsg_data(data), blobmsg_data_len(data)); 367 | if (tb[MSG_ASSOC] && blobmsg_get_u8(tb[MSG_ASSOC])) { 368 | if (si->connected == STA_NOT_CONNECTED) { 369 | /* New connection. Check if STA roamed. */ 370 | for_each_remote_node(rn) { 371 | remote_si = usteer_sta_info_get(si->sta, &rn->node, NULL); 372 | if (!remote_si) 373 | continue; 374 | 375 | if (current_time - remote_si->last_connected < config.roam_process_timeout) { 376 | rn->node.roam_events.source++; 377 | /* Don't abort looking for roam sources here. 378 | * The client might have roamed via another node 379 | * within the roam-timeout. 380 | */ 381 | } 382 | } 383 | } 384 | si->connected = STA_CONNECTED; 385 | } 386 | } 387 | 388 | static void 389 | usteer_local_node_update_sta_rrm_wnm(struct sta_info *si, struct blob_attr *client_attr) 390 | { 391 | static const struct blobmsg_policy mbo_policy = { 392 | .name = "mbo", 393 | .type = BLOBMSG_TYPE_BOOL, 394 | }; 395 | static const struct blobmsg_policy rrm_policy = { 396 | .name = "rrm", 397 | .type = BLOBMSG_TYPE_ARRAY, 398 | }; 399 | static const struct blobmsg_policy ext_capa_policy = { 400 | .name = "extended_capabilities", 401 | .type = BLOBMSG_TYPE_ARRAY, 402 | }; 403 | struct blob_attr *mbo_blob = NULL, *rrm_blob = NULL, *wnm_blob = NULL, *cur; 404 | int rem; 405 | int i = 0; 406 | 407 | /* MBO */ 408 | blobmsg_parse(&mbo_policy, 1, &mbo_blob, blobmsg_data(client_attr), blobmsg_data_len(client_attr)); 409 | 410 | if (mbo_blob) 411 | si->mbo = blobmsg_get_u8(mbo_blob); 412 | else 413 | si->mbo = false; 414 | 415 | /* RRM */ 416 | blobmsg_parse(&rrm_policy, 1, &rrm_blob, blobmsg_data(client_attr), blobmsg_data_len(client_attr)); 417 | if (!rrm_blob) 418 | return; 419 | 420 | si->rrm = blobmsg_get_u32(blobmsg_data(rrm_blob)); 421 | 422 | /* Extended Capabilities / WNM */ 423 | blobmsg_parse(&ext_capa_policy, 1, &wnm_blob, blobmsg_data(client_attr), blobmsg_data_len(client_attr)); 424 | if (!wnm_blob) 425 | return; 426 | 427 | blobmsg_for_each_attr(cur, wnm_blob, rem) { 428 | if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32) 429 | return; 430 | 431 | if (i == 2) { 432 | if (blobmsg_get_u32(cur) & (1 << 3)) 433 | si->bss_transition = true; 434 | } 435 | 436 | i++; 437 | } 438 | } 439 | 440 | static void 441 | usteer_local_node_set_assoc(struct usteer_local_node *ln, struct blob_attr *cl) 442 | { 443 | struct usteer_node *node = &ln->node; 444 | struct usteer_node_handler *h; 445 | struct blob_attr *cur; 446 | struct sta_info *si; 447 | struct sta *sta; 448 | int n_assoc = 0; 449 | int rem; 450 | 451 | usteer_update_time(); 452 | 453 | list_for_each_entry(si, &node->sta_info, node_list) { 454 | if (si->connected) 455 | si->connected = STA_DISCONNECTED; 456 | } 457 | 458 | blobmsg_for_each_attr(cur, cl, rem) { 459 | uint8_t *addr = (uint8_t *) ether_aton(blobmsg_name(cur)); 460 | bool create; 461 | 462 | if (!addr) 463 | continue; 464 | 465 | sta = usteer_sta_get(addr, true); 466 | si = usteer_sta_info_get(sta, node, &create); 467 | list_for_each_entry(h, &node_handlers, list) { 468 | if (!h->update_sta) 469 | continue; 470 | 471 | h->update_sta(node, si); 472 | } 473 | usteer_local_node_assoc_update(si, cur); 474 | if (si->connected == STA_CONNECTED) { 475 | si->last_connected = current_time; 476 | n_assoc++; 477 | } 478 | 479 | /* Read RRM information */ 480 | usteer_local_node_update_sta_rrm_wnm(si, cur); 481 | } 482 | 483 | node->n_assoc = n_assoc; 484 | 485 | list_for_each_entry(si, &node->sta_info, node_list) { 486 | if (si->connected != STA_DISCONNECTED) 487 | continue; 488 | 489 | usteer_sta_disconnected(si); 490 | MSG(VERBOSE, "station "MAC_ADDR_FMT" disconnected from node %s\n", 491 | MAC_ADDR_DATA(si->sta->addr), usteer_node_name(node)); 492 | } 493 | } 494 | 495 | static void 496 | usteer_local_node_list_cb(struct ubus_request *req, int type, struct blob_attr *msg) 497 | { 498 | enum { 499 | MSG_FREQ, 500 | MSG_CLIENTS, 501 | __MSG_MAX, 502 | }; 503 | static struct blobmsg_policy policy[__MSG_MAX] = { 504 | [MSG_FREQ] = { "freq", BLOBMSG_TYPE_INT32 }, 505 | [MSG_CLIENTS] = { "clients", BLOBMSG_TYPE_TABLE }, 506 | }; 507 | struct blob_attr *tb[__MSG_MAX]; 508 | struct usteer_local_node *ln; 509 | struct usteer_node *node; 510 | 511 | ln = container_of(req, struct usteer_local_node, req); 512 | node = &ln->node; 513 | 514 | blobmsg_parse(policy, __MSG_MAX, tb, blob_data(msg), blob_len(msg)); 515 | if (!tb[MSG_FREQ] || !tb[MSG_CLIENTS]) 516 | return; 517 | 518 | node->freq = blobmsg_get_u32(tb[MSG_FREQ]); 519 | usteer_local_node_set_assoc(ln, tb[MSG_CLIENTS]); 520 | } 521 | 522 | static void 523 | usteer_local_node_status_cb(struct ubus_request *req, int type, struct blob_attr *msg) 524 | { 525 | enum { 526 | MSG_FREQ, 527 | MSG_CHANNEL, 528 | MSG_OP_CLASS, 529 | MSG_BEACON_INTERVAL, 530 | __MSG_MAX, 531 | }; 532 | static struct blobmsg_policy policy[__MSG_MAX] = { 533 | [MSG_FREQ] = { "freq", BLOBMSG_TYPE_INT32 }, 534 | [MSG_CHANNEL] = { "channel", BLOBMSG_TYPE_INT32 }, 535 | [MSG_OP_CLASS] = { "op_class", BLOBMSG_TYPE_INT32 }, 536 | [MSG_BEACON_INTERVAL] = { "beacon_interval", BLOBMSG_TYPE_INT32 }, 537 | }; 538 | struct blob_attr *tb[__MSG_MAX]; 539 | struct usteer_local_node *ln; 540 | struct usteer_node *node; 541 | 542 | ln = container_of(req, struct usteer_local_node, req); 543 | node = &ln->node; 544 | 545 | blobmsg_parse(policy, __MSG_MAX, tb, blob_data(msg), blob_len(msg)); 546 | if (tb[MSG_FREQ]) 547 | node->freq = blobmsg_get_u32(tb[MSG_FREQ]); 548 | if (tb[MSG_CHANNEL]) 549 | node->channel = blobmsg_get_u32(tb[MSG_CHANNEL]); 550 | if (tb[MSG_OP_CLASS]) 551 | node->op_class = blobmsg_get_u32(tb[MSG_OP_CLASS]); 552 | 553 | /* Local-Node */ 554 | if (tb[MSG_BEACON_INTERVAL]) 555 | ln->beacon_interval = blobmsg_get_u32(tb[MSG_BEACON_INTERVAL]); 556 | } 557 | 558 | static void 559 | usteer_local_node_rrm_nr_cb(struct ubus_request *req, int type, struct blob_attr *msg) 560 | { 561 | static const struct blobmsg_policy policy = { 562 | "value", BLOBMSG_TYPE_ARRAY 563 | }; 564 | struct usteer_local_node *ln; 565 | struct blob_attr *tb; 566 | 567 | ln = container_of(req, struct usteer_local_node, req); 568 | 569 | blobmsg_parse(&policy, 1, &tb, blob_data(msg), blob_len(msg)); 570 | if (!tb) 571 | return; 572 | 573 | usteer_node_set_blob(&ln->node.rrm_nr, tb); 574 | } 575 | 576 | static void 577 | usteer_local_node_req_cb(struct ubus_request *req, int ret) 578 | { 579 | struct usteer_local_node *ln; 580 | 581 | ln = container_of(req, struct usteer_local_node, req); 582 | uloop_timeout_set(&ln->req_timer, 1); 583 | } 584 | 585 | static bool 586 | usteer_add_rrm_data(struct usteer_local_node *ln, struct usteer_node *node) 587 | { 588 | if (node == &ln->node) 589 | return false; 590 | 591 | if (!node->rrm_nr) 592 | return false; 593 | 594 | /* Remote node only adds same SSID. Required for local-node. */ 595 | if (strcmp(ln->node.ssid, node->ssid) != 0) 596 | return false; 597 | 598 | blobmsg_add_field(&b, BLOBMSG_TYPE_ARRAY, "", 599 | blobmsg_data(node->rrm_nr), 600 | blobmsg_data_len(node->rrm_nr)); 601 | 602 | return true; 603 | } 604 | 605 | static void 606 | usteer_local_node_prepare_rrm_set(struct usteer_local_node *ln) 607 | { 608 | struct usteer_node *node, *last_remote_neighbor = NULL; 609 | int i = 0; 610 | void *c; 611 | 612 | c = blobmsg_open_array(&b, "list"); 613 | for_each_local_node(node) { 614 | if (i >= config.max_neighbor_reports) 615 | break; 616 | if (usteer_add_rrm_data(ln, node)) 617 | i++; 618 | } 619 | 620 | while (i < config.max_neighbor_reports) { 621 | node = usteer_node_get_next_neighbor(&ln->node, last_remote_neighbor); 622 | if (!node) { 623 | /* No more nodes available */ 624 | break; 625 | } 626 | 627 | last_remote_neighbor = node; 628 | if (usteer_add_rrm_data(ln, node)) 629 | i++; 630 | } 631 | 632 | blobmsg_close_array(&b, c); 633 | } 634 | 635 | static void 636 | usteer_local_node_state_next(struct uloop_timeout *timeout) 637 | { 638 | struct usteer_local_node *ln; 639 | 640 | ln = container_of(timeout, struct usteer_local_node, req_timer); 641 | 642 | ln->req_state++; 643 | if (ln->req_state >= __REQ_MAX) { 644 | ln->req_state = REQ_IDLE; 645 | return; 646 | } 647 | 648 | blob_buf_init(&b, 0); 649 | switch (ln->req_state) { 650 | case REQ_CLIENTS: 651 | ubus_invoke_async(ubus_ctx, ln->obj_id, "get_clients", b.head, &ln->req); 652 | ln->req.data_cb = usteer_local_node_list_cb; 653 | break; 654 | case REQ_STATUS: 655 | ubus_invoke_async(ubus_ctx, ln->obj_id, "get_status", b.head, &ln->req); 656 | ln->req.data_cb = usteer_local_node_status_cb; 657 | break; 658 | case REQ_RRM_SET_LIST: 659 | usteer_local_node_prepare_rrm_set(ln); 660 | ubus_invoke_async(ubus_ctx, ln->obj_id, "rrm_nr_set", b.head, &ln->req); 661 | ln->req.data_cb = NULL; 662 | break; 663 | case REQ_RRM_GET_OWN: 664 | ubus_invoke_async(ubus_ctx, ln->obj_id, "rrm_nr_get_own", b.head, &ln->req); 665 | ln->req.data_cb = usteer_local_node_rrm_nr_cb; 666 | break; 667 | default: 668 | break; 669 | } 670 | ln->req.complete_cb = usteer_local_node_req_cb; 671 | ubus_complete_request_async(ubus_ctx, &ln->req); 672 | } 673 | 674 | static void 675 | usteer_local_node_request_link_measurement(struct usteer_local_node *ln) 676 | { 677 | unsigned int min_count = DIV_ROUND_UP(config.link_measurement_interval, config.local_sta_update); 678 | struct usteer_node *node; 679 | struct sta_info *si; 680 | 681 | node = &ln->node; 682 | 683 | if (ln->link_measurement_tries < min_count) { 684 | ln->link_measurement_tries++; 685 | return; 686 | } 687 | 688 | ln->link_measurement_tries = 0; 689 | 690 | if (!config.link_measurement_interval) 691 | return; 692 | 693 | list_for_each_entry(si, &node->sta_info, node_list) { 694 | if (si->connected != STA_CONNECTED) 695 | continue; 696 | 697 | usteer_ubus_trigger_link_measurement(si); 698 | } 699 | } 700 | 701 | static void 702 | usteer_local_node_update(struct uloop_timeout *timeout) 703 | { 704 | struct usteer_local_node *ln; 705 | struct usteer_node_handler *h; 706 | struct usteer_node *node; 707 | 708 | ln = container_of(timeout, struct usteer_local_node, update); 709 | node = &ln->node; 710 | 711 | list_for_each_entry(h, &node_handlers, list) { 712 | if (!h->update_node) 713 | continue; 714 | 715 | h->update_node(node); 716 | } 717 | 718 | usteer_local_node_state_reset(ln); 719 | uloop_timeout_set(&ln->req_timer, 1); 720 | usteer_local_node_kick(ln); 721 | usteer_band_steering_perform_steer(ln); 722 | usteer_local_node_request_link_measurement(ln); 723 | 724 | uloop_timeout_set(timeout, config.local_sta_update); 725 | } 726 | 727 | static void 728 | usteer_local_node_process_bss_tm_queries(struct uloop_timeout *timeout) 729 | { 730 | struct usteer_bss_tm_query *query, *tmp; 731 | struct usteer_local_node *ln; 732 | struct usteer_node *node; 733 | struct sta_info *si; 734 | struct sta *sta; 735 | uint8_t validity_period; 736 | 737 | ln = container_of(timeout, struct usteer_local_node, bss_tm_queries_timeout); 738 | node = &ln->node; 739 | 740 | validity_period = 10000 / usteer_local_node_get_beacon_interval(ln); /* ~ 10 seconds */ 741 | 742 | list_for_each_entry_safe(query, tmp, &ln->bss_tm_queries, list) { 743 | sta = usteer_sta_get(query->sta_addr, false); 744 | if (!sta) 745 | continue; 746 | 747 | si = usteer_sta_info_get(sta, node, false); 748 | if (!si) 749 | continue; 750 | 751 | usteer_ubus_bss_transition_request(si, query->dialog_token, false, false, validity_period, NULL); 752 | } 753 | 754 | /* Free pending queries we can not handle */ 755 | usteer_local_node_pending_bss_tm_free(ln); 756 | } 757 | 758 | static struct usteer_local_node * 759 | usteer_get_node(struct ubus_context *ctx, const char *name) 760 | { 761 | struct usteer_local_node *ln; 762 | struct usteer_node *node; 763 | char *str; 764 | 765 | ln = avl_find_element(&local_nodes, name, ln, node.avl); 766 | if (ln) 767 | return ln; 768 | 769 | ln = calloc_a(sizeof(*ln), &str, strlen(name) + 1); 770 | node = &ln->node; 771 | node->type = NODE_TYPE_LOCAL; 772 | node->created = current_time; 773 | node->avl.key = strcpy(str, name); 774 | ln->ev.remove_cb = usteer_handle_remove; 775 | ln->ev.cb = usteer_handle_event; 776 | ln->update.cb = usteer_local_node_update; 777 | ln->req_timer.cb = usteer_local_node_state_next; 778 | ubus_register_subscriber(ctx, &ln->ev); 779 | avl_insert(&local_nodes, &node->avl); 780 | kvlist_init(&ln->node_info, kvlist_blob_len); 781 | INIT_LIST_HEAD(&node->sta_info); 782 | INIT_LIST_HEAD(&node->measurements); 783 | 784 | ln->bss_tm_queries_timeout.cb = usteer_local_node_process_bss_tm_queries; 785 | INIT_LIST_HEAD(&ln->bss_tm_queries); 786 | return ln; 787 | } 788 | 789 | static void 790 | usteer_node_run_update_script(struct usteer_node *node) 791 | { 792 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 793 | char *val; 794 | 795 | if (!node_up_script) 796 | return; 797 | 798 | val = alloca(strlen(node_up_script) + strlen(ln->iface) + 8); 799 | sprintf(val, "%s '%s'", node_up_script, ln->iface); 800 | if (system(val)) 801 | MSG(INFO, "failed to execute %s\n", val); 802 | } 803 | 804 | static void 805 | usteer_check_node_enabled(struct usteer_local_node *ln) 806 | { 807 | bool ssid_disabled = config.ssid_list; 808 | struct blob_attr *cur; 809 | int rem; 810 | 811 | blobmsg_for_each_attr(cur, config.ssid_list, rem) { 812 | if (strcmp(blobmsg_get_string(cur), ln->node.ssid) != 0) 813 | continue; 814 | 815 | ssid_disabled = false; 816 | break; 817 | } 818 | 819 | if (ln->node.disabled == ssid_disabled) 820 | return; 821 | 822 | ln->node.disabled = ssid_disabled; 823 | 824 | if (ssid_disabled) { 825 | MSG(INFO, "Disconnecting from local node %s\n", usteer_node_name(&ln->node)); 826 | usteer_local_node_state_reset(ln); 827 | usteer_sta_node_cleanup(&ln->node); 828 | usteer_measurement_report_node_cleanup(&ln->node); 829 | uloop_timeout_cancel(&ln->update); 830 | ubus_unsubscribe(ubus_ctx, &ln->ev, ln->obj_id); 831 | return; 832 | } 833 | 834 | MSG(INFO, "Connecting to local node %s\n", usteer_node_name(&ln->node)); 835 | ubus_subscribe(ubus_ctx, &ln->ev, ln->obj_id); 836 | uloop_timeout_set(&ln->update, 1); 837 | usteer_node_run_update_script(&ln->node); 838 | } 839 | 840 | static void 841 | usteer_register_node(struct ubus_context *ctx, const char *name, uint32_t id) 842 | { 843 | struct usteer_local_node *ln; 844 | struct usteer_node_handler *h; 845 | const char *iface; 846 | int offset = sizeof("hostapd.") - 1; 847 | 848 | iface = name + offset; 849 | if (strncmp(name, "hostapd.", iface - name) != 0) 850 | return; 851 | 852 | MSG(INFO, "Creating local node %s\n", name); 853 | ln = usteer_get_node(ctx, name); 854 | ln->obj_id = id; 855 | ln->iface = usteer_node_name(&ln->node) + offset; 856 | ln->ifindex = if_nametoindex(ln->iface); 857 | 858 | blob_buf_init(&b, 0); 859 | blobmsg_add_u32(&b, "notify_response", 1); 860 | ubus_invoke(ctx, id, "notify_response", b.head, NULL, NULL, 1000); 861 | 862 | blob_buf_init(&b, 0); 863 | blobmsg_add_u8(&b, "neighbor_report", 1); 864 | blobmsg_add_u8(&b, "link_measurement", 1); 865 | blobmsg_add_u8(&b, "beacon_report", 1); 866 | blobmsg_add_u8(&b, "bss_transition", 1); 867 | ubus_invoke(ctx, id, "bss_mgmt_enable", b.head, NULL, NULL, 1000); 868 | 869 | list_for_each_entry(h, &node_handlers, list) { 870 | if (!h->init_node) 871 | continue; 872 | 873 | h->init_node(&ln->node); 874 | } 875 | 876 | ln->node.disabled = true; 877 | usteer_check_node_enabled(ln); 878 | } 879 | 880 | static void 881 | usteer_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, 882 | const char *type, struct blob_attr *msg) 883 | { 884 | static const struct blobmsg_policy policy[2] = { 885 | { .name = "id", .type = BLOBMSG_TYPE_INT32 }, 886 | { .name = "path", .type = BLOBMSG_TYPE_STRING }, 887 | }; 888 | struct blob_attr *tb[2]; 889 | const char *path; 890 | 891 | blobmsg_parse(policy, 2, tb, blob_data(msg), blob_len(msg)); 892 | 893 | if (!tb[0] || !tb[1]) 894 | return; 895 | 896 | path = blobmsg_data(tb[1]); 897 | usteer_register_node(ctx, path, blobmsg_get_u32(tb[0])); 898 | } 899 | 900 | static void 901 | usteer_register_events(struct ubus_context *ctx) 902 | { 903 | static struct ubus_event_handler handler = { 904 | .cb = usteer_event_handler 905 | }; 906 | 907 | ubus_register_event_handler(ctx, &handler, "ubus.object.add"); 908 | } 909 | 910 | static void 911 | node_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv) 912 | { 913 | usteer_register_node(ctx, obj->path, obj->id); 914 | } 915 | 916 | int 917 | usteer_local_node_get_beacon_interval(struct usteer_local_node *ln) 918 | { 919 | /* Check if beacon-interval is not available (pre-21.02+) */ 920 | if (ln->beacon_interval < 1) 921 | return 100; 922 | 923 | return ln->beacon_interval; 924 | } 925 | 926 | void config_set_node_up_script(struct blob_attr *data) 927 | { 928 | const char *val; 929 | struct usteer_node *node; 930 | 931 | if (!data) 932 | return; 933 | 934 | val = blobmsg_get_string(data); 935 | if (node_up_script && !strcmp(val, node_up_script)) 936 | return; 937 | 938 | free(node_up_script); 939 | 940 | if (!strlen(val)) { 941 | node_up_script = NULL; 942 | return; 943 | } 944 | 945 | node_up_script = strdup(val); 946 | 947 | for_each_local_node(node) 948 | usteer_node_run_update_script(node); 949 | } 950 | 951 | void config_get_node_up_script(struct blob_buf *buf) 952 | { 953 | if (!node_up_script) 954 | return; 955 | 956 | blobmsg_add_string(buf, "node_up_script", node_up_script); 957 | } 958 | 959 | void config_set_ssid_list(struct blob_attr *data) 960 | { 961 | struct usteer_local_node *ln; 962 | 963 | free(config.ssid_list); 964 | 965 | if (data && blobmsg_len(data)) 966 | config.ssid_list = blob_memdup(data); 967 | else 968 | config.ssid_list = NULL; 969 | 970 | avl_for_each_element(&local_nodes, ln, node.avl) 971 | usteer_check_node_enabled(ln); 972 | } 973 | 974 | void config_get_ssid_list(struct blob_buf *buf) 975 | { 976 | if (config.ssid_list) 977 | blobmsg_add_blob(buf, config.ssid_list); 978 | } 979 | 980 | void 981 | usteer_local_nodes_init(struct ubus_context *ctx) 982 | { 983 | usteer_register_events(ctx); 984 | ubus_lookup(ctx, "hostapd.*", node_list_cb, NULL); 985 | } 986 | --------------------------------------------------------------------------------