├── .gitignore ├── CMakeLists.txt ├── README.md ├── event.c ├── event.h ├── fakeap.c ├── local_node.c ├── main.c ├── measurement.c ├── monitor.c ├── netifd.c ├── nl80211.c ├── node.c ├── node.h ├── openwrt └── usteer │ ├── Makefile │ └── files │ └── etc │ ├── config │ └── usteer │ └── init.d │ └── usteer ├── parse.c ├── policy.c ├── remote.c ├── remote.h ├── sta.c ├── timeout.c ├── timeout.h ├── ubus.c ├── usteer.h └── utils.h /.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 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 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) 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 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 * 84 | usteer_local_node_by_bssid(uint8_t *bssid) { 85 | struct usteer_local_node *ln; 86 | struct usteer_node *n; 87 | 88 | for_each_local_node(n) { 89 | ln = container_of(n, struct usteer_local_node, node); 90 | if (!memcmp(n->bssid, bssid, 6)) 91 | return ln; 92 | } 93 | 94 | return NULL; 95 | } 96 | 97 | static void 98 | usteer_handle_remove(struct ubus_context *ctx, struct ubus_subscriber *s, 99 | uint32_t id) 100 | { 101 | struct usteer_local_node *ln = container_of(s, struct usteer_local_node, ev); 102 | 103 | usteer_free_node(ctx, ln); 104 | } 105 | 106 | static int 107 | usteer_handle_bss_tm_query(struct usteer_local_node *ln, struct blob_attr *msg) 108 | { 109 | enum { 110 | BSS_TM_QUERY_ADDRESS, 111 | BSS_TM_QUERY_DIALOG_TOKEN, 112 | BSS_TM_QUERY_CANDIDATE_LIST, 113 | __BSS_TM_QUERY_MAX 114 | }; 115 | struct blobmsg_policy policy[__BSS_TM_QUERY_MAX] = { 116 | [BSS_TM_QUERY_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 117 | [BSS_TM_QUERY_DIALOG_TOKEN] = { .name = "dialog-token", .type = BLOBMSG_TYPE_INT8 }, 118 | [BSS_TM_QUERY_CANDIDATE_LIST] = { .name = "candidate-list", .type = BLOBMSG_TYPE_STRING }, 119 | }; 120 | struct blob_attr *tb[__BSS_TM_QUERY_MAX]; 121 | struct usteer_bss_tm_query *query; 122 | uint8_t *sta_addr; 123 | 124 | blobmsg_parse(policy, __BSS_TM_QUERY_MAX, tb, blob_data(msg), blob_len(msg)); 125 | 126 | if (!tb[BSS_TM_QUERY_ADDRESS] || !tb[BSS_TM_QUERY_DIALOG_TOKEN]) 127 | return 0; 128 | 129 | query = calloc(1, sizeof(*query)); 130 | if (!query) 131 | return 0; 132 | 133 | query->dialog_token = blobmsg_get_u8(tb[BSS_TM_QUERY_DIALOG_TOKEN]); 134 | 135 | sta_addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BSS_TM_QUERY_ADDRESS])); 136 | if (!sta_addr) 137 | return 0; 138 | 139 | memcpy(query->sta_addr, sta_addr, 6); 140 | 141 | list_add(&query->list, &ln->bss_tm_queries); 142 | uloop_timeout_set(&ln->bss_tm_queries_timeout, 1); 143 | 144 | return 1; 145 | } 146 | 147 | static int 148 | usteer_handle_bss_tm_response(struct usteer_local_node *ln, struct blob_attr *msg) 149 | { 150 | enum { 151 | BSS_TM_RESPONSE_ADDRESS, 152 | BSS_TM_RESPONSE_STATUS_CODE, 153 | __BSS_TM_RESPONSE_MAX 154 | }; 155 | struct blobmsg_policy policy[__BSS_TM_RESPONSE_MAX] = { 156 | [BSS_TM_RESPONSE_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 157 | [BSS_TM_RESPONSE_STATUS_CODE] = { .name = "status-code", .type = BLOBMSG_TYPE_INT8 }, 158 | }; 159 | struct blob_attr *tb[__BSS_TM_RESPONSE_MAX]; 160 | struct sta_info *si; 161 | struct sta *sta; 162 | uint8_t *sta_addr; 163 | 164 | blobmsg_parse(policy, __BSS_TM_RESPONSE_MAX, tb, blob_data(msg), blob_len(msg)); 165 | 166 | if (!tb[BSS_TM_RESPONSE_ADDRESS] || !tb[BSS_TM_RESPONSE_STATUS_CODE]) 167 | return 0; 168 | 169 | sta_addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BSS_TM_RESPONSE_ADDRESS])); 170 | if (!sta_addr) 171 | return 0; 172 | 173 | sta = usteer_sta_get(sta_addr, false); 174 | if (!sta) 175 | return 0; 176 | 177 | si = usteer_sta_info_get(sta, &ln->node, false); 178 | if (!si) 179 | return 0; 180 | 181 | si->bss_transition_response.status_code = blobmsg_get_u8(tb[BSS_TM_RESPONSE_STATUS_CODE]); 182 | si->bss_transition_response.timestamp = current_time; 183 | 184 | return 0; 185 | } 186 | 187 | static int 188 | usteer_local_node_handle_beacon_report(struct usteer_local_node *ln, struct blob_attr *msg) 189 | { 190 | enum { 191 | BR_ADDRESS, 192 | BR_BSSID, 193 | BR_RCPI, 194 | BR_RSNI, 195 | __BR_MAX 196 | }; 197 | struct blobmsg_policy policy[__BR_MAX] = { 198 | [BR_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 199 | [BR_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING }, 200 | [BR_RCPI] = { .name = "rcpi", .type = BLOBMSG_TYPE_INT16 }, 201 | [BR_RSNI] = { .name = "rsni", .type = BLOBMSG_TYPE_INT16 }, 202 | }; 203 | struct blob_attr *tb[__BR_MAX]; 204 | 205 | struct usteer_beacon_report br; 206 | struct usteer_node *node; 207 | uint8_t *addr; 208 | struct sta *sta; 209 | 210 | blobmsg_parse(policy, __BR_MAX, tb, blob_data(msg), blob_len(msg)); 211 | if (!tb[BR_ADDRESS] || !tb[BR_BSSID] || !tb[BR_RCPI] || !tb[BR_RSNI]) 212 | return 0; 213 | 214 | addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BR_ADDRESS])); 215 | if (!addr) 216 | return 0; 217 | 218 | sta = usteer_sta_get(addr, false); 219 | if (!sta) 220 | return 0; 221 | 222 | addr = (uint8_t *) ether_aton(blobmsg_get_string(tb[BR_BSSID])); 223 | if (!addr) 224 | return 0; 225 | 226 | node = usteer_node_by_bssid(addr); 227 | if (!node) 228 | return 0; 229 | 230 | br.rcpi = (uint8_t)blobmsg_get_u16(tb[BR_RCPI]); 231 | br.rsni = (uint8_t)blobmsg_get_u16(tb[BR_RSNI]); 232 | 233 | usteer_measurement_report_add_beacon_report(sta, node, &br, current_time); 234 | return 0; 235 | } 236 | 237 | static int 238 | usteer_handle_event(struct ubus_context *ctx, struct ubus_object *obj, 239 | struct ubus_request_data *req, const char *method, 240 | struct blob_attr *msg) 241 | { 242 | enum { 243 | EVENT_ADDR, 244 | EVENT_SIGNAL, 245 | EVENT_TARGET, 246 | EVENT_FREQ, 247 | __EVENT_MAX 248 | }; 249 | struct blobmsg_policy policy[__EVENT_MAX] = { 250 | [EVENT_ADDR] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, 251 | [EVENT_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 }, 252 | [EVENT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, 253 | [EVENT_FREQ] = { .name = "freq", .type = BLOBMSG_TYPE_INT32 }, 254 | }; 255 | enum usteer_event_type ev_type = __EVENT_TYPE_MAX; 256 | struct blob_attr *tb[__EVENT_MAX]; 257 | struct usteer_local_node *ln; 258 | struct usteer_node *node; 259 | int signal = NO_SIGNAL; 260 | int freq = 0; 261 | const char *addr_str; 262 | const uint8_t *addr; 263 | int i; 264 | bool ret; 265 | 266 | usteer_update_time(); 267 | 268 | ln = container_of(obj, struct usteer_local_node, ev.obj); 269 | 270 | if(!strcmp(method, "bss-transition-query")) { 271 | return usteer_handle_bss_tm_query(ln, msg); 272 | } else if(!strcmp(method, "bss-transition-response")) { 273 | return usteer_handle_bss_tm_response(ln, msg); 274 | } else if(!strcmp(method, "beacon-report")) { 275 | return usteer_local_node_handle_beacon_report(ln, msg); 276 | } 277 | 278 | for (i = 0; i < ARRAY_SIZE(event_types); i++) { 279 | if (strcmp(method, event_types[i]) != 0) 280 | continue; 281 | 282 | ev_type = i; 283 | break; 284 | } 285 | 286 | ln = container_of(obj, struct usteer_local_node, ev.obj); 287 | node = &ln->node; 288 | blobmsg_parse(policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg)); 289 | if (!tb[EVENT_ADDR] || !tb[EVENT_FREQ]) 290 | return UBUS_STATUS_INVALID_ARGUMENT; 291 | 292 | if (tb[EVENT_SIGNAL]) 293 | signal = (int32_t) blobmsg_get_u32(tb[EVENT_SIGNAL]); 294 | 295 | if (tb[EVENT_FREQ]) 296 | freq = blobmsg_get_u32(tb[EVENT_FREQ]); 297 | 298 | addr_str = blobmsg_data(tb[EVENT_ADDR]); 299 | addr = (uint8_t *) ether_aton(addr_str); 300 | if (!addr) 301 | return UBUS_STATUS_INVALID_ARGUMENT; 302 | 303 | ret = usteer_handle_sta_event(node, addr, ev_type, freq, signal); 304 | 305 | MSG(DEBUG, "received %s event from %s, signal=%d, freq=%d, handled:%s\n", 306 | method, addr_str, signal, freq, ret ? "true" : "false"); 307 | 308 | return ret ? 0 : 17 /* WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA */; 309 | } 310 | 311 | static void 312 | usteer_local_node_assoc_update(struct sta_info *si, struct blob_attr *data) 313 | { 314 | enum { 315 | MSG_ASSOC, 316 | __MSG_MAX, 317 | }; 318 | static struct blobmsg_policy policy[__MSG_MAX] = { 319 | [MSG_ASSOC] = { "assoc", BLOBMSG_TYPE_BOOL }, 320 | }; 321 | struct blob_attr *tb[__MSG_MAX]; 322 | struct usteer_remote_node *rn; 323 | struct sta_info *remote_si; 324 | 325 | blobmsg_parse(policy, __MSG_MAX, tb, blobmsg_data(data), blobmsg_data_len(data)); 326 | if (tb[MSG_ASSOC] && blobmsg_get_u8(tb[MSG_ASSOC])) { 327 | if (si->connected == STA_NOT_CONNECTED) { 328 | /* New connection. Check if STA roamed. */ 329 | for_each_remote_node(rn) { 330 | remote_si = usteer_sta_info_get(si->sta, &rn->node, NULL); 331 | if (!remote_si) 332 | continue; 333 | 334 | if (current_time - remote_si->last_connected < config.roam_process_timeout) { 335 | rn->node.roam_events.source++; 336 | /* Don't abort looking for roam sources here. 337 | * The client might have roamed via another node 338 | * within the roam-timeout. 339 | */ 340 | } 341 | } 342 | } 343 | si->connected = STA_CONNECTED; 344 | } 345 | } 346 | 347 | static void 348 | usteer_local_node_update_sta_rrm(const uint8_t *addr, struct blob_attr *client_attr) 349 | { 350 | static const struct blobmsg_policy rrm_policy = { 351 | .name = "rrm", 352 | .type = BLOBMSG_TYPE_ARRAY, 353 | }; 354 | struct blob_attr *sta_blob = NULL; 355 | struct sta *sta; 356 | 357 | if (!addr) 358 | return; 359 | 360 | /* Don't create the STA */ 361 | sta = usteer_sta_get(addr, false); 362 | if (!sta) 363 | return; 364 | 365 | blobmsg_parse(&rrm_policy, 1, &sta_blob, blobmsg_data(client_attr), blobmsg_data_len(client_attr)); 366 | if (!sta_blob) 367 | return; 368 | 369 | sta->rrm = blobmsg_get_u32(blobmsg_data(sta_blob)); 370 | } 371 | 372 | static void 373 | usteer_local_node_set_assoc(struct usteer_local_node *ln, struct blob_attr *cl) 374 | { 375 | struct usteer_node *node = &ln->node; 376 | struct usteer_node_handler *h; 377 | struct blob_attr *cur; 378 | struct sta_info *si; 379 | struct sta *sta; 380 | int n_assoc = 0; 381 | int rem; 382 | 383 | usteer_update_time(); 384 | 385 | list_for_each_entry(si, &node->sta_info, node_list) { 386 | if (si->connected) 387 | si->connected = STA_DISCONNECTED; 388 | } 389 | 390 | blobmsg_for_each_attr(cur, cl, rem) { 391 | uint8_t *addr = (uint8_t *) ether_aton(blobmsg_name(cur)); 392 | bool create; 393 | 394 | if (!addr) 395 | continue; 396 | 397 | sta = usteer_sta_get(addr, true); 398 | si = usteer_sta_info_get(sta, node, &create); 399 | list_for_each_entry(h, &node_handlers, list) { 400 | if (!h->update_sta) 401 | continue; 402 | 403 | h->update_sta(node, si); 404 | } 405 | usteer_local_node_assoc_update(si, cur); 406 | if (si->connected == STA_CONNECTED) { 407 | si->last_connected = current_time; 408 | n_assoc++; 409 | } 410 | 411 | /* Read RRM information */ 412 | usteer_local_node_update_sta_rrm(addr, cur); 413 | } 414 | 415 | node->n_assoc = n_assoc; 416 | 417 | list_for_each_entry(si, &node->sta_info, node_list) { 418 | if (si->connected != STA_DISCONNECTED) 419 | continue; 420 | 421 | usteer_sta_disconnected(si); 422 | MSG(VERBOSE, "station "MAC_ADDR_FMT" disconnected from node %s\n", 423 | MAC_ADDR_DATA(si->sta->addr), usteer_node_name(node)); 424 | } 425 | } 426 | 427 | static void 428 | usteer_local_node_list_cb(struct ubus_request *req, int type, struct blob_attr *msg) 429 | { 430 | enum { 431 | MSG_FREQ, 432 | MSG_CLIENTS, 433 | __MSG_MAX, 434 | }; 435 | static struct blobmsg_policy policy[__MSG_MAX] = { 436 | [MSG_FREQ] = { "freq", BLOBMSG_TYPE_INT32 }, 437 | [MSG_CLIENTS] = { "clients", BLOBMSG_TYPE_TABLE }, 438 | }; 439 | struct blob_attr *tb[__MSG_MAX]; 440 | struct usteer_local_node *ln; 441 | struct usteer_node *node; 442 | 443 | ln = container_of(req, struct usteer_local_node, req); 444 | node = &ln->node; 445 | 446 | blobmsg_parse(policy, __MSG_MAX, tb, blob_data(msg), blob_len(msg)); 447 | if (!tb[MSG_FREQ] || !tb[MSG_CLIENTS]) 448 | return; 449 | 450 | node->freq = blobmsg_get_u32(tb[MSG_FREQ]); 451 | usteer_local_node_set_assoc(ln, tb[MSG_CLIENTS]); 452 | } 453 | 454 | static void 455 | usteer_local_node_status_cb(struct ubus_request *req, int type, struct blob_attr *msg) 456 | { 457 | enum { 458 | MSG_FREQ, 459 | MSG_CHANNEL, 460 | MSG_OP_CLASS, 461 | __MSG_MAX, 462 | }; 463 | static struct blobmsg_policy policy[__MSG_MAX] = { 464 | [MSG_FREQ] = { "freq", BLOBMSG_TYPE_INT32 }, 465 | [MSG_CHANNEL] = { "channel", BLOBMSG_TYPE_INT32 }, 466 | [MSG_OP_CLASS] = { "op_class", BLOBMSG_TYPE_INT32 }, 467 | }; 468 | struct blob_attr *tb[__MSG_MAX]; 469 | struct usteer_local_node *ln; 470 | struct usteer_node *node; 471 | 472 | ln = container_of(req, struct usteer_local_node, req); 473 | node = &ln->node; 474 | 475 | blobmsg_parse(policy, __MSG_MAX, tb, blob_data(msg), blob_len(msg)); 476 | if (tb[MSG_FREQ]) 477 | node->freq = blobmsg_get_u32(tb[MSG_FREQ]); 478 | if (tb[MSG_CHANNEL]) 479 | node->channel = blobmsg_get_u32(tb[MSG_CHANNEL]); 480 | if (tb[MSG_OP_CLASS]) 481 | node->op_class = blobmsg_get_u32(tb[MSG_OP_CLASS]); 482 | } 483 | 484 | static void 485 | usteer_local_node_rrm_nr_cb(struct ubus_request *req, int type, struct blob_attr *msg) 486 | { 487 | static const struct blobmsg_policy policy = { 488 | "value", BLOBMSG_TYPE_ARRAY 489 | }; 490 | struct usteer_local_node *ln; 491 | struct blob_attr *tb; 492 | 493 | ln = container_of(req, struct usteer_local_node, req); 494 | 495 | blobmsg_parse(&policy, 1, &tb, blob_data(msg), blob_len(msg)); 496 | if (!tb) 497 | return; 498 | 499 | usteer_node_set_blob(&ln->node.rrm_nr, tb); 500 | } 501 | 502 | static void 503 | usteer_local_node_req_cb(struct ubus_request *req, int ret) 504 | { 505 | struct usteer_local_node *ln; 506 | 507 | ln = container_of(req, struct usteer_local_node, req); 508 | uloop_timeout_set(&ln->req_timer, 1); 509 | } 510 | 511 | static bool 512 | usteer_add_rrm_data(struct usteer_local_node *ln, struct usteer_node *node) 513 | { 514 | if (node == &ln->node) 515 | return false; 516 | 517 | if (!node->rrm_nr) 518 | return false; 519 | 520 | /* Remote node only adds same SSID. Required for local-node. */ 521 | if (strcmp(ln->node.ssid, node->ssid) != 0) 522 | return false; 523 | 524 | blobmsg_add_field(&b, BLOBMSG_TYPE_ARRAY, "", 525 | blobmsg_data(node->rrm_nr), 526 | blobmsg_data_len(node->rrm_nr)); 527 | 528 | return true; 529 | } 530 | 531 | static void 532 | usteer_local_node_prepare_rrm_set(struct usteer_local_node *ln) 533 | { 534 | struct usteer_node *node, *last_remote_neighbor = NULL; 535 | int i = 0; 536 | void *c; 537 | 538 | c = blobmsg_open_array(&b, "list"); 539 | for_each_local_node(node) { 540 | if (i >= config.max_neighbor_reports) 541 | break; 542 | if (usteer_add_rrm_data(ln, node)) 543 | i++; 544 | } 545 | 546 | while (i < config.max_neighbor_reports) { 547 | node = usteer_node_get_next_neighbor(&ln->node, last_remote_neighbor); 548 | if (!node) { 549 | /* No more nodes available */ 550 | break; 551 | } 552 | 553 | last_remote_neighbor = node; 554 | if (usteer_add_rrm_data(ln, node)) 555 | i++; 556 | } 557 | 558 | blobmsg_close_array(&b, c); 559 | } 560 | 561 | static void 562 | usteer_local_node_state_next(struct uloop_timeout *timeout) 563 | { 564 | struct usteer_local_node *ln; 565 | 566 | ln = container_of(timeout, struct usteer_local_node, req_timer); 567 | 568 | ln->req_state++; 569 | if (ln->req_state >= __REQ_MAX) { 570 | ln->req_state = REQ_IDLE; 571 | return; 572 | } 573 | 574 | blob_buf_init(&b, 0); 575 | switch (ln->req_state) { 576 | case REQ_CLIENTS: 577 | ubus_invoke_async(ubus_ctx, ln->obj_id, "get_clients", b.head, &ln->req); 578 | ln->req.data_cb = usteer_local_node_list_cb; 579 | break; 580 | case REQ_STATUS: 581 | ubus_invoke_async(ubus_ctx, ln->obj_id, "get_status", b.head, &ln->req); 582 | ln->req.data_cb = usteer_local_node_status_cb; 583 | break; 584 | case REQ_RRM_SET_LIST: 585 | usteer_local_node_prepare_rrm_set(ln); 586 | ubus_invoke_async(ubus_ctx, ln->obj_id, "rrm_nr_set", b.head, &ln->req); 587 | ln->req.data_cb = NULL; 588 | break; 589 | case REQ_RRM_GET_OWN: 590 | ubus_invoke_async(ubus_ctx, ln->obj_id, "rrm_nr_get_own", b.head, &ln->req); 591 | ln->req.data_cb = usteer_local_node_rrm_nr_cb; 592 | break; 593 | default: 594 | break; 595 | } 596 | ln->req.complete_cb = usteer_local_node_req_cb; 597 | ubus_complete_request_async(ubus_ctx, &ln->req); 598 | } 599 | 600 | static void 601 | usteer_local_node_update(struct uloop_timeout *timeout) 602 | { 603 | struct usteer_local_node *ln; 604 | struct usteer_node_handler *h; 605 | struct usteer_node *node; 606 | 607 | ln = container_of(timeout, struct usteer_local_node, update); 608 | node = &ln->node; 609 | 610 | list_for_each_entry(h, &node_handlers, list) { 611 | if (!h->update_node) 612 | continue; 613 | 614 | h->update_node(node); 615 | } 616 | 617 | usteer_local_node_state_reset(ln); 618 | uloop_timeout_set(&ln->req_timer, 1); 619 | usteer_local_node_kick(ln); 620 | uloop_timeout_set(timeout, config.local_sta_update); 621 | } 622 | 623 | static void 624 | usteer_local_node_process_bss_tm_queries(struct uloop_timeout *timeout) 625 | { 626 | struct usteer_bss_tm_query *query, *tmp; 627 | struct usteer_local_node *ln; 628 | struct usteer_node *node; 629 | struct sta_info *si; 630 | struct sta *sta; 631 | 632 | uint8_t validity_period = 100; /* ~ 10 seconds */ 633 | 634 | ln = container_of(timeout, struct usteer_local_node, bss_tm_queries_timeout); 635 | node = &ln->node; 636 | 637 | list_for_each_entry_safe(query, tmp, &ln->bss_tm_queries, list) { 638 | sta = usteer_sta_get(query->sta_addr, false); 639 | if (!sta) 640 | continue; 641 | 642 | si = usteer_sta_info_get(sta, node, false); 643 | if (!si) 644 | continue; 645 | 646 | usteer_ubus_bss_transition_request(si, query->dialog_token, false, false, validity_period); 647 | } 648 | 649 | /* Free pending queries we can not handle */ 650 | usteer_local_node_pending_bss_tm_free(ln); 651 | } 652 | 653 | static struct usteer_local_node * 654 | usteer_get_node(struct ubus_context *ctx, const char *name) 655 | { 656 | struct usteer_local_node *ln; 657 | struct usteer_node *node; 658 | char *str; 659 | 660 | ln = avl_find_element(&local_nodes, name, ln, node.avl); 661 | if (ln) 662 | return ln; 663 | 664 | ln = calloc_a(sizeof(*ln), &str, strlen(name) + 1); 665 | node = &ln->node; 666 | node->type = NODE_TYPE_LOCAL; 667 | node->created = current_time; 668 | node->avl.key = strcpy(str, name); 669 | ln->ev.remove_cb = usteer_handle_remove; 670 | ln->ev.cb = usteer_handle_event; 671 | ln->update.cb = usteer_local_node_update; 672 | ln->req_timer.cb = usteer_local_node_state_next; 673 | ubus_register_subscriber(ctx, &ln->ev); 674 | avl_insert(&local_nodes, &node->avl); 675 | kvlist_init(&ln->node_info, kvlist_blob_len); 676 | INIT_LIST_HEAD(&node->sta_info); 677 | INIT_LIST_HEAD(&node->measurements); 678 | 679 | ln->bss_tm_queries_timeout.cb = usteer_local_node_process_bss_tm_queries; 680 | INIT_LIST_HEAD(&ln->bss_tm_queries); 681 | return ln; 682 | } 683 | 684 | static void 685 | usteer_node_run_update_script(struct usteer_node *node) 686 | { 687 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 688 | char *val; 689 | 690 | if (!node_up_script) 691 | return; 692 | 693 | val = alloca(strlen(node_up_script) + strlen(ln->iface) + 8); 694 | sprintf(val, "%s '%s'", node_up_script, ln->iface); 695 | if (system(val)) 696 | MSG(INFO, "failed to execute %s\n", val); 697 | } 698 | 699 | static void 700 | usteer_check_node_enabled(struct usteer_local_node *ln) 701 | { 702 | bool ssid_disabled = config.ssid_list; 703 | struct blob_attr *cur; 704 | int rem; 705 | 706 | blobmsg_for_each_attr(cur, config.ssid_list, rem) { 707 | if (strcmp(blobmsg_get_string(cur), ln->node.ssid) != 0) 708 | continue; 709 | 710 | ssid_disabled = false; 711 | break; 712 | } 713 | 714 | if (ln->node.disabled == ssid_disabled) 715 | return; 716 | 717 | ln->node.disabled = ssid_disabled; 718 | 719 | if (ssid_disabled) { 720 | MSG(INFO, "Disconnecting from local node %s\n", usteer_node_name(&ln->node)); 721 | usteer_local_node_state_reset(ln); 722 | usteer_sta_node_cleanup(&ln->node); 723 | usteer_measurement_report_node_cleanup(&ln->node); 724 | uloop_timeout_cancel(&ln->update); 725 | ubus_unsubscribe(ubus_ctx, &ln->ev, ln->obj_id); 726 | return; 727 | } 728 | 729 | MSG(INFO, "Connecting to local node %s\n", usteer_node_name(&ln->node)); 730 | ubus_subscribe(ubus_ctx, &ln->ev, ln->obj_id); 731 | uloop_timeout_set(&ln->update, 1); 732 | usteer_node_run_update_script(&ln->node); 733 | } 734 | 735 | /* A new ubus object appeared. Figure out if we want to subscribe to it. */ 736 | static void 737 | usteer_register_node(struct ubus_context *ctx, const char *name, uint32_t id) 738 | { 739 | struct usteer_local_node *ln; 740 | struct usteer_node_handler *h; 741 | const char *iface; 742 | int offset = sizeof("hostapd.") - 1; 743 | 744 | iface = name + offset; 745 | if (strncmp(name, "hostapd.", iface - name) != 0) 746 | return; 747 | 748 | MSG(INFO, "Creating local node %s\n", name); 749 | ln = usteer_get_node(ctx, name); 750 | ln->obj_id = id; 751 | ln->iface = usteer_node_name(&ln->node) + offset; 752 | ln->ifindex = if_nametoindex(ln->iface); 753 | 754 | blob_buf_init(&b, 0); 755 | blobmsg_add_u32(&b, "notify_response", 1); 756 | ubus_invoke(ctx, id, "notify_response", b.head, NULL, NULL, 1000); 757 | 758 | /* Enable 802.11k support */ 759 | blob_buf_init(&b, 0); 760 | blobmsg_add_u8(&b, "neighbor_report", 1); 761 | blobmsg_add_u8(&b, "beacon_report", 1); 762 | blobmsg_add_u8(&b, "bss_transition", 1); 763 | ubus_invoke(ctx, id, "bss_mgmt_enable", b.head, NULL, NULL, 1000); 764 | 765 | list_for_each_entry(h, &node_handlers, list) { 766 | if (!h->init_node) 767 | continue; 768 | 769 | h->init_node(&ln->node); 770 | } 771 | 772 | ln->node.disabled = true; 773 | usteer_check_node_enabled(ln); 774 | } 775 | 776 | /* This callback that gets called whenever a new instance appears on ubus */ 777 | static void 778 | usteer_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, 779 | const char *type, struct blob_attr *msg) 780 | { 781 | static const struct blobmsg_policy policy[2] = { 782 | { .name = "id", .type = BLOBMSG_TYPE_INT32 }, 783 | { .name = "path", .type = BLOBMSG_TYPE_STRING }, 784 | }; 785 | struct blob_attr *tb[2]; 786 | const char *path; 787 | 788 | blobmsg_parse(policy, 2, tb, blob_data(msg), blob_len(msg)); 789 | 790 | if (!tb[0] || !tb[1]) 791 | return; 792 | 793 | path = blobmsg_data(tb[1]); 794 | usteer_register_node(ctx, path, blobmsg_get_u32(tb[0])); 795 | } 796 | 797 | /* Start listening to ubus events when new objects connect. This is used 798 | * to catch hostapd instances appearing */ 799 | static void 800 | usteer_register_events(struct ubus_context *ctx) 801 | { 802 | static struct ubus_event_handler handler = { 803 | .cb = usteer_event_handler 804 | }; 805 | 806 | ubus_register_event_handler(ctx, &handler, "ubus.object.add"); 807 | } 808 | 809 | static void 810 | node_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv) 811 | { 812 | usteer_register_node(ctx, obj->path, obj->id); 813 | } 814 | 815 | void config_set_node_up_script(struct blob_attr *data) 816 | { 817 | const char *val; 818 | struct usteer_node *node; 819 | 820 | if (!data) 821 | return; 822 | 823 | val = blobmsg_get_string(data); 824 | if (node_up_script && !strcmp(val, node_up_script)) 825 | return; 826 | 827 | free(node_up_script); 828 | 829 | if (!strlen(val)) { 830 | node_up_script = NULL; 831 | return; 832 | } 833 | 834 | node_up_script = strdup(val); 835 | 836 | for_each_local_node(node) 837 | usteer_node_run_update_script(node); 838 | } 839 | 840 | void config_get_node_up_script(struct blob_buf *buf) 841 | { 842 | if (!node_up_script) 843 | return; 844 | 845 | blobmsg_add_string(buf, "node_up_script", node_up_script); 846 | } 847 | 848 | void config_set_ssid_list(struct blob_attr *data) 849 | { 850 | struct usteer_local_node *ln; 851 | 852 | free(config.ssid_list); 853 | 854 | if (data && blobmsg_len(data)) 855 | config.ssid_list = blob_memdup(data); 856 | else 857 | config.ssid_list = NULL; 858 | 859 | avl_for_each_element(&local_nodes, ln, node.avl) 860 | usteer_check_node_enabled(ln); 861 | } 862 | 863 | void config_get_ssid_list(struct blob_buf *buf) 864 | { 865 | if (config.ssid_list) 866 | blobmsg_add_blob(buf, config.ssid_list); 867 | } 868 | 869 | /* Lookup all hostapd instances on ubus */ 870 | void 871 | usteer_local_nodes_init(struct ubus_context *ctx) 872 | { 873 | usteer_register_events(ctx); 874 | ubus_lookup(ctx, "hostapd.*", node_list_cb, NULL); 875 | } 876 | -------------------------------------------------------------------------------- /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 | 35 | LIST_HEAD(node_handlers); 36 | 37 | const char * const event_types[__EVENT_TYPE_MAX] = { 38 | [EVENT_TYPE_PROBE] = "probe", 39 | [EVENT_TYPE_AUTH] = "auth", 40 | [EVENT_TYPE_ASSOC] = "assoc", 41 | }; 42 | 43 | void log_msg(char *msg) 44 | { 45 | if (config.syslog) 46 | syslog(LOG_INFO, "%s\n", msg); 47 | else 48 | fprintf(stderr, "%s\n", msg); 49 | } 50 | 51 | void debug_msg(int level, const char *func, int line, const char *format, ...) 52 | { 53 | va_list ap; 54 | 55 | if (config.debug_level < level) 56 | return; 57 | 58 | if (!config.syslog) 59 | fprintf(stderr, "[%s:%d] ", func, line); 60 | 61 | va_start(ap, format); 62 | if (config.syslog) 63 | vsyslog(level >= MSG_DEBUG ? LOG_DEBUG : LOG_INFO, format, ap); 64 | else 65 | vfprintf(stderr, format, ap); 66 | va_end(ap); 67 | 68 | } 69 | 70 | void debug_msg_cont(int level, const char *format, ...) 71 | { 72 | va_list ap; 73 | 74 | if (config.debug_level < level) 75 | return; 76 | 77 | va_start(ap, format); 78 | vfprintf(stderr, format, ap); 79 | va_end(ap); 80 | } 81 | 82 | void usteer_init_defaults(void) 83 | { 84 | memset(&config, 0, sizeof(config)); 85 | 86 | config.sta_block_timeout = 30 * 1000; 87 | config.local_sta_timeout = 120 * 1000; 88 | config.measurement_report_timeout = 120 * 1000; 89 | config.local_sta_update = 1 * 1000; 90 | config.max_retry_band = 5; 91 | config.max_neighbor_reports = 8; 92 | config.seen_policy_timeout = 30 * 1000; 93 | config.band_steering_threshold = 5; 94 | config.load_balancing_threshold = 5; 95 | config.remote_update_interval = 1000; 96 | config.initial_connect_delay = 0; 97 | config.remote_node_timeout = 10; 98 | 99 | config.roam_kick_delay = 100; 100 | config.roam_process_timeout = 5 * 1000; 101 | config.roam_scan_tries = 3; 102 | config.roam_scan_timeout = 0; 103 | config.roam_scan_interval = 10 * 1000; 104 | config.roam_trigger_interval = 60 * 1000; 105 | 106 | config.min_snr_kick_delay = 5 * 1000; 107 | 108 | config.load_kick_enabled = false; 109 | config.load_kick_threshold = 75; 110 | config.load_kick_delay = 10 * 1000; 111 | config.load_kick_min_clients = 10; 112 | config.load_kick_reason_code = 5; /* WLAN_REASON_DISASSOC_AP_BUSY */ 113 | 114 | config.debug_level = MSG_FATAL; 115 | } 116 | 117 | void usteer_update_time(void) 118 | { 119 | struct timespec ts; 120 | 121 | clock_gettime(CLOCK_MONOTONIC, &ts); 122 | current_time = (uint64_t) ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 123 | } 124 | 125 | static int usage(const char *prog) 126 | { 127 | fprintf(stderr, "Usage: %s [options]\n" 128 | "Options:\n" 129 | " -v: Increase debug level (repeat for more messages):\n" 130 | " 1: info messages\n" 131 | " 2: debug messages\n" 132 | " 3: verbose debug messages\n" 133 | " 4: include network messages\n" 134 | " 5: include extra testing messages\n" 135 | " -i : Connect to other instances on interface \n" 136 | " -s: Output log messages via syslog instead of stderr\n" 137 | " remote hosts and nodes\n" 138 | "\n", prog); 139 | return 1; 140 | } 141 | 142 | int main(int argc, char **argv) 143 | { 144 | int ch; 145 | 146 | usteer_init_defaults(); 147 | 148 | while ((ch = getopt(argc, argv, "D:i:sv")) != -1) { 149 | switch(ch) { 150 | case 'v': 151 | config.debug_level++; 152 | break; 153 | case 's': 154 | config.syslog = true; 155 | break; 156 | case 'i': 157 | usteer_interface_add(optarg); 158 | break; 159 | default: 160 | return usage(argv[0]); 161 | } 162 | } 163 | 164 | openlog("usteer", 0, LOG_USER); 165 | 166 | config_set_event_log_types(NULL); 167 | usteer_update_time(); 168 | uloop_init(); 169 | 170 | ubus_ctx = ubus_connect(NULL); 171 | if (!ubus_ctx) { 172 | fprintf(stderr, "Failed to connect to ubus\n"); 173 | return -1; 174 | } 175 | 176 | ubus_add_uloop(ubus_ctx); 177 | usteer_ubus_init(ubus_ctx); 178 | usteer_local_nodes_init(ubus_ctx); 179 | uloop_run(); 180 | 181 | uloop_done(); 182 | return 0; 183 | } 184 | -------------------------------------------------------------------------------- /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 | void 24 | usteer_measurement_report_node_cleanup(struct usteer_node *node) 25 | { 26 | struct usteer_measurement_report *mr, *tmp; 27 | 28 | list_for_each_entry_safe(mr, tmp, &node->measurements, node_list) 29 | usteer_measurement_report_del(mr); 30 | } 31 | 32 | void 33 | usteer_measurement_report_sta_cleanup(struct sta *sta) 34 | { 35 | struct usteer_measurement_report *mr, *tmp; 36 | 37 | list_for_each_entry_safe(mr, tmp, &sta->measurements, sta_list) 38 | usteer_measurement_report_del(mr); 39 | } 40 | 41 | struct usteer_measurement_report * 42 | usteer_measurement_report_get(struct sta *sta, struct usteer_node *node, bool create) 43 | { 44 | struct usteer_measurement_report *mr; 45 | 46 | list_for_each_entry(mr, &sta->measurements, sta_list) { 47 | if (mr->node == node) 48 | return mr; 49 | } 50 | 51 | if (!create) 52 | return NULL; 53 | 54 | mr = calloc(1, sizeof(*mr)); 55 | if (!mr) 56 | return NULL; 57 | 58 | /* Set node & add to nodes list */ 59 | mr->node = node; 60 | list_add(&mr->node_list, &node->measurements); 61 | 62 | /* Set sta & add to STAs list */ 63 | mr->sta = sta; 64 | list_add(&mr->sta_list, &sta->measurements); 65 | 66 | /* Add to Measurement list */ 67 | list_add(&mr->list, &measurements); 68 | 69 | /* Set measurement expiration */ 70 | usteer_timeout_set(&tq, &mr->timeout, config.measurement_report_timeout); 71 | 72 | return mr; 73 | } 74 | 75 | struct usteer_measurement_report * 76 | usteer_measurement_report_add_beacon_report(struct sta *sta, struct usteer_node *node, 77 | struct usteer_beacon_report *br, uint64_t timestamp) 78 | { 79 | struct usteer_measurement_report *mr = usteer_measurement_report_get(sta, node, true); 80 | 81 | if (!mr) 82 | return NULL; 83 | 84 | mr->timestamp = timestamp; 85 | memcpy(&mr->beacon_report, br, sizeof(*br)); 86 | 87 | return mr; 88 | } 89 | 90 | void 91 | usteer_measurement_report_del(struct usteer_measurement_report *mr) 92 | { 93 | usteer_timeout_cancel(&tq, &mr->timeout); 94 | list_del(&mr->node_list); 95 | list_del(&mr->sta_list); 96 | list_del(&mr->list); 97 | free(mr); 98 | } 99 | 100 | static void 101 | usteer_measurement_timeout(struct usteer_timeout_queue *q, struct usteer_timeout *t) 102 | { 103 | struct usteer_measurement_report *mr = container_of(t, struct usteer_measurement_report, timeout); 104 | 105 | usteer_measurement_report_del(mr); 106 | } 107 | 108 | static void __usteer_init usteer_measurement_init(void) 109 | { 110 | usteer_timeout_init(&tq); 111 | tq.cb = usteer_measurement_timeout; 112 | } 113 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | usteer_sta_info_update(si, signal, true); 274 | 275 | nla_put_failure: 276 | nlmsg_free(msg); 277 | return; 278 | } 279 | 280 | static int nl80211_scan_result(struct nl_msg *msg, void *arg) 281 | { 282 | static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = { 283 | [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 }, 284 | [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 }, 285 | [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 }, 286 | }; 287 | struct nlattr *tb[NL80211_ATTR_MAX + 1]; 288 | struct nlattr *bss[NL80211_BSS_MAX + 1]; 289 | struct nl80211_scan_req *req = arg; 290 | struct usteer_scan_result data = { 291 | .signal = -127, 292 | }; 293 | struct genlmsghdr *gnlh; 294 | struct nlattr *ie_attr; 295 | int ielen = 0; 296 | uint8_t *ie; 297 | 298 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 299 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 300 | genlmsg_attrlen(gnlh, 0), NULL); 301 | 302 | if (!tb[NL80211_ATTR_BSS]) 303 | return NL_SKIP; 304 | 305 | if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], 306 | bss_policy)) 307 | return NL_SKIP; 308 | 309 | if (!bss[NL80211_BSS_BSSID] || 310 | !bss[NL80211_BSS_FREQUENCY]) 311 | return NL_SKIP; 312 | 313 | data.freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); 314 | memcpy(data.bssid, nla_data(bss[NL80211_BSS_BSSID]), sizeof(data.bssid)); 315 | 316 | if (bss[NL80211_BSS_SIGNAL_MBM]) { 317 | int32_t signal = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]); 318 | data.signal = signal / 100; 319 | } 320 | 321 | ie_attr = bss[NL80211_BSS_INFORMATION_ELEMENTS]; 322 | if (!ie_attr) 323 | ie_attr = bss[NL80211_BSS_BEACON_IES]; 324 | 325 | if (!ie_attr) 326 | goto skip_ie; 327 | 328 | ie = (uint8_t *) nla_data(ie_attr); 329 | ielen = nla_len(ie_attr); 330 | for (; ielen >= 2 && ielen >= ie[1]; 331 | ielen -= ie[1] + 2, ie += ie[1] + 2) { 332 | if (ie[0] == 0) { /* SSID */ 333 | if (ie[1] > 32) 334 | continue; 335 | 336 | memcpy(data.ssid, ie + 2, ie[1]); 337 | } 338 | } 339 | 340 | skip_ie: 341 | req->cb(req->priv, &data); 342 | 343 | return NL_SKIP; 344 | } 345 | 346 | static int nl80211_scan_event_cb(struct nl_msg *msg, void *data) 347 | { 348 | struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); 349 | 350 | switch (gnlh->cmd) { 351 | case NL80211_CMD_NEW_SCAN_RESULTS: 352 | case NL80211_CMD_SCAN_ABORTED: 353 | unl_loop_done(&unl); 354 | break; 355 | } 356 | 357 | return NL_SKIP; 358 | } 359 | 360 | static int nl80211_scan(struct usteer_node *node, struct usteer_scan_request *req, 361 | void *priv, void (*cb)(void *priv, struct usteer_scan_result *r)) 362 | { 363 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 364 | struct nl80211_scan_req reqdata = { 365 | .priv = priv, 366 | .cb = cb, 367 | }; 368 | struct nl_msg *msg; 369 | struct nlattr *cur; 370 | int i, ret; 371 | 372 | if (!ln->nl80211.present) 373 | return -ENODEV; 374 | 375 | msg = unl_genl_msg(&unl, NL80211_CMD_TRIGGER_SCAN, false); 376 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 377 | 378 | if (!req->passive) { 379 | cur = nla_nest_start(msg, NL80211_ATTR_SCAN_SSIDS); 380 | NLA_PUT(msg, 1, 0, ""); 381 | nla_nest_end(msg, cur); 382 | } 383 | 384 | NLA_PUT_U32(msg, NL80211_ATTR_SCAN_FLAGS, NL80211_SCAN_FLAG_AP); 385 | 386 | if (req->n_freq) { 387 | cur = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES); 388 | for (i = 0; i < req->n_freq; i++) 389 | NLA_PUT_U32(msg, i, req->freq[i]); 390 | nla_nest_end(msg, cur); 391 | } 392 | 393 | unl_genl_subscribe(&unl, "scan"); 394 | ret = unl_genl_request(&unl, msg, NULL, NULL); 395 | if (ret < 0) 396 | goto done; 397 | 398 | unl_genl_loop(&unl, nl80211_scan_event_cb, NULL); 399 | 400 | done: 401 | unl_genl_unsubscribe(&unl, "scan"); 402 | if (ret < 0) 403 | return ret; 404 | 405 | if (!cb) 406 | return 0; 407 | 408 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_SCAN, true); 409 | NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex); 410 | unl_genl_request(&unl, msg, nl80211_scan_result, &reqdata); 411 | 412 | return 0; 413 | 414 | nla_put_failure: 415 | nlmsg_free(msg); 416 | return -ENOMEM; 417 | } 418 | 419 | static int nl80211_wiphy_result(struct nl_msg *msg, void *arg) 420 | { 421 | struct nl80211_freqlist_req *req = arg; 422 | struct nlattr *tb[NL80211_ATTR_MAX + 1]; 423 | struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; 424 | struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; 425 | struct nlattr *nl_band; 426 | struct nlattr *nl_freq; 427 | struct nlattr *cur; 428 | struct genlmsghdr *gnlh; 429 | int rem_band; 430 | int rem_freq; 431 | 432 | gnlh = nlmsg_data(nlmsg_hdr(msg)); 433 | nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), 434 | genlmsg_attrlen(gnlh, 0), NULL); 435 | 436 | if (!tb[NL80211_ATTR_WIPHY_BANDS]) 437 | return NL_SKIP; 438 | 439 | nla_for_each_nested(nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { 440 | nla_parse(tb_band, NL80211_BAND_ATTR_MAX, nla_data(nl_band), 441 | nla_len(nl_band), NULL); 442 | 443 | if (!tb_band[NL80211_BAND_ATTR_FREQS]) 444 | continue; 445 | 446 | nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], 447 | rem_freq) { 448 | struct usteer_freq_data f = {}; 449 | 450 | nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, 451 | nla_data(nl_freq), nla_len(nl_freq), NULL); 452 | 453 | if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) 454 | continue; 455 | 456 | if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IR]) 457 | continue; 458 | 459 | cur = tb_freq[NL80211_FREQUENCY_ATTR_FREQ]; 460 | if (!cur) 461 | continue; 462 | 463 | f.freq = nla_get_u32(cur); 464 | f.dfs = !!tb_freq[NL80211_FREQUENCY_ATTR_RADAR]; 465 | 466 | cur = tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER]; 467 | if (cur) 468 | f.txpower = nla_get_u32(cur) / 100; 469 | 470 | req->cb(req->priv, &f); 471 | } 472 | } 473 | 474 | return NL_SKIP; 475 | } 476 | 477 | static void nl80211_get_freqlist(struct usteer_node *node, void *priv, 478 | void (*cb)(void *priv, struct usteer_freq_data *f)) 479 | { 480 | struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node); 481 | struct nl80211_freqlist_req req = { 482 | .priv = priv, 483 | .cb = cb 484 | }; 485 | struct nl_msg *msg; 486 | 487 | if (!ln->nl80211.present) 488 | return; 489 | 490 | msg = unl_genl_msg(&unl, NL80211_CMD_GET_WIPHY, false); 491 | 492 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, ln->wiphy); 493 | NLA_PUT_FLAG(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP); 494 | 495 | unl_genl_request(&unl, msg, nl80211_wiphy_result, &req); 496 | 497 | return; 498 | 499 | nla_put_failure: 500 | nlmsg_free(msg); 501 | } 502 | 503 | static struct usteer_node_handler nl80211_handler = { 504 | .init_node = nl80211_init_node, 505 | .free_node = nl80211_free_node, 506 | .update_sta = nl80211_update_sta, 507 | .get_survey = nl80211_get_survey, 508 | .get_freqlist = nl80211_get_freqlist, 509 | .scan = nl80211_scan, 510 | }; 511 | 512 | static void __usteer_init usteer_nl80211_init(void) 513 | { 514 | list_add(&nl80211_handler.list, &node_handlers); 515 | } 516 | -------------------------------------------------------------------------------- /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 24 | *usteer_remote_node_by_bssid(uint8_t *bssid) { 25 | struct usteer_remote_node *rn; 26 | 27 | for_each_remote_node(rn) { 28 | if (!memcmp(rn->node.bssid, bssid, 6)) 29 | return rn; 30 | } 31 | 32 | return NULL; 33 | } 34 | 35 | struct usteer_node 36 | *usteer_node_by_bssid(uint8_t *bssid) { 37 | struct usteer_remote_node *rn; 38 | struct usteer_local_node *ln; 39 | 40 | rn = usteer_remote_node_by_bssid(bssid); 41 | if (rn) 42 | return &rn->node; 43 | 44 | ln = usteer_local_node_by_bssid(bssid); 45 | if (ln) 46 | return &ln->node; 47 | 48 | return NULL; 49 | } 50 | 51 | void 52 | usteer_node_set_blob(struct blob_attr **dest, struct blob_attr *val) 53 | { 54 | int new_len; 55 | int len; 56 | 57 | if (!val) { 58 | free(*dest); 59 | *dest = NULL; 60 | return; 61 | } 62 | 63 | len = *dest ? blob_pad_len(*dest) : 0; 64 | new_len = blob_pad_len(val); 65 | if (new_len != len) 66 | *dest = realloc(*dest, new_len); 67 | memcpy(*dest, val, new_len); 68 | } 69 | 70 | static struct usteer_node * 71 | usteer_node_higher_bssid(struct usteer_node *node1, struct usteer_node *node2) 72 | { 73 | int i; 74 | 75 | for (i = 0; i < 6; i++) { 76 | if (node1->bssid[i] == node2->bssid[i]) 77 | continue; 78 | if (node1->bssid[i] < node2->bssid[i]) 79 | return node2; 80 | 81 | break; 82 | } 83 | 84 | return node1; 85 | } 86 | 87 | static struct usteer_node * 88 | usteer_node_higher_roamability(struct usteer_node *node, struct usteer_node *ref) 89 | { 90 | uint64_t roamability_node, roamability_ref; 91 | 92 | roamability_node = ((uint64_t)(node->roam_events.source + node->roam_events.target)) * current_time / ((current_time - node->created) + 1); 93 | roamability_ref = ((uint64_t)(ref->roam_events.source + ref->roam_events.target)) * current_time / ((current_time - ref->created) + 1); 94 | 95 | if (roamability_node < roamability_ref) 96 | return ref; 97 | 98 | return node; 99 | } 100 | 101 | static struct usteer_node * 102 | usteer_node_better_neighbor(struct usteer_node *node, struct usteer_node *ref) 103 | { 104 | struct usteer_node *n1, *n2; 105 | 106 | /** 107 | * 1. Return one node if the other one is NULL 108 | * 2. Return the node with the higher roam events. 109 | * 3. Return the node with the higher BSSID. 110 | * 4. Return first method argument. 111 | */ 112 | 113 | if (!ref) 114 | return node; 115 | 116 | if (!node) 117 | return ref; 118 | 119 | n1 = usteer_node_higher_roamability(node, ref); 120 | n2 = usteer_node_higher_roamability(ref, node); 121 | if (n1 == n2) 122 | return n1; 123 | 124 | /* Identical roam interactions. Check BSSID */ 125 | n1 = usteer_node_higher_bssid(node, ref); 126 | n2 = usteer_node_higher_bssid(ref, node); 127 | if (n1 == n2) 128 | return n1; 129 | 130 | return node; 131 | } 132 | 133 | struct usteer_node * 134 | usteer_node_get_next_neighbor(struct usteer_node *current_node, struct usteer_node *last) 135 | { 136 | struct usteer_remote_node *rn; 137 | struct usteer_node *next = NULL, *n1, *n2; 138 | 139 | for_each_remote_node(rn) { 140 | if (next == &rn->node) 141 | continue; 142 | 143 | if (strcmp(current_node->ssid, rn->node.ssid)) 144 | continue; 145 | 146 | /* Skip nodes which can't handle additional STA */ 147 | if (rn->node.max_assoc && rn->node.n_assoc >= rn->node.max_assoc) 148 | continue; 149 | 150 | /* Check if this node is ranked lower than the last one */ 151 | n1 = usteer_node_better_neighbor(last, &rn->node); 152 | n2 = usteer_node_better_neighbor(&rn->node, last); 153 | if (n1 != n2) { 154 | /* Identical rank. Skip. */ 155 | continue; 156 | } else if (last && n1 == &rn->node) { 157 | /* Candidate rank is higher than the last neighbor. Skip. */ 158 | continue; 159 | } 160 | 161 | /* Check with current next candidate */ 162 | n1 = usteer_node_better_neighbor(next, &rn->node); 163 | n2 = usteer_node_better_neighbor(&rn->node, next); 164 | if (n1 != n2) { 165 | /* Identical rank. Skip. */ 166 | continue; 167 | } else if (n1 != &rn->node) { 168 | /* Next candidate ranked higher. */ 169 | continue; 170 | } 171 | 172 | next = n1; 173 | } 174 | 175 | return next; 176 | } 177 | -------------------------------------------------------------------------------- /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 | struct { 61 | bool present; 62 | struct uloop_timeout update; 63 | } nl80211; 64 | struct { 65 | struct ubus_request req; 66 | bool req_pending; 67 | bool status_complete; 68 | } netifd; 69 | }; 70 | 71 | struct interface; 72 | 73 | struct usteer_remote_host { 74 | struct avl_node avl; 75 | 76 | struct list_head nodes; 77 | struct blob_attr *host_info; 78 | char *addr; 79 | }; 80 | 81 | struct usteer_remote_node { 82 | struct list_head list; 83 | struct list_head host_list; 84 | const char *name; 85 | 86 | struct usteer_remote_host *host; 87 | struct usteer_node node; 88 | 89 | int check; 90 | }; 91 | 92 | extern struct avl_tree local_nodes; 93 | extern struct list_head remote_nodes; 94 | extern struct avl_tree remote_hosts; 95 | 96 | #define for_each_local_node(node) \ 97 | avl_for_each_element(&local_nodes, node, avl) \ 98 | if (!node->disabled) 99 | 100 | #define for_each_remote_node(rn) \ 101 | list_for_each_entry(rn, &remote_nodes, list) 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | # Use IPv6 for remote exchange 9 | option 'ipv6' '0' 10 | 11 | # Minimum level of logged messages 12 | # 0 = fatal 13 | # 1 = info 14 | # 2 = verbose 15 | # 3 = some debug messages 16 | # 4 = network packet information 17 | # 5 = all debug messages 18 | option 'debug_level' '2' 19 | 20 | # Maximum number of neighbor reports set for a node 21 | #option max_neighbor_reports 8 22 | 23 | # Maximum amount of time (ms) a station may be blocked due to policy decisions 24 | #option sta_block_timeout 30000 25 | 26 | # Maximum amount of time (ms) a local unconnected station is tracked 27 | #option local_sta_timeout 120000 28 | 29 | # Maximum amount of time (ms) a measurement report is stored 30 | #option measurement_report_timeout 120000 31 | 32 | # Local station information update interval (ms) 33 | #option local_sta_update 1000 34 | 35 | # Maximum number of consecutive times a station may be blocked by policy 36 | #option max_retry_band 5 37 | 38 | # Maximum idle time of a station entry (ms) to be considered for policy decisions 39 | #option seen_policy_timeout 30000 40 | 41 | # Minimum number of stations delta between APs before load balancing policy is active 42 | #option load_balancing_threshold 5 43 | 44 | # Minimum number of stations delta between bands before band steering policy is active 45 | #option band_steering_threshold 5 46 | 47 | # Interval (ms) between sending state updates to other APs 48 | #option remote_update_interval 1000 49 | 50 | # Number of remote update intervals after which a remote-node is deleted 51 | #option remote_node_timeout 10 52 | 53 | # Allow rejecting assoc requests for steering purposes (0/1) 54 | #option assoc_steering 0 55 | 56 | # Minimum signal-to-noise ratio or signal level (dBm) to allow connections 57 | #option min_connect_snr 0 58 | 59 | # Minimum signal-to-noise ratio or signal level (dBm) to remain connected 60 | #option min_snr 0 61 | 62 | # Timeout after which a station with snr < min_snr will be kicked 63 | #option min_snr_kick_delay 5000 64 | 65 | # Timeout (in ms) after which a association following a disassociation is not seen 66 | # as a roam 67 | #option roam_process_timeout 5000 68 | 69 | # Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger 70 | # client scans for roaming 71 | #option roam_scan_snr 0 72 | 73 | # Maximum number of client roaming scan trigger attempts 74 | #option roam_scan_tries 3 75 | 76 | # Retry scanning when roam_scan_tries is exceeded after this timeout (in ms) 77 | # In case this option is set to 0, the client is kicked instead 78 | #option roam_scan_timeout 0 79 | 80 | # Minimum time (ms) between client roaming scan trigger attempts 81 | #option roam_scan_interval 10000 82 | 83 | # Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger 84 | # forced client roaming 85 | #option roam_trigger_snr 0 86 | 87 | # Minimum time (ms) between client roaming trigger attempts 88 | #option roam_trigger_interval 60000 89 | 90 | # Timeout (in 100ms beacon intervals) for client roam requests 91 | #option roam_kick_delay 100 92 | 93 | # Minimum signal strength difference until AP steering policy is active 94 | #option signal_diff_threshold 0 95 | 96 | # Initial delay (ms) before responding to probe requests (to allow other APs to see packets as well) 97 | #option initial_connect_delay 0 98 | 99 | # Enable kicking client on excessive channel load (0/1) 100 | #option load_kick_enabled 0 101 | 102 | # Minimum channel load (%) before kicking clients 103 | #option load_kick_threshold 75 104 | 105 | # Minimum amount of time (ms) that channel load is above threshold before starting to kick clients 106 | #option load_kick_delay 10000 107 | 108 | # Minimum number of connected clients before kicking based on channel load 109 | #option load_kick_min_clients 10 110 | 111 | # Reason code on client kick based on channel load (default: WLAN_REASON_DISASSOC_AP_BUSY) 112 | #option load_kick_reason_code 5 113 | 114 | # Script to run after bringing up a node 115 | #option node_up_script '' 116 | 117 | # Message types to include in log 118 | # Available types: 119 | # - probe_req_accept 120 | # - probe_req_deny 121 | # - auth_req_accept 122 | # - auth_req_deny 123 | # - assoc_req_accept 124 | # - assoc_req_deny 125 | # - load_kick_trigger 126 | # - load_kick_reset 127 | # - load_kick_min_clients 128 | # - load_kick_no_client 129 | # - load_kick_client 130 | # - signal_kick 131 | #list event_log_types '' 132 | 133 | # List of SSIDs to enable steering on 134 | #list ssid_list '' 135 | -------------------------------------------------------------------------------- /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" load_kick_enabled 70 | uci_option_to_json_bool "$cfg" assoc_steering 71 | uci_option_to_json_string "$cfg" node_up_script 72 | uci_option_to_json_string_array "$cfg" ssid_list 73 | uci_option_to_json_string_array "$cfg" event_log_types 74 | 75 | for opt in \ 76 | debug_level \ 77 | sta_block_timeout local_sta_timeout local_sta_update \ 78 | max_neighbor_reports max_retry_band seen_policy_timeout \ 79 | measurement_report_timeout \ 80 | load_balancing_threshold band_steering_threshold \ 81 | remote_update_interval remote_node_timeout\ 82 | min_connect_snr min_snr min_snr_kick_delay signal_diff_threshold \ 83 | initial_connect_delay roam_process_timeout\ 84 | roam_kick_delay roam_scan_tries roam_scan_timeout \ 85 | roam_scan_snr roam_scan_interval \ 86 | roam_trigger_snr roam_trigger_interval \ 87 | load_kick_threshold load_kick_delay load_kick_min_clients \ 88 | load_kick_reason_code 89 | do 90 | uci_option_to_json "$cfg" "$opt" 91 | done 92 | } 93 | 94 | 95 | load_config() { 96 | [ "$ENABLED" -gt 0 ] || return 97 | 98 | ubus -t 10 wait_for usteer 99 | 100 | json_init 101 | json_add_array interfaces 102 | for i in $(load_ifaces); do 103 | json_add_string "" "$i" 104 | done 105 | json_close_array 106 | 107 | config_load usteer 108 | config_foreach uci_usteer usteer 109 | 110 | ubus call usteer set_config "$(json_dump)" 111 | } 112 | 113 | reload_service() { 114 | start 115 | load_config 116 | } 117 | 118 | service_started() { 119 | load_config 120 | } 121 | 122 | service_triggers() { 123 | procd_add_reload_trigger usteer 124 | procd_add_raw_trigger "interface.*" 2000 /etc/init.d/usteer reload 125 | } 126 | 127 | start_service() 128 | { 129 | local network="$(uci -q get usteer.@usteer[-1].network)" 130 | ENABLED="$(uci -q get usteer.@usteer[-1].enabled)" 131 | ENABLED="${ENABLED:-1}" 132 | 133 | [ "$ENABLED" -gt 0 ] || return 134 | 135 | procd_open_instance 136 | procd_set_param command "$PROG" 137 | procd_close_instance 138 | } 139 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 sta_info *si_cur, struct sta_info *si_new) 26 | { 27 | int n_assoc_cur = si_cur->node->n_assoc; 28 | int n_assoc_new = si_new->node->n_assoc; 29 | bool ref_5g = si_cur->node->freq > 4000; 30 | bool node_5g = si_new->node->freq > 4000; 31 | 32 | if (ref_5g && !node_5g) 33 | n_assoc_new += config.band_steering_threshold; 34 | else if (!ref_5g && node_5g) 35 | n_assoc_cur += config.band_steering_threshold; 36 | 37 | n_assoc_new += config.load_balancing_threshold; 38 | 39 | return n_assoc_new <= n_assoc_cur; 40 | } 41 | 42 | static bool 43 | better_signal_strength(struct sta_info *si_cur, struct sta_info *si_new) 44 | { 45 | const bool is_better = si_new->signal - si_cur->signal 46 | > (int) config.signal_diff_threshold; 47 | 48 | if (!config.signal_diff_threshold) 49 | return false; 50 | 51 | return is_better; 52 | } 53 | 54 | static bool 55 | below_load_threshold(struct sta_info *si) 56 | { 57 | return si->node->n_assoc >= config.load_kick_min_clients && 58 | si->node->load > config.load_kick_threshold; 59 | } 60 | 61 | static bool 62 | has_better_load(struct sta_info *si_cur, struct sta_info *si_new) 63 | { 64 | return !below_load_threshold(si_cur) && below_load_threshold(si_new); 65 | } 66 | 67 | static bool 68 | below_max_assoc(struct sta_info *si) 69 | { 70 | struct usteer_node *node = si->node; 71 | 72 | return !node->max_assoc || node->n_assoc < node->max_assoc; 73 | } 74 | 75 | static bool 76 | over_min_signal(struct sta_info *si) 77 | { 78 | if (config.min_snr && si->signal < usteer_snr_to_signal(si->node, config.min_snr)) 79 | return false; 80 | 81 | if (config.roam_trigger_snr && si->signal < usteer_snr_to_signal(si->node, config.roam_trigger_snr)) 82 | return false; 83 | 84 | return true; 85 | } 86 | 87 | static uint32_t 88 | is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new) 89 | { 90 | uint32_t reasons = 0; 91 | 92 | if (!below_max_assoc(si_new)) 93 | return 0; 94 | 95 | if (!over_min_signal(si_new)) 96 | return 0; 97 | 98 | if (below_assoc_threshold(si_cur, si_new) && 99 | !below_assoc_threshold(si_new, si_cur)) 100 | reasons |= (1 << UEV_SELECT_REASON_NUM_ASSOC); 101 | 102 | if (better_signal_strength(si_cur, si_new)) 103 | reasons |= (1 << UEV_SELECT_REASON_SIGNAL); 104 | 105 | if (has_better_load(si_cur, si_new) && 106 | !has_better_load(si_cur, si_new)) 107 | reasons |= (1 << UEV_SELECT_REASON_LOAD); 108 | 109 | return reasons; 110 | } 111 | 112 | static struct sta_info * 113 | find_better_candidate(struct sta_info *si_ref, struct uevent *ev, uint32_t required_criteria, uint64_t max_age) 114 | { 115 | struct sta_info *si; 116 | struct sta *sta = si_ref->sta; 117 | uint32_t reasons; 118 | 119 | list_for_each_entry(si, &sta->nodes, list) { 120 | if (si == si_ref) 121 | continue; 122 | 123 | if (current_time - si->seen > config.seen_policy_timeout) 124 | continue; 125 | 126 | if (strcmp(si->node->ssid, si_ref->node->ssid) != 0) 127 | continue; 128 | 129 | if (max_age && max_age < current_time - si->seen) 130 | continue; 131 | 132 | reasons = is_better_candidate(si_ref, si); 133 | if (!reasons) 134 | continue; 135 | 136 | if (!(reasons & required_criteria)) 137 | continue; 138 | 139 | if (ev) { 140 | ev->si_other = si; 141 | ev->select_reasons = reasons; 142 | } 143 | 144 | return si; 145 | } 146 | 147 | return NULL; 148 | } 149 | 150 | int 151 | usteer_snr_to_signal(struct usteer_node *node, int snr) 152 | { 153 | int noise = -95; 154 | 155 | if (snr < 0) 156 | return snr; 157 | 158 | if (node->noise) 159 | noise = node->noise; 160 | 161 | return noise + snr; 162 | } 163 | /* Handle events coming in from hostapd. The function will evaluate if hostapd should 164 | * respond to the request */ 165 | bool 166 | usteer_check_request(struct sta_info *si, enum usteer_event_type type) 167 | { 168 | struct uevent ev = { 169 | .si_cur = si, 170 | }; 171 | int min_signal; 172 | bool ret = true; 173 | 174 | /* auth requests are always accepted */ 175 | if (type == EVENT_TYPE_AUTH) 176 | goto out; 177 | 178 | if (type == EVENT_TYPE_ASSOC) { 179 | /* Check if assoc request has lower signal than min_signal. 180 | * If this is the case, block assoc even when assoc steering is enabled. 181 | * 182 | * Otherwise, the client potentially ends up in a assoc - kick loop. 183 | */ 184 | if (config.min_snr && si->signal < usteer_snr_to_signal(si->node, config.min_snr)) { 185 | ev.reason = UEV_REASON_LOW_SIGNAL; 186 | ev.threshold.cur = si->signal; 187 | ev.threshold.ref = usteer_snr_to_signal(si->node, config.min_snr); 188 | ret = false; 189 | goto out; 190 | } else if (!config.assoc_steering) { 191 | goto out; 192 | } 193 | } 194 | 195 | /* Reject and request that has a too low signal quality */ 196 | min_signal = usteer_snr_to_signal(si->node, config.min_connect_snr); 197 | if (si->signal < min_signal) { 198 | ev.reason = UEV_REASON_LOW_SIGNAL; 199 | ev.threshold.cur = si->signal; 200 | ev.threshold.ref = min_signal; 201 | ret = false; 202 | goto out; 203 | } 204 | 205 | /* Reject if the station is younger than the Initial connect delay before responding to probe requests 206 | * this allows other APs to see packets as well 207 | */ 208 | if (type == EVENT_TYPE_PROBE && current_time - si->created < config.initial_connect_delay) { 209 | ev.reason = UEV_REASON_CONNECT_DELAY; 210 | ev.threshold.cur = current_time - si->created; 211 | ev.threshold.ref = config.initial_connect_delay; 212 | ret = false; 213 | goto out; 214 | } 215 | 216 | /* Check if any of the other APs are better suited for accepting this station */ 217 | if (!find_better_candidate(si, &ev, UEV_SELECT_REASON_ALL, 0)) 218 | goto out; 219 | 220 | /* Reject the request if a better AP was found */ 221 | ev.reason = UEV_REASON_BETTER_CANDIDATE; 222 | ev.node_cur = si->node; 223 | ret = false; 224 | 225 | out: 226 | switch (type) { 227 | case EVENT_TYPE_PROBE: 228 | ev.type = UEV_PROBE_REQ_ACCEPT; 229 | break; 230 | case EVENT_TYPE_ASSOC: 231 | ev.type = UEV_ASSOC_REQ_ACCEPT; 232 | break; 233 | case EVENT_TYPE_AUTH: 234 | ev.type = UEV_AUTH_REQ_ACCEPT; 235 | break; 236 | default: 237 | break; 238 | } 239 | 240 | /* Turn event type into REJECT if we want to reject */ 241 | if (!ret) 242 | ev.type++; 243 | 244 | if (!ret && si->stats[type].blocked_cur >= config.max_retry_band) { 245 | ev.reason = UEV_REASON_RETRY_EXCEEDED; 246 | ev.threshold.cur = si->stats[type].blocked_cur; 247 | ev.threshold.ref = config.max_retry_band; 248 | } 249 | 250 | /* send the event */ 251 | usteer_event(&ev); 252 | 253 | return ret; 254 | } 255 | 256 | static bool 257 | is_more_kickable(struct sta_info *si_cur, struct sta_info *si_new) 258 | { 259 | if (!si_cur) 260 | return true; 261 | 262 | if (si_new->kick_count > si_cur->kick_count) 263 | return false; 264 | 265 | return si_cur->signal > si_new->signal; 266 | } 267 | 268 | static void 269 | usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state, 270 | struct uevent *ev) 271 | { 272 | si->roam_event = current_time; 273 | 274 | if (si->roam_state == state) { 275 | if (si->roam_state == ROAM_TRIGGER_IDLE) { 276 | si->roam_tries = 0; 277 | return; 278 | } 279 | 280 | si->roam_tries++; 281 | } else { 282 | si->roam_tries = 0; 283 | } 284 | 285 | si->roam_state = state; 286 | usteer_event(ev); 287 | } 288 | 289 | static void 290 | usteer_roam_sm_start_scan(struct sta_info *si, struct uevent *ev) 291 | { 292 | /* Start scanning in case we are not timeout-constrained or timeout has expired */ 293 | if (!config.roam_scan_timeout || 294 | current_time > si->roam_scan_timeout_start + config.roam_scan_timeout) { 295 | usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, ev); 296 | return; 297 | } 298 | 299 | /* We are currently in scan timeout / cooldown. 300 | * Check if we are in ROAM_TRIGGER_IDLE state. Enter this state if not. 301 | */ 302 | if (si->roam_state == ROAM_TRIGGER_IDLE) 303 | return; 304 | 305 | /* Enter idle state */ 306 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev); 307 | } 308 | 309 | static bool 310 | usteer_roam_sm_found_better_node(struct sta_info *si, struct uevent *ev, enum roam_trigger_state next_state) 311 | { 312 | uint64_t max_age = 2 * config.roam_scan_interval; 313 | 314 | if (max_age > current_time - si->roam_scan_start) 315 | max_age = current_time - si->roam_scan_start; 316 | 317 | if (find_better_candidate(si, ev, (1 << UEV_SELECT_REASON_SIGNAL), max_age)) { 318 | usteer_roam_set_state(si, next_state, ev); 319 | return true; 320 | } 321 | 322 | return false; 323 | } 324 | 325 | static bool 326 | usteer_roam_trigger_sm(struct sta_info *si) 327 | { 328 | struct uevent ev = { 329 | .si_cur = si, 330 | }; 331 | uint64_t min_signal; 332 | 333 | min_signal = usteer_snr_to_signal(si->node, config.roam_trigger_snr); 334 | 335 | switch (si->roam_state) { 336 | case ROAM_TRIGGER_SCAN: 337 | if (!si->roam_tries) { 338 | si->roam_scan_start = current_time; 339 | } 340 | 341 | /* Check if we've found a better node regardless of the scan-interval */ 342 | if (usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE)) 343 | break; 344 | 345 | /* Only scan every scan-interval */ 346 | if (current_time - si->roam_event < config.roam_scan_interval) 347 | break; 348 | 349 | /* Check if no node was found within roam_scan_tries tries */ 350 | if (config.roam_scan_tries && si->roam_tries >= config.roam_scan_tries) { 351 | if (!config.roam_scan_timeout) { 352 | /* Prepare to kick client */ 353 | usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK, &ev); 354 | } else { 355 | /* Kick in scan timeout */ 356 | si->roam_scan_timeout_start = current_time; 357 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 358 | } 359 | break; 360 | } 361 | 362 | /* Send beacon-request to client */ 363 | usteer_ubus_trigger_client_scan(si); 364 | usteer_roam_sm_start_scan(si, &ev); 365 | break; 366 | 367 | case ROAM_TRIGGER_IDLE: 368 | usteer_roam_sm_start_scan(si, &ev); 369 | break; 370 | 371 | case ROAM_TRIGGER_SCAN_DONE: 372 | if (usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_WAIT_KICK)) 373 | break; 374 | 375 | /* Kick back to SCAN state if candidate expired */ 376 | usteer_roam_sm_start_scan(si, &ev); 377 | break; 378 | 379 | case ROAM_TRIGGER_WAIT_KICK: 380 | if (si->signal > min_signal) 381 | break; 382 | 383 | usteer_roam_set_state(si, ROAM_TRIGGER_NOTIFY_KICK, &ev); 384 | usteer_ubus_notify_client_disassoc(si); 385 | break; 386 | case ROAM_TRIGGER_NOTIFY_KICK: 387 | if (current_time - si->roam_event < config.roam_kick_delay * 100) 388 | break; 389 | 390 | usteer_roam_set_state(si, ROAM_TRIGGER_KICK, &ev); 391 | break; 392 | case ROAM_TRIGGER_KICK: 393 | usteer_ubus_kick_client(si); 394 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 395 | return true; 396 | } 397 | 398 | return false; 399 | } 400 | 401 | static void 402 | usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev) 403 | { 404 | struct sta_info *si; 405 | int min_signal; 406 | 407 | if (config.roam_scan_snr) 408 | min_signal = config.roam_scan_snr; 409 | else if (config.roam_trigger_snr) 410 | min_signal = config.roam_trigger_snr; 411 | else 412 | return; 413 | 414 | usteer_update_time(); 415 | min_signal = usteer_snr_to_signal(&ln->node, min_signal); 416 | 417 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 418 | if (si->connected != STA_CONNECTED || si->signal >= min_signal || 419 | current_time - si->roam_kick < config.roam_trigger_interval) { 420 | usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev); 421 | continue; 422 | } 423 | 424 | /* 425 | * If the state machine kicked a client, other clients should wait 426 | * until the next turn 427 | */ 428 | if (usteer_roam_trigger_sm(si)) 429 | return; 430 | } 431 | } 432 | 433 | static void 434 | usteer_local_node_snr_kick(struct usteer_local_node *ln) 435 | { 436 | unsigned int min_count = DIV_ROUND_UP(config.min_snr_kick_delay, config.local_sta_update); 437 | struct uevent ev = { 438 | .node_local = &ln->node, 439 | }; 440 | struct sta_info *si; 441 | int min_signal; 442 | 443 | if (!config.min_snr) 444 | return; 445 | 446 | min_signal = usteer_snr_to_signal(&ln->node, config.min_snr); 447 | ev.threshold.ref = min_signal; 448 | 449 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 450 | if (si->connected != STA_CONNECTED) 451 | continue; 452 | 453 | if (si->signal >= min_signal) { 454 | si->below_min_snr = 0; 455 | continue; 456 | } else { 457 | si->below_min_snr++; 458 | } 459 | 460 | if (si->below_min_snr <= min_count) 461 | continue; 462 | 463 | si->kick_count++; 464 | 465 | ev.type = UEV_SIGNAL_KICK; 466 | ev.threshold.cur = si->signal; 467 | ev.count = si->kick_count; 468 | usteer_event(&ev); 469 | 470 | usteer_ubus_kick_client(si); 471 | return; 472 | } 473 | } 474 | 475 | void 476 | usteer_local_node_kick(struct usteer_local_node *ln) 477 | { 478 | struct usteer_node *node = &ln->node; 479 | struct sta_info *kick1 = NULL, *kick2 = NULL; 480 | struct sta_info *candidate = NULL; 481 | struct sta_info *si; 482 | struct uevent ev = { 483 | .node_local = &ln->node, 484 | }; 485 | unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update); 486 | 487 | usteer_local_node_roam_check(ln, &ev); 488 | usteer_local_node_snr_kick(ln); 489 | 490 | if (!config.load_kick_enabled || !config.load_kick_threshold || 491 | !config.load_kick_delay) 492 | return; 493 | 494 | if (node->load < config.load_kick_threshold) { 495 | if (!ln->load_thr_count) 496 | return; 497 | 498 | ln->load_thr_count = 0; 499 | ev.type = UEV_LOAD_KICK_RESET; 500 | ev.threshold.cur = node->load; 501 | ev.threshold.ref = config.load_kick_threshold; 502 | goto out; 503 | } 504 | 505 | if (++ln->load_thr_count <= min_count) { 506 | if (ln->load_thr_count > 1) 507 | return; 508 | 509 | ev.type = UEV_LOAD_KICK_TRIGGER; 510 | ev.threshold.cur = node->load; 511 | ev.threshold.ref = config.load_kick_threshold; 512 | goto out; 513 | } 514 | 515 | ln->load_thr_count = 0; 516 | if (node->n_assoc < config.load_kick_min_clients) { 517 | ev.type = UEV_LOAD_KICK_MIN_CLIENTS; 518 | ev.threshold.cur = node->n_assoc; 519 | ev.threshold.ref = config.load_kick_min_clients; 520 | goto out; 521 | } 522 | 523 | list_for_each_entry(si, &ln->node.sta_info, node_list) { 524 | struct sta_info *tmp; 525 | 526 | if (si->connected != STA_CONNECTED) 527 | continue; 528 | 529 | if (is_more_kickable(kick1, si)) 530 | kick1 = si; 531 | 532 | tmp = find_better_candidate(si, NULL, (1 << UEV_SELECT_REASON_LOAD), 0); 533 | if (!tmp) 534 | continue; 535 | 536 | if (is_more_kickable(kick2, si)) { 537 | kick2 = si; 538 | candidate = tmp; 539 | } 540 | } 541 | 542 | if (!kick1) { 543 | ev.type = UEV_LOAD_KICK_NO_CLIENT; 544 | goto out; 545 | } 546 | 547 | if (kick2) 548 | kick1 = kick2; 549 | 550 | kick1->kick_count++; 551 | 552 | ev.type = UEV_LOAD_KICK_CLIENT; 553 | ev.si_cur = kick1; 554 | ev.si_other = candidate; 555 | ev.count = kick1->kick_count; 556 | 557 | usteer_ubus_kick_client(kick1); 558 | 559 | out: 560 | usteer_event(&ev); 561 | } 562 | -------------------------------------------------------------------------------- /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 (blob_pad_len(data) != len) { 319 | MSG(DEBUG, "Invalid message length (header: %d, real: %d)\n", blob_pad_len(data), len); 320 | return; 321 | } 322 | 323 | if (!parse_apmsg(&msg, data)) { 324 | MSG(DEBUG, "Missing fields in message\n"); 325 | return; 326 | } 327 | 328 | if (msg.id == local_id) 329 | return; 330 | 331 | MSG(NETWORK, "Received message on %s (id=%08x->%08x seq=%d len=%d)\n", 332 | interface_name(iface), msg.id, local_id, msg.seq, len); 333 | 334 | host = interface_get_host(addr_str, msg.id); 335 | usteer_node_set_blob(&host->host_info, msg.host_info); 336 | 337 | blob_for_each_attr(cur, msg.nodes, rem) 338 | interface_add_node(host, cur); 339 | } 340 | 341 | static struct interface * 342 | interface_find_by_ifindex(int index) 343 | { 344 | struct interface *iface; 345 | 346 | vlist_for_each_element(&interfaces, iface, node) { 347 | if (iface->ifindex == index) 348 | return iface; 349 | } 350 | 351 | return NULL; 352 | } 353 | 354 | static void 355 | interface_recv_v4(struct uloop_fd *u, unsigned int events) 356 | { 357 | static char buf[APMGR_BUFLEN]; 358 | static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1]; 359 | static struct sockaddr_in sin; 360 | char addr_str[INET_ADDRSTRLEN]; 361 | static struct iovec iov = { 362 | .iov_base = buf, 363 | .iov_len = sizeof(buf) 364 | }; 365 | static struct msghdr msg = { 366 | .msg_name = &sin, 367 | .msg_namelen = sizeof(sin), 368 | .msg_iov = &iov, 369 | .msg_iovlen = 1, 370 | .msg_control = cmsg_buf, 371 | .msg_controllen = sizeof(cmsg_buf), 372 | }; 373 | struct cmsghdr *cmsg; 374 | int len; 375 | 376 | do { 377 | struct in_pktinfo *pkti = NULL; 378 | struct interface *iface; 379 | 380 | len = recvmsg(u->fd, &msg, 0); 381 | if (len < 0) { 382 | switch (errno) { 383 | case EAGAIN: 384 | return; 385 | case EINTR: 386 | continue; 387 | default: 388 | perror("recvmsg"); 389 | uloop_fd_delete(u); 390 | return; 391 | } 392 | } 393 | 394 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { 395 | if (cmsg->cmsg_type != IP_PKTINFO) 396 | continue; 397 | 398 | pkti = (struct in_pktinfo *) CMSG_DATA(cmsg); 399 | } 400 | 401 | if (!pkti) { 402 | MSG(DEBUG, "Received packet without ifindex\n"); 403 | continue; 404 | } 405 | 406 | iface = interface_find_by_ifindex(pkti->ipi_ifindex); 407 | if (!iface) { 408 | MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi_ifindex); 409 | continue; 410 | } 411 | 412 | inet_ntop(AF_INET, &sin.sin_addr, addr_str, sizeof(addr_str)); 413 | 414 | interface_recv_msg(iface, addr_str, buf, len); 415 | } while (1); 416 | } 417 | 418 | 419 | static void interface_recv_v6(struct uloop_fd *u, unsigned int events){ 420 | static char buf[APMGR_BUFLEN]; 421 | static char cmsg_buf[( CMSG_SPACE(sizeof(struct in6_pktinfo)) + sizeof(int)) + 1]; 422 | static struct sockaddr_in6 sin; 423 | static struct iovec iov = { 424 | .iov_base = buf, 425 | .iov_len = sizeof(buf) 426 | }; 427 | static struct msghdr msg = { 428 | .msg_name = &sin, 429 | .msg_namelen = sizeof(sin), 430 | .msg_iov = &iov, 431 | .msg_iovlen = 1, 432 | .msg_control = cmsg_buf, 433 | .msg_controllen = sizeof(cmsg_buf), 434 | }; 435 | struct cmsghdr *cmsg; 436 | char addr_str[INET6_ADDRSTRLEN]; 437 | int len; 438 | 439 | do { 440 | struct in6_pktinfo *pkti = NULL; 441 | struct interface *iface; 442 | 443 | len = recvmsg(u->fd, &msg, 0); 444 | if (len < 0) { 445 | switch (errno) { 446 | case EAGAIN: 447 | return; 448 | case EINTR: 449 | continue; 450 | default: 451 | perror("recvmsg"); 452 | uloop_fd_delete(u); 453 | return; 454 | } 455 | } 456 | 457 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { 458 | if (cmsg->cmsg_type != IPV6_PKTINFO) 459 | continue; 460 | 461 | pkti = (struct in6_pktinfo *) CMSG_DATA(cmsg); 462 | } 463 | 464 | if (!pkti) { 465 | MSG(DEBUG, "Received packet without ifindex\n"); 466 | continue; 467 | } 468 | 469 | iface = interface_find_by_ifindex(pkti->ipi6_ifindex); 470 | if (!iface) { 471 | MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi6_ifindex); 472 | continue; 473 | } 474 | 475 | inet_ntop(AF_INET6, &sin.sin6_addr, addr_str, sizeof(addr_str)); 476 | if (sin.sin6_addr.s6_addr[0] == 0) { 477 | /* IPv4 mapped address. Ignore. */ 478 | continue; 479 | } 480 | 481 | interface_recv_msg(iface, addr_str, buf, len); 482 | } while (1); 483 | } 484 | 485 | static void interface_send_msg_v4(struct interface *iface, struct blob_attr *data) 486 | { 487 | static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1]; 488 | static struct sockaddr_in a; 489 | static struct iovec iov; 490 | static struct msghdr m = { 491 | .msg_name = (struct sockaddr *) &a, 492 | .msg_namelen = sizeof(a), 493 | .msg_iov = &iov, 494 | .msg_iovlen = 1, 495 | .msg_control = cmsg_data, 496 | .msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)), 497 | }; 498 | struct in_pktinfo *pkti; 499 | struct cmsghdr *cmsg; 500 | 501 | a.sin_family = AF_INET; 502 | a.sin_port = htons(APMGR_PORT); 503 | a.sin_addr.s_addr = ~0; 504 | 505 | memset(cmsg_data, 0, sizeof(cmsg_data)); 506 | cmsg = CMSG_FIRSTHDR(&m); 507 | cmsg->cmsg_len = m.msg_controllen; 508 | cmsg->cmsg_level = IPPROTO_IP; 509 | cmsg->cmsg_type = IP_PKTINFO; 510 | 511 | pkti = (struct in_pktinfo *) CMSG_DATA(cmsg); 512 | pkti->ipi_ifindex = iface->ifindex; 513 | 514 | iov.iov_base = data; 515 | iov.iov_len = blob_pad_len(data); 516 | 517 | if (sendmsg(remote_fd.fd, &m, 0) < 0) 518 | perror("sendmsg"); 519 | } 520 | 521 | 522 | static void interface_send_msg_v6(struct interface *iface, struct blob_attr *data) { 523 | static struct sockaddr_in6 groupSock = {}; 524 | 525 | groupSock.sin6_family = AF_INET6; 526 | inet_pton(AF_INET6, APMGR_V6_MCAST_GROUP, &groupSock.sin6_addr); 527 | groupSock.sin6_port = htons(APMGR_PORT); 528 | 529 | setsockopt(remote_fd.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex)); 530 | 531 | if (sendto(remote_fd.fd, data, blob_pad_len(data), 0, (const struct sockaddr *)&groupSock, sizeof(groupSock)) < 0) 532 | perror("sendmsg"); 533 | } 534 | 535 | static void interface_send_msg(struct interface *iface, struct blob_attr *data){ 536 | if (config.ipv6) { 537 | interface_send_msg_v6(iface, data); 538 | } else { 539 | interface_send_msg_v4(iface, data); 540 | } 541 | } 542 | 543 | static void usteer_send_sta_info(struct sta_info *sta) 544 | { 545 | int seen = current_time - sta->seen; 546 | int last_connected = !!sta->connected ? 0 : current_time - sta->last_connected; 547 | void *c; 548 | 549 | c = blob_nest_start(&buf, 0); 550 | blob_put(&buf, APMSG_STA_ADDR, sta->sta->addr, 6); 551 | blob_put_int8(&buf, APMSG_STA_CONNECTED, !!sta->connected); 552 | blob_put_int32(&buf, APMSG_STA_SIGNAL, sta->signal); 553 | blob_put_int32(&buf, APMSG_STA_SEEN, seen); 554 | blob_put_int32(&buf, APMSG_STA_LAST_CONNECTED, last_connected); 555 | blob_put_int32(&buf, APMSG_STA_TIMEOUT, config.local_sta_timeout - seen); 556 | blob_nest_end(&buf, c); 557 | } 558 | 559 | static void usteer_send_node(struct usteer_node *node, struct sta_info *sta) 560 | { 561 | void *c, *s, *r; 562 | 563 | c = blob_nest_start(&buf, 0); 564 | 565 | blob_put_string(&buf, APMSG_NODE_NAME, usteer_node_name(node)); 566 | blob_put_string(&buf, APMSG_NODE_SSID, node->ssid); 567 | blob_put_int32(&buf, APMSG_NODE_FREQ, node->freq); 568 | blob_put_int32(&buf, APMSG_NODE_NOISE, node->noise); 569 | blob_put_int32(&buf, APMSG_NODE_LOAD, node->load); 570 | blob_put_int32(&buf, APMSG_NODE_N_ASSOC, node->n_assoc); 571 | blob_put_int32(&buf, APMSG_NODE_MAX_ASSOC, node->max_assoc); 572 | blob_put_int32(&buf, APMSG_NODE_OP_CLASS, node->op_class); 573 | blob_put_int32(&buf, APMSG_NODE_CHANNEL, node->channel); 574 | blob_put(&buf, APMSG_NODE_BSSID, node->bssid, sizeof(node->bssid)); 575 | if (node->rrm_nr) { 576 | r = blob_nest_start(&buf, APMSG_NODE_RRM_NR); 577 | blobmsg_add_field(&buf, BLOBMSG_TYPE_ARRAY, "", 578 | blobmsg_data(node->rrm_nr), 579 | blobmsg_data_len(node->rrm_nr)); 580 | blob_nest_end(&buf, r); 581 | } 582 | 583 | if (node->node_info) 584 | blob_put(&buf, APMSG_NODE_NODE_INFO, 585 | blob_data(node->node_info), 586 | blob_len(node->node_info)); 587 | 588 | s = blob_nest_start(&buf, APMSG_NODE_STATIONS); 589 | 590 | if (sta) { 591 | usteer_send_sta_info(sta); 592 | } else { 593 | list_for_each_entry(sta, &node->sta_info, node_list) 594 | usteer_send_sta_info(sta); 595 | } 596 | 597 | blob_nest_end(&buf, s); 598 | 599 | blob_nest_end(&buf, c); 600 | } 601 | 602 | static void 603 | usteer_check_timeout(void) 604 | { 605 | struct usteer_remote_node *node, *tmp; 606 | int timeout = config.remote_node_timeout; 607 | 608 | list_for_each_entry_safe(node, tmp, &remote_nodes, list) { 609 | if (node->check++ > timeout) 610 | remote_node_free(node); 611 | } 612 | } 613 | 614 | static void * 615 | usteer_update_init(void) 616 | { 617 | blob_buf_init(&buf, 0); 618 | blob_put_int32(&buf, APMSG_ID, local_id); 619 | blob_put_int32(&buf, APMSG_SEQ, ++msg_seq); 620 | if (host_info_blob) 621 | blob_put(&buf, APMSG_HOST_INFO, 622 | blob_data(host_info_blob), 623 | blob_len(host_info_blob)); 624 | 625 | return blob_nest_start(&buf, APMSG_NODES); 626 | } 627 | 628 | static void 629 | usteer_update_send(void *c) 630 | { 631 | struct interface *iface; 632 | 633 | blob_nest_end(&buf, c); 634 | 635 | vlist_for_each_element(&interfaces, iface, node) 636 | interface_send_msg(iface, buf.head); 637 | } 638 | 639 | void 640 | usteer_send_sta_update(struct sta_info *si) 641 | { 642 | void *c = usteer_update_init(); 643 | usteer_send_node(si->node, si); 644 | usteer_update_send(c); 645 | } 646 | 647 | static void 648 | usteer_send_update_timer(struct uloop_timeout *t) 649 | { 650 | struct usteer_node *node; 651 | void *c; 652 | 653 | usteer_update_time(); 654 | uloop_timeout_set(t, config.remote_update_interval); 655 | 656 | if (!avl_is_empty(&local_nodes) || host_info_blob) { 657 | c = usteer_update_init(); 658 | for_each_local_node(node) 659 | usteer_send_node(node, NULL); 660 | 661 | usteer_update_send(c); 662 | } 663 | usteer_check_timeout(); 664 | } 665 | 666 | static int 667 | usteer_init_local_id(void) 668 | { 669 | FILE *f; 670 | 671 | f = fopen("/dev/urandom", "r"); 672 | if (!f) { 673 | perror("fopen(/dev/urandom)"); 674 | return -1; 675 | } 676 | 677 | if (fread(&local_id, sizeof(local_id), 1, f) < 1) 678 | return -1; 679 | 680 | fclose(f); 681 | return 0; 682 | } 683 | 684 | static int usteer_create_v4_socket() { 685 | int yes = 1; 686 | int fd; 687 | 688 | fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK | 689 | USOCK_NUMERIC | USOCK_IPV4ONLY, 690 | "0.0.0.0", APMGR_PORT_STR); 691 | if (fd < 0) { 692 | perror("usock"); 693 | return - 1; 694 | } 695 | 696 | if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0) 697 | perror("setsockopt(IP_PKTINFO)"); 698 | 699 | if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0) 700 | perror("setsockopt(SO_BROADCAST)"); 701 | 702 | return fd; 703 | } 704 | 705 | 706 | static int usteer_create_v6_socket() { 707 | struct interface *iface; 708 | struct ipv6_mreq group; 709 | int yes = 1; 710 | int fd; 711 | 712 | fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK | 713 | USOCK_NUMERIC | USOCK_IPV6ONLY, 714 | "::", APMGR_PORT_STR); 715 | if (fd < 0) { 716 | perror("usock"); 717 | return fd; 718 | } 719 | 720 | if (!inet_pton(AF_INET6, APMGR_V6_MCAST_GROUP, &group.ipv6mr_multiaddr.s6_addr)) 721 | perror("inet_pton(AF_INET6)"); 722 | 723 | /* Membership has to be added for every interface we listen on. */ 724 | vlist_for_each_element(&interfaces, iface, node) { 725 | group.ipv6mr_interface = iface->ifindex; 726 | if(setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&group, sizeof group) < 0) 727 | perror("setsockopt(IPV6_ADD_MEMBERSHIP)"); 728 | } 729 | 730 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes)) < 0) 731 | perror("setsockopt(IPV6_RECVPKTINFO)"); 732 | 733 | if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0) 734 | perror("setsockopt(SO_BROADCAST)"); 735 | 736 | return fd; 737 | } 738 | 739 | static void usteer_reload_timer(struct uloop_timeout *t) { 740 | /* Remove uloop descriptor */ 741 | if (remote_fd.fd && remote_fd.registered) { 742 | uloop_fd_delete(&remote_fd); 743 | close(remote_fd.fd); 744 | } 745 | 746 | if (config.ipv6) { 747 | remote_fd.fd = usteer_create_v6_socket(); 748 | remote_fd.cb = interface_recv_v6; 749 | } else { 750 | remote_fd.fd = usteer_create_v4_socket(); 751 | remote_fd.cb = interface_recv_v4; 752 | } 753 | 754 | if (remote_fd.fd < 0) 755 | return; 756 | 757 | uloop_fd_add(&remote_fd, ULOOP_READ); 758 | } 759 | 760 | int usteer_interface_init(void) 761 | { 762 | if (usteer_init_local_id()) 763 | return -1; 764 | 765 | remote_timer.cb = usteer_send_update_timer; 766 | remote_timer.cb(&remote_timer); 767 | 768 | reload_timer.cb = usteer_reload_timer; 769 | reload_timer.cb(&reload_timer); 770 | 771 | return 0; 772 | } 773 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | /* Remove all data related to a remote node */ 60 | void 61 | usteer_sta_node_cleanup(struct usteer_node *node) 62 | { 63 | struct sta_info *si, *tmp; 64 | 65 | free(node->rrm_nr); 66 | node->rrm_nr = NULL; 67 | 68 | list_for_each_entry_safe(si, tmp, &node->sta_info, node_list) 69 | usteer_sta_info_del(si); 70 | } 71 | 72 | static void 73 | usteer_sta_info_timeout(struct usteer_timeout_queue *q, struct usteer_timeout *t) 74 | { 75 | struct sta_info *si = container_of(t, struct sta_info, timeout); 76 | 77 | usteer_sta_info_del(si); 78 | } 79 | 80 | struct sta_info * 81 | usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create) 82 | { 83 | struct sta_info *si; 84 | 85 | list_for_each_entry(si, &sta->nodes, list) { 86 | if (si->node != node) 87 | continue; 88 | 89 | if (create) 90 | *create = false; 91 | 92 | return si; 93 | } 94 | 95 | if (!create) 96 | return NULL; 97 | 98 | MSG(DEBUG, "Create station " MAC_ADDR_FMT " entry for node %s\n", 99 | MAC_ADDR_DATA(sta->addr), usteer_node_name(node)); 100 | 101 | si = calloc(1, sizeof(*si)); 102 | si->node = node; 103 | si->sta = sta; 104 | list_add(&si->list, &sta->nodes); 105 | list_add(&si->node_list, &node->sta_info); 106 | si->created = current_time; 107 | *create = true; 108 | 109 | /* Node is by default not connected. */ 110 | usteer_sta_disconnected(si); 111 | 112 | return si; 113 | } 114 | 115 | 116 | void 117 | usteer_sta_info_update_timeout(struct sta_info *si, int timeout) 118 | { 119 | if (si->connected == STA_CONNECTED) 120 | usteer_timeout_cancel(&tq, &si->timeout); 121 | else if (timeout > 0) 122 | usteer_timeout_set(&tq, &si->timeout, timeout); 123 | else 124 | usteer_sta_info_del(si); 125 | } 126 | 127 | struct sta * 128 | usteer_sta_get(const uint8_t *addr, bool create) 129 | { 130 | struct sta *sta; 131 | 132 | sta = avl_find_element(&stations, addr, sta, avl); 133 | if (sta) 134 | return sta; 135 | 136 | if (!create) 137 | return NULL; 138 | 139 | MSG(DEBUG, "Create station entry " MAC_ADDR_FMT "\n", MAC_ADDR_DATA(addr)); 140 | sta = calloc(1, sizeof(*sta)); 141 | memcpy(sta->addr, addr, sizeof(sta->addr)); 142 | sta->avl.key = sta->addr; 143 | avl_insert(&stations, &sta->avl); 144 | INIT_LIST_HEAD(&sta->nodes); 145 | INIT_LIST_HEAD(&sta->measurements); 146 | 147 | return sta; 148 | } 149 | 150 | void usteer_sta_disconnected(struct sta_info *si) 151 | { 152 | si->connected = STA_NOT_CONNECTED; 153 | usteer_sta_info_update_timeout(si, config.local_sta_timeout); 154 | } 155 | 156 | void 157 | usteer_sta_info_update(struct sta_info *si, int signal, bool avg) 158 | { 159 | /* ignore probe request signal when connected */ 160 | if (si->connected == STA_CONNECTED && si->signal != NO_SIGNAL && !avg) 161 | signal = NO_SIGNAL; 162 | 163 | if (signal != NO_SIGNAL) 164 | si->signal = signal; 165 | 166 | si->seen = current_time; 167 | 168 | if (si->node->freq < 4000) 169 | si->sta->seen_2ghz = 1; 170 | else 171 | si->sta->seen_5ghz = 1; 172 | 173 | usteer_sta_info_update_timeout(si, config.local_sta_timeout); 174 | } 175 | 176 | bool 177 | usteer_handle_sta_event(struct usteer_node *node, const uint8_t *addr, 178 | enum usteer_event_type type, int freq, int signal) 179 | { 180 | struct sta *sta; 181 | struct sta_info *si; 182 | uint32_t diff; 183 | bool ret; 184 | bool create; 185 | 186 | sta = usteer_sta_get(addr, true); 187 | if (!sta) 188 | return -1; 189 | 190 | si = usteer_sta_info_get(sta, node, &create); 191 | usteer_sta_info_update(si, signal, false); 192 | si->stats[type].requests++; 193 | 194 | diff = si->stats[type].blocked_last_time - current_time; 195 | if (diff > config.sta_block_timeout) 196 | si->stats[type].blocked_cur = 0; 197 | 198 | ret = usteer_check_request(si, type); 199 | if (!ret) { 200 | si->stats[type].blocked_cur++; 201 | si->stats[type].blocked_total++; 202 | si->stats[type].blocked_last_time = current_time; 203 | } else { 204 | si->stats[type].blocked_cur = 0; 205 | } 206 | 207 | if (create) 208 | usteer_send_sta_update(si); 209 | 210 | return ret; 211 | } 212 | 213 | /* Helper function to figure out what kind of beacon measurement a station supports */ 214 | bool 215 | usteer_sta_supports_beacon_measurement_mode(struct sta *sta, enum usteer_beacon_measurement_mode mode) 216 | { 217 | switch (mode) { 218 | case BEACON_MEASUREMENT_PASSIVE: 219 | return sta->rrm & (1 << 4); 220 | case BEACON_MEASUREMENT_ACTIVE: 221 | return sta->rrm & (1 << 5); 222 | case BEACON_MEASUREMENT_TABLE: 223 | return sta->rrm & (1 << 6); 224 | } 225 | 226 | return false; 227 | } 228 | 229 | static void __usteer_init usteer_sta_init(void) 230 | { 231 | usteer_timeout_init(&tq); 232 | tq.cb = usteer_sta_info_timeout; 233 | } 234 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | static int 128 | usteer_ubus_client_kick(struct ubus_context *ctx, struct ubus_object *obj, 129 | struct ubus_request_data *req, const char *method, 130 | struct blob_attr *msg) 131 | { 132 | struct blob_attr *mac_str; 133 | struct usteer_node *node; 134 | struct sta_info *si; 135 | struct sta *sta; 136 | uint8_t *mac; 137 | 138 | blobmsg_parse(client_arg, 1, &mac_str, blob_data(msg), blob_len(msg)); 139 | if (!mac_str) 140 | return UBUS_STATUS_INVALID_ARGUMENT; 141 | 142 | mac = (uint8_t *) ether_aton(blobmsg_data(mac_str)); 143 | if (!mac) 144 | return UBUS_STATUS_INVALID_ARGUMENT; 145 | 146 | sta = usteer_sta_get(mac, false); 147 | if (!sta) 148 | return UBUS_STATUS_NOT_FOUND; 149 | 150 | for_each_local_node(node) { 151 | si = usteer_sta_info_get(sta, node, false); 152 | if (si) 153 | break; 154 | } 155 | 156 | if (!si) 157 | return UBUS_STATUS_NOT_FOUND; 158 | 159 | usteer_ubus_kick_client(si); 160 | 161 | return 0; 162 | } 163 | 164 | static int 165 | usteer_ubus_disassoc_immenent(struct ubus_context *ctx, struct ubus_object *obj, 166 | struct ubus_request_data *req, const char *method, 167 | struct blob_attr *msg) 168 | { 169 | struct blob_attr *mac_str; 170 | struct usteer_node *node; 171 | struct sta_info *si; 172 | struct sta *sta; 173 | uint8_t *mac; 174 | 175 | blobmsg_parse(client_arg, 1, &mac_str, blob_data(msg), blob_len(msg)); 176 | if (!mac_str) 177 | return UBUS_STATUS_INVALID_ARGUMENT; 178 | 179 | mac = (uint8_t *) ether_aton(blobmsg_data(mac_str)); 180 | if (!mac) 181 | return UBUS_STATUS_INVALID_ARGUMENT; 182 | 183 | sta = usteer_sta_get(mac, false); 184 | if (!sta) 185 | return UBUS_STATUS_NOT_FOUND; 186 | 187 | for_each_local_node(node) { 188 | si = usteer_sta_info_get(sta, node, false); 189 | if (si) 190 | break; 191 | } 192 | 193 | if (!si) 194 | return UBUS_STATUS_NOT_FOUND; 195 | 196 | usteer_ubus_notify_client_disassoc(si); 197 | 198 | return 0; 199 | } 200 | 201 | static int 202 | usteer_ubus_beacon_measure(struct ubus_context *ctx, struct ubus_object *obj, 203 | struct ubus_request_data *req, const char *method, 204 | struct blob_attr *msg) 205 | { 206 | struct usteer_node *node; 207 | struct sta_info *si; 208 | 209 | for_each_local_node(node) 210 | list_for_each_entry(si, &node->sta_info, node_list) 211 | if (si->connected == STA_CONNECTED) 212 | usteer_ubus_trigger_client_scan(si); 213 | return 0; 214 | } 215 | 216 | enum cfg_type { 217 | CFG_BOOL, 218 | CFG_I32, 219 | CFG_U32, 220 | CFG_ARRAY_CB, 221 | CFG_STRING_CB, 222 | }; 223 | 224 | struct cfg_item { 225 | enum cfg_type type; 226 | union { 227 | bool *BOOL; 228 | uint32_t *U32; 229 | int32_t *I32; 230 | struct { 231 | void (*set)(struct blob_attr *data); 232 | void (*get)(struct blob_buf *buf); 233 | } CB; 234 | } ptr; 235 | }; 236 | 237 | #define __config_items \ 238 | _cfg(BOOL, syslog), \ 239 | _cfg(U32, debug_level), \ 240 | _cfg(BOOL, ipv6), \ 241 | _cfg(U32, sta_block_timeout), \ 242 | _cfg(U32, local_sta_timeout), \ 243 | _cfg(U32, local_sta_update), \ 244 | _cfg(U32, max_neighbor_reports), \ 245 | _cfg(U32, max_retry_band), \ 246 | _cfg(U32, seen_policy_timeout), \ 247 | _cfg(U32, measurement_report_timeout), \ 248 | _cfg(U32, load_balancing_threshold), \ 249 | _cfg(U32, band_steering_threshold), \ 250 | _cfg(U32, remote_update_interval), \ 251 | _cfg(U32, remote_node_timeout), \ 252 | _cfg(BOOL, assoc_steering), \ 253 | _cfg(I32, min_connect_snr), \ 254 | _cfg(I32, min_snr), \ 255 | _cfg(U32, min_snr_kick_delay), \ 256 | _cfg(U32, roam_process_timeout), \ 257 | _cfg(I32, roam_scan_snr), \ 258 | _cfg(U32, roam_scan_tries), \ 259 | _cfg(U32, roam_scan_timeout), \ 260 | _cfg(U32, roam_scan_interval), \ 261 | _cfg(I32, roam_trigger_snr), \ 262 | _cfg(U32, roam_trigger_interval), \ 263 | _cfg(U32, roam_kick_delay), \ 264 | _cfg(U32, signal_diff_threshold), \ 265 | _cfg(U32, initial_connect_delay), \ 266 | _cfg(BOOL, load_kick_enabled), \ 267 | _cfg(U32, load_kick_threshold), \ 268 | _cfg(U32, load_kick_delay), \ 269 | _cfg(U32, load_kick_min_clients), \ 270 | _cfg(U32, load_kick_reason_code), \ 271 | _cfg(ARRAY_CB, interfaces), \ 272 | _cfg(STRING_CB, node_up_script), \ 273 | _cfg(ARRAY_CB, event_log_types), \ 274 | _cfg(ARRAY_CB, ssid_list) 275 | 276 | enum cfg_items { 277 | #define _cfg(_type, _name) CFG_##_name 278 | __config_items, 279 | #undef _cfg 280 | __CFG_MAX, 281 | }; 282 | 283 | static const struct blobmsg_policy config_policy[__CFG_MAX] = { 284 | #define _cfg_policy(_type, _name) [CFG_##_name] = { .name = #_name, .type = BLOBMSG_TYPE_ ## _type } 285 | #define _cfg_policy_BOOL(_name) _cfg_policy(BOOL, _name) 286 | #define _cfg_policy_U32(_name) _cfg_policy(INT32, _name) 287 | #define _cfg_policy_I32(_name) _cfg_policy(INT32, _name) 288 | #define _cfg_policy_ARRAY_CB(_name) _cfg_policy(ARRAY, _name) 289 | #define _cfg_policy_STRING_CB(_name) _cfg_policy(STRING, _name) 290 | #define _cfg(_type, _name) _cfg_policy_##_type(_name) 291 | __config_items, 292 | #undef _cfg 293 | }; 294 | 295 | static const struct cfg_item config_data[__CFG_MAX] = { 296 | #define _cfg_data_BOOL(_name) .ptr.BOOL = &config._name 297 | #define _cfg_data_U32(_name) .ptr.U32 = &config._name 298 | #define _cfg_data_I32(_name) .ptr.I32 = &config._name 299 | #define _cfg_data_ARRAY_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name } 300 | #define _cfg_data_STRING_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name } 301 | #define _cfg(_type, _name) [CFG_##_name] = { .type = CFG_##_type, _cfg_data_##_type(_name) } 302 | __config_items, 303 | #undef _cfg 304 | }; 305 | 306 | static int 307 | usteer_ubus_get_config(struct ubus_context *ctx, struct ubus_object *obj, 308 | struct ubus_request_data *req, const char *method, 309 | struct blob_attr *msg) 310 | { 311 | int i; 312 | 313 | blob_buf_init(&b, 0); 314 | for (i = 0; i < __CFG_MAX; i++) { 315 | switch(config_data[i].type) { 316 | case CFG_BOOL: 317 | blobmsg_add_u8(&b, config_policy[i].name, 318 | *config_data[i].ptr.BOOL); 319 | break; 320 | case CFG_I32: 321 | case CFG_U32: 322 | blobmsg_add_u32(&b, config_policy[i].name, 323 | *config_data[i].ptr.U32); 324 | break; 325 | case CFG_ARRAY_CB: 326 | case CFG_STRING_CB: 327 | config_data[i].ptr.CB.get(&b); 328 | break; 329 | } 330 | } 331 | ubus_send_reply(ctx, req, b.head); 332 | return 0; 333 | } 334 | 335 | static int 336 | usteer_ubus_set_config(struct ubus_context *ctx, struct ubus_object *obj, 337 | struct ubus_request_data *req, const char *method, 338 | struct blob_attr *msg) 339 | { 340 | struct blob_attr *tb[__CFG_MAX]; 341 | int i; 342 | 343 | if (!strcmp(method, "set_config")) 344 | usteer_init_defaults(); 345 | 346 | blobmsg_parse(config_policy, __CFG_MAX, tb, blob_data(msg), blob_len(msg)); 347 | for (i = 0; i < __CFG_MAX; i++) { 348 | switch(config_data[i].type) { 349 | case CFG_BOOL: 350 | if (!tb[i]) 351 | continue; 352 | 353 | *config_data[i].ptr.BOOL = blobmsg_get_u8(tb[i]); 354 | break; 355 | case CFG_I32: 356 | case CFG_U32: 357 | if (!tb[i]) 358 | continue; 359 | 360 | *config_data[i].ptr.U32 = blobmsg_get_u32(tb[i]); 361 | break; 362 | case CFG_ARRAY_CB: 363 | case CFG_STRING_CB: 364 | config_data[i].ptr.CB.set(tb[i]); 365 | break; 366 | } 367 | } 368 | 369 | usteer_interface_init(); 370 | 371 | return 0; 372 | } 373 | 374 | void usteer_dump_node(struct blob_buf *buf, struct usteer_node *node) 375 | { 376 | void *c, *roam_events; 377 | 378 | c = blobmsg_open_table(buf, usteer_node_name(node)); 379 | blobmsg_printf(buf, "bssid", MAC_ADDR_FMT, MAC_ADDR_DATA(node->bssid)); 380 | blobmsg_add_u32(buf, "freq", node->freq); 381 | blobmsg_add_u32(buf, "n_assoc", node->n_assoc); 382 | blobmsg_add_u32(buf, "noise", node->noise); 383 | blobmsg_add_u32(buf, "load", node->load); 384 | blobmsg_add_u32(buf, "max_assoc", node->max_assoc); 385 | 386 | roam_events = blobmsg_open_table(buf, "roam_events"); 387 | blobmsg_add_u32(buf, "source", node->roam_events.source); 388 | blobmsg_add_u32(buf, "target", node->roam_events.target); 389 | blobmsg_close_table(buf, roam_events); 390 | 391 | if (node->rrm_nr) 392 | blobmsg_add_field(buf, BLOBMSG_TYPE_ARRAY, "rrm_nr", 393 | blobmsg_data(node->rrm_nr), 394 | blobmsg_data_len(node->rrm_nr)); 395 | if (node->node_info) 396 | blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "node_info", 397 | blob_data(node->node_info), 398 | blob_len(node->node_info)); 399 | 400 | blobmsg_close_table(buf, c); 401 | } 402 | 403 | void usteer_dump_host(struct blob_buf *buf, struct usteer_remote_host *host) 404 | { 405 | void *c; 406 | 407 | c = blobmsg_open_table(buf, host->addr); 408 | blobmsg_add_u32(buf, "id", (uint32_t)(uintptr_t)host->avl.key); 409 | if (host->host_info) 410 | blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "host_info", 411 | blobmsg_data(host->host_info), 412 | blobmsg_len(host->host_info)); 413 | blobmsg_close_table(buf, c); 414 | } 415 | 416 | static int 417 | usteer_ubus_local_info(struct ubus_context *ctx, struct ubus_object *obj, 418 | struct ubus_request_data *req, const char *method, 419 | struct blob_attr *msg) 420 | { 421 | struct usteer_node *node; 422 | 423 | blob_buf_init(&b, 0); 424 | 425 | for_each_local_node(node) 426 | usteer_dump_node(&b, node); 427 | 428 | ubus_send_reply(ctx, req, b.head); 429 | 430 | return 0; 431 | } 432 | 433 | static int 434 | usteer_ubus_remote_hosts(struct ubus_context *ctx, struct ubus_object *obj, 435 | struct ubus_request_data *req, const char *method, 436 | struct blob_attr *msg) 437 | { 438 | struct usteer_remote_host *host; 439 | 440 | blob_buf_init(&b, 0); 441 | 442 | avl_for_each_element(&remote_hosts, host, avl) 443 | usteer_dump_host(&b, host); 444 | 445 | ubus_send_reply(ctx, req, b.head); 446 | 447 | return 0; 448 | } 449 | 450 | static int 451 | usteer_ubus_remote_info(struct ubus_context *ctx, struct ubus_object *obj, 452 | struct ubus_request_data *req, const char *method, 453 | struct blob_attr *msg) 454 | { 455 | struct usteer_remote_node *rn; 456 | 457 | blob_buf_init(&b, 0); 458 | 459 | for_each_remote_node(rn) 460 | usteer_dump_node(&b, &rn->node); 461 | 462 | ubus_send_reply(ctx, req, b.head); 463 | 464 | return 0; 465 | } 466 | 467 | static int 468 | usteer_ubus_get_connected_clients(struct ubus_context *ctx, struct ubus_object *obj, 469 | struct ubus_request_data *req, const char *method, 470 | struct blob_attr *msg) 471 | { 472 | struct usteer_measurement_report *mr; 473 | struct usteer_node *node; 474 | struct sta_info *si; 475 | void *n, *s, *t, *a; 476 | 477 | blob_buf_init(&b, 0); 478 | 479 | for_each_local_node(node) { 480 | n = blobmsg_open_table(&b, usteer_node_name(node)); 481 | 482 | list_for_each_entry(si, &node->sta_info, node_list) { 483 | if (si->connected != STA_CONNECTED) 484 | continue; 485 | 486 | s = blobmsg_open_table_mac(&b, si->sta->addr); 487 | blobmsg_add_u32(&b, "signal", si->signal); 488 | blobmsg_add_u64(&b, "created", si->created); 489 | blobmsg_add_u64(&b, "seen", si->seen); 490 | blobmsg_add_u64(&b, "last_connected", si->last_connected); 491 | 492 | t = blobmsg_open_table(&b, "snr-kick"); 493 | blobmsg_add_u32(&b, "seen-below", si->below_min_snr); 494 | blobmsg_close_table(&b, t); 495 | 496 | t = blobmsg_open_table(&b, "load-kick"); 497 | blobmsg_add_u32(&b, "count", si->kick_count); 498 | blobmsg_close_table(&b, t); 499 | 500 | t = blobmsg_open_table(&b, "roam-state-machine"); 501 | blobmsg_add_u32(&b, "tries", si->roam_tries); 502 | blobmsg_add_u64(&b, "event", si->roam_event); 503 | blobmsg_add_u64(&b, "kick", si->roam_kick); 504 | blobmsg_add_u64(&b, "scan_start", si->roam_scan_start); 505 | blobmsg_add_u64(&b, "scan_timeout_start", si->roam_scan_timeout_start); 506 | blobmsg_close_table(&b, t); 507 | 508 | t = blobmsg_open_table(&b, "bss-transition-response"); 509 | blobmsg_add_u32(&b, "status-code", si->bss_transition_response.status_code); 510 | blobmsg_add_u64(&b, "timestamp", si->bss_transition_response.timestamp); 511 | blobmsg_close_table(&b, t); 512 | 513 | /* Measurements */ 514 | a = blobmsg_open_array(&b, "measurements"); 515 | list_for_each_entry(mr, &si->sta->measurements, sta_list) { 516 | t = blobmsg_open_table(&b, ""); 517 | blobmsg_add_string(&b, "node", usteer_node_name(mr->node)); 518 | blobmsg_add_u32(&b, "rcpi", mr->beacon_report.rcpi); 519 | blobmsg_add_u32(&b, "rsni", mr->beacon_report.rsni); 520 | blobmsg_add_u64(&b, "timestamp", mr->timestamp); 521 | blobmsg_close_table(&b, t); 522 | } 523 | blobmsg_close_array(&b, a); 524 | 525 | blobmsg_close_table(&b, s); 526 | } 527 | 528 | blobmsg_close_table(&b, n); 529 | } 530 | 531 | ubus_send_reply(ctx, req, b.head); 532 | 533 | return 0; 534 | } 535 | 536 | enum { 537 | NODE_DATA_NODE, 538 | NODE_DATA_VALUES, 539 | __NODE_DATA_MAX, 540 | }; 541 | 542 | static const struct blobmsg_policy set_node_data_policy[] = { 543 | [NODE_DATA_NODE] = { "node", BLOBMSG_TYPE_STRING }, 544 | [NODE_DATA_VALUES] = { "data", BLOBMSG_TYPE_TABLE }, 545 | }; 546 | 547 | static const struct blobmsg_policy del_node_data_policy[] = { 548 | [NODE_DATA_NODE] = { "node", BLOBMSG_TYPE_STRING }, 549 | [NODE_DATA_VALUES] = { "names", BLOBMSG_TYPE_ARRAY }, 550 | }; 551 | 552 | static void 553 | usteer_update_kvlist_data(struct kvlist *kv, struct blob_attr *data, 554 | bool delete) 555 | { 556 | struct blob_attr *cur; 557 | int rem; 558 | 559 | blobmsg_for_each_attr(cur, data, rem) { 560 | if (delete) 561 | kvlist_delete(kv, blobmsg_get_string(cur)); 562 | else 563 | kvlist_set(kv, blobmsg_name(cur), cur); 564 | } 565 | } 566 | 567 | static void 568 | usteer_update_kvlist_blob(struct blob_attr **dest, struct kvlist *kv) 569 | { 570 | struct blob_attr *val; 571 | const char *name; 572 | 573 | blob_buf_init(&b, 0); 574 | kvlist_for_each(kv, name, val) 575 | blobmsg_add_field(&b, blobmsg_type(val), name, 576 | blobmsg_data(val), blobmsg_len(val)); 577 | 578 | val = b.head; 579 | if (!blobmsg_len(val)) 580 | val = NULL; 581 | 582 | usteer_node_set_blob(dest, val); 583 | } 584 | 585 | static int 586 | usteer_ubus_update_node_data(struct ubus_context *ctx, struct ubus_object *obj, 587 | struct ubus_request_data *req, const char *method, 588 | struct blob_attr *msg) 589 | { 590 | const struct blobmsg_policy *policy; 591 | struct blob_attr *tb[__NODE_DATA_MAX]; 592 | struct usteer_local_node *ln; 593 | struct blob_attr *val; 594 | const char *name; 595 | bool delete; 596 | 597 | delete = !strncmp(method, "del", 3); 598 | policy = delete ? del_node_data_policy : set_node_data_policy; 599 | 600 | blobmsg_parse(policy, __NODE_DATA_MAX, tb, blob_data(msg), blob_len(msg)); 601 | if (!tb[NODE_DATA_NODE] || !tb[NODE_DATA_VALUES]) 602 | return UBUS_STATUS_INVALID_ARGUMENT; 603 | 604 | name = blobmsg_get_string(tb[NODE_DATA_NODE]); 605 | val = tb[NODE_DATA_VALUES]; 606 | if (delete && blobmsg_check_array(val, BLOBMSG_TYPE_STRING) < 0) 607 | return UBUS_STATUS_INVALID_ARGUMENT; 608 | 609 | if (strcmp(name, "*") != 0) { 610 | ln = avl_find_element(&local_nodes, name, ln, node.avl); 611 | if (!ln) 612 | return UBUS_STATUS_NOT_FOUND; 613 | 614 | usteer_update_kvlist_data(&ln->node_info, val, delete); 615 | usteer_update_kvlist_blob(&ln->node.node_info, &ln->node_info); 616 | 617 | return 0; 618 | } 619 | 620 | usteer_update_kvlist_data(&host_info, val, delete); 621 | usteer_update_kvlist_blob(&host_info_blob, &host_info); 622 | 623 | return 0; 624 | } 625 | 626 | static const struct ubus_method usteer_methods[] = { 627 | UBUS_METHOD_NOARG("local_info", usteer_ubus_local_info), 628 | UBUS_METHOD_NOARG("remote_hosts", usteer_ubus_remote_hosts), 629 | UBUS_METHOD_NOARG("remote_info", usteer_ubus_remote_info), 630 | UBUS_METHOD_NOARG("connected_clients", usteer_ubus_get_connected_clients), 631 | UBUS_METHOD_NOARG("get_clients", usteer_ubus_get_clients), 632 | UBUS_METHOD("get_client_info", usteer_ubus_get_client_info, client_arg), 633 | UBUS_METHOD("kick_client", usteer_ubus_client_kick, client_arg), 634 | UBUS_METHOD("disassoc_immenent", usteer_ubus_disassoc_immenent, client_arg), 635 | UBUS_METHOD_NOARG("beacon_measure", usteer_ubus_beacon_measure), 636 | UBUS_METHOD_NOARG("get_config", usteer_ubus_get_config), 637 | UBUS_METHOD("set_config", usteer_ubus_set_config, config_policy), 638 | UBUS_METHOD("update_config", usteer_ubus_set_config, config_policy), 639 | UBUS_METHOD("set_node_data", usteer_ubus_update_node_data, set_node_data_policy), 640 | UBUS_METHOD("delete_node_data", usteer_ubus_update_node_data, del_node_data_policy), 641 | }; 642 | 643 | static struct ubus_object_type usteer_obj_type = 644 | UBUS_OBJECT_TYPE("usteer", usteer_methods); 645 | 646 | struct ubus_object usteer_obj = { 647 | .name = "usteer", 648 | .type = &usteer_obj_type, 649 | .methods = usteer_methods, 650 | .n_methods = ARRAY_SIZE(usteer_methods), 651 | }; 652 | 653 | static bool 654 | usteer_add_nr_entry(struct usteer_node *ln, struct usteer_node *node) 655 | { 656 | struct blobmsg_policy policy[3] = { 657 | { .type = BLOBMSG_TYPE_STRING }, 658 | { .type = BLOBMSG_TYPE_STRING }, 659 | { .type = BLOBMSG_TYPE_STRING }, 660 | }; 661 | struct blob_attr *tb[3]; 662 | 663 | if (!node->rrm_nr) 664 | return false; 665 | 666 | if (strcmp(ln->ssid, node->ssid) != 0) 667 | return false; 668 | 669 | blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb, 670 | blobmsg_data(node->rrm_nr), 671 | blobmsg_data_len(node->rrm_nr)); 672 | if (!tb[2]) 673 | return false; 674 | 675 | blobmsg_add_field(&b, BLOBMSG_TYPE_STRING, "", 676 | blobmsg_data(tb[2]), 677 | blobmsg_data_len(tb[2])); 678 | 679 | return true; 680 | } 681 | 682 | static void 683 | usteer_ubus_disassoc_add_neighbors(struct sta_info *si) 684 | { 685 | struct usteer_node *node, *last_remote_neighbor = NULL; 686 | int i = 0; 687 | void *c; 688 | 689 | c = blobmsg_open_array(&b, "neighbors"); 690 | for_each_local_node(node) { 691 | if (i >= config.max_neighbor_reports) 692 | break; 693 | if (si->node == node) 694 | continue; 695 | if (usteer_add_nr_entry(si->node, node)) 696 | i++; 697 | } 698 | 699 | while (i < config.max_neighbor_reports) { 700 | node = usteer_node_get_next_neighbor(si->node, last_remote_neighbor); 701 | if (!node) { 702 | /* No more nodes available */ 703 | break; 704 | } 705 | 706 | last_remote_neighbor = node; 707 | if (usteer_add_nr_entry(si->node, node)) 708 | i++; 709 | } 710 | blobmsg_close_array(&b, c); 711 | } 712 | 713 | int usteer_ubus_bss_transition_request(struct sta_info *si, 714 | uint8_t dialog_token, 715 | bool disassoc_imminent, 716 | bool abridged, 717 | uint8_t validity_period) 718 | { 719 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 720 | 721 | blob_buf_init(&b, 0); 722 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 723 | blobmsg_add_u32(&b, "dialog_token", dialog_token); 724 | blobmsg_add_u8(&b, "disassociation_imminent", disassoc_imminent); 725 | blobmsg_add_u8(&b, "abridged", abridged); 726 | blobmsg_add_u32(&b, "validity_period", validity_period); 727 | usteer_ubus_disassoc_add_neighbors(si); 728 | return ubus_invoke(ubus_ctx, ln->obj_id, "bss_transition_request", b.head, NULL, 0, 100); 729 | } 730 | 731 | int usteer_ubus_notify_client_disassoc(struct sta_info *si) 732 | { 733 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 734 | 735 | MSG(DEBUG, "Trigger disassoc imminent on sta=" MAC_ADDR_FMT "\n", MAC_ADDR_DATA(si->sta->addr)); 736 | 737 | blob_buf_init(&b, 0); 738 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 739 | blobmsg_add_u32(&b, "duration", config.roam_kick_delay); 740 | usteer_ubus_disassoc_add_neighbors(si); 741 | return ubus_invoke(ubus_ctx, ln->obj_id, "wnm_disassoc_imminent", b.head, NULL, 0, 100); 742 | } 743 | 744 | int usteer_ubus_trigger_client_scan(struct sta_info *si) 745 | { 746 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 747 | 748 | if (!usteer_sta_supports_beacon_measurement_mode(si->sta, BEACON_MEASUREMENT_ACTIVE)) { 749 | MSG(DEBUG, "STA does not support beacon measurement sta=" MAC_ADDR_FMT "\n", MAC_ADDR_DATA(si->sta->addr)); 750 | return 0; 751 | } 752 | MSG(DEBUG, "Trigger beacon measurement on sta=" MAC_ADDR_FMT "\n", MAC_ADDR_DATA(si->sta->addr)); 753 | 754 | si->scan_band = !si->scan_band; 755 | 756 | blob_buf_init(&b, 0); 757 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 758 | blobmsg_add_string(&b, "ssid", si->node->ssid); 759 | blobmsg_add_u32(&b, "mode", BEACON_MEASUREMENT_ACTIVE); 760 | blobmsg_add_u32(&b, "duration", config.roam_scan_interval / 100); 761 | blobmsg_add_u32(&b, "channel", 0); 762 | blobmsg_add_u32(&b, "op_class", si->scan_band ? 1 : 12); 763 | return ubus_invoke(ubus_ctx, ln->obj_id, "rrm_beacon_req", b.head, NULL, 0, 100); 764 | } 765 | 766 | void usteer_ubus_kick_client(struct sta_info *si) 767 | { 768 | struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node); 769 | 770 | MSG(DEBUG, "Kick station sta=" MAC_ADDR_FMT "\n", MAC_ADDR_DATA(si->sta->addr)); 771 | 772 | blob_buf_init(&b, 0); 773 | blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr)); 774 | blobmsg_add_u32(&b, "reason", config.load_kick_reason_code); 775 | blobmsg_add_u8(&b, "deauth", 1); 776 | ubus_invoke(ubus_ctx, ln->obj_id, "del_client", b.head, NULL, 0, 100); 777 | usteer_sta_disconnected(si); 778 | si->roam_kick = current_time; 779 | } 780 | 781 | void usteer_ubus_init(struct ubus_context *ctx) 782 | { 783 | ubus_add_object(ctx, &usteer_obj); 784 | } 785 | -------------------------------------------------------------------------------- /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 | 153 | uint32_t sta_block_timeout; 154 | uint32_t local_sta_timeout; 155 | uint32_t local_sta_update; 156 | 157 | uint32_t max_retry_band; 158 | uint32_t seen_policy_timeout; 159 | uint32_t measurement_report_timeout; 160 | 161 | bool assoc_steering; 162 | 163 | uint32_t max_neighbor_reports; 164 | 165 | uint32_t band_steering_threshold; 166 | uint32_t load_balancing_threshold; 167 | 168 | uint32_t remote_update_interval; 169 | uint32_t remote_node_timeout; 170 | 171 | int32_t min_snr; 172 | uint32_t min_snr_kick_delay; 173 | int32_t min_connect_snr; 174 | uint32_t signal_diff_threshold; 175 | 176 | int32_t roam_scan_snr; 177 | uint32_t roam_process_timeout; 178 | 179 | uint32_t roam_scan_tries; 180 | uint32_t roam_scan_timeout; 181 | uint32_t roam_scan_interval; 182 | 183 | int32_t roam_trigger_snr; 184 | uint32_t roam_trigger_interval; 185 | 186 | uint32_t roam_kick_delay; 187 | 188 | uint32_t initial_connect_delay; 189 | 190 | bool load_kick_enabled; 191 | uint32_t load_kick_threshold; 192 | uint32_t load_kick_delay; 193 | uint32_t load_kick_min_clients; 194 | uint32_t load_kick_reason_code; 195 | 196 | const char *node_up_script; 197 | uint32_t event_log_mask; 198 | 199 | struct blob_attr *ssid_list; 200 | }; 201 | 202 | struct usteer_bss_tm_query { 203 | struct list_head list; 204 | 205 | /* Can't use sta_info here, as the STA might already be deleted */ 206 | uint8_t sta_addr[6]; 207 | uint8_t dialog_token; 208 | }; 209 | 210 | struct sta_info_stats { 211 | uint32_t requests; 212 | uint32_t blocked_cur; 213 | uint32_t blocked_total; 214 | uint32_t blocked_last_time; 215 | }; 216 | 217 | enum roam_trigger_state { 218 | ROAM_TRIGGER_IDLE, 219 | ROAM_TRIGGER_SCAN, 220 | ROAM_TRIGGER_SCAN_DONE, 221 | ROAM_TRIGGER_WAIT_KICK, 222 | ROAM_TRIGGER_NOTIFY_KICK, 223 | ROAM_TRIGGER_KICK, 224 | }; 225 | 226 | struct sta_info { 227 | struct list_head list; 228 | struct list_head node_list; 229 | 230 | struct usteer_node *node; 231 | struct sta *sta; 232 | 233 | struct usteer_timeout timeout; 234 | 235 | struct sta_info_stats stats[__EVENT_TYPE_MAX]; 236 | uint64_t created; 237 | uint64_t seen; 238 | uint64_t last_connected; 239 | int signal; 240 | 241 | enum roam_trigger_state roam_state; 242 | uint8_t roam_tries; 243 | uint64_t roam_event; 244 | uint64_t roam_kick; 245 | uint64_t roam_scan_start; 246 | uint64_t roam_scan_timeout_start; 247 | 248 | struct { 249 | uint8_t status_code; 250 | uint64_t timestamp; 251 | } bss_transition_response; 252 | 253 | int kick_count; 254 | 255 | uint32_t below_min_snr; 256 | 257 | uint8_t scan_band : 1; 258 | uint8_t connected : 2; 259 | }; 260 | 261 | struct sta { 262 | struct avl_node avl; 263 | struct list_head nodes; 264 | struct list_head measurements; 265 | 266 | uint8_t seen_2ghz : 1; 267 | uint8_t seen_5ghz : 1; 268 | 269 | uint8_t addr[6]; 270 | 271 | uint8_t rrm; 272 | }; 273 | 274 | struct usteer_beacon_report { 275 | uint8_t rcpi; 276 | uint8_t rsni; 277 | }; 278 | 279 | struct usteer_measurement_report { 280 | struct usteer_timeout timeout; 281 | 282 | struct list_head list; 283 | 284 | struct usteer_node *node; 285 | struct list_head node_list; 286 | 287 | struct sta *sta; 288 | struct list_head sta_list; 289 | 290 | uint64_t timestamp; 291 | 292 | struct usteer_beacon_report beacon_report; 293 | }; 294 | 295 | extern struct ubus_context *ubus_ctx; 296 | extern struct usteer_config config; 297 | extern struct list_head node_handlers; 298 | extern struct avl_tree stations; 299 | extern struct ubus_object usteer_obj; 300 | extern uint64_t current_time; 301 | extern const char * const event_types[__EVENT_TYPE_MAX]; 302 | extern struct blob_attr *host_info_blob; 303 | 304 | void usteer_update_time(void); 305 | void usteer_init_defaults(void); 306 | bool usteer_handle_sta_event(struct usteer_node *node, const uint8_t *addr, 307 | enum usteer_event_type type, int freq, int signal); 308 | 309 | int usteer_snr_to_signal(struct usteer_node *node, int snr); 310 | 311 | void usteer_local_nodes_init(struct ubus_context *ctx); 312 | void usteer_local_node_kick(struct usteer_local_node *ln); 313 | 314 | void usteer_ubus_init(struct ubus_context *ctx); 315 | void usteer_ubus_kick_client(struct sta_info *si); 316 | int usteer_ubus_trigger_client_scan(struct sta_info *si); 317 | int usteer_ubus_notify_client_disassoc(struct sta_info *si); 318 | int usteer_ubus_bss_transition_request(struct sta_info *si, 319 | uint8_t dialog_token, 320 | bool disassoc_imminent, 321 | bool abridged, 322 | uint8_t validity_period); 323 | 324 | struct sta *usteer_sta_get(const uint8_t *addr, bool create); 325 | struct sta_info *usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create); 326 | 327 | bool usteer_sta_supports_beacon_measurement_mode(struct sta *sta, enum usteer_beacon_measurement_mode mode); 328 | 329 | void usteer_sta_disconnected(struct sta_info *si); 330 | void usteer_sta_info_update_timeout(struct sta_info *si, int timeout); 331 | void usteer_sta_info_update(struct sta_info *si, int signal, bool avg); 332 | 333 | static inline const char *usteer_node_name(struct usteer_node *node) 334 | { 335 | return node->avl.key; 336 | } 337 | void usteer_node_set_blob(struct blob_attr **dest, struct blob_attr *val); 338 | 339 | struct usteer_local_node *usteer_local_node_by_bssid(uint8_t *bssid); 340 | struct usteer_remote_node *usteer_remote_node_by_bssid(uint8_t *bssid); 341 | struct usteer_node *usteer_node_by_bssid(uint8_t *bssid); 342 | 343 | struct usteer_node *usteer_node_get_next_neighbor(struct usteer_node *current_node, struct usteer_node *last); 344 | bool usteer_check_request(struct sta_info *si, enum usteer_event_type type); 345 | 346 | void config_set_interfaces(struct blob_attr *data); 347 | void config_get_interfaces(struct blob_buf *buf); 348 | 349 | void config_set_node_up_script(struct blob_attr *data); 350 | void config_get_node_up_script(struct blob_buf *buf); 351 | 352 | void config_set_ssid_list(struct blob_attr *data); 353 | void config_get_ssid_list(struct blob_buf *buf); 354 | 355 | int usteer_interface_init(void); 356 | void usteer_interface_add(const char *name); 357 | void usteer_sta_node_cleanup(struct usteer_node *node); 358 | void usteer_send_sta_update(struct sta_info *si); 359 | 360 | void usteer_run_hook(const char *name, const char *arg); 361 | 362 | void usteer_dump_node(struct blob_buf *buf, struct usteer_node *node); 363 | void usteer_dump_host(struct blob_buf *buf, struct usteer_remote_host *host); 364 | 365 | struct usteer_measurement_report * usteer_measurement_report_get(struct sta *sta, struct usteer_node *node, bool create); 366 | void usteer_measurement_report_node_cleanup(struct usteer_node *node); 367 | void usteer_measurement_report_sta_cleanup(struct sta *sta); 368 | void usteer_measurement_report_del(struct usteer_measurement_report *mr); 369 | 370 | struct usteer_measurement_report * 371 | usteer_measurement_report_add_beacon_report(struct sta *sta, struct usteer_node *node, struct usteer_beacon_report *br, uint64_t timestamp); 372 | 373 | #endif 374 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------