usteer: Initial import

Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
Felix Fietkau
2018-03-21 16:40:05 +01:00
committed by John Crispin
commit d10731f806
23 changed files with 4512 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
/Makefile
CMakeCache.txt
CMakeFiles
*.cmake
install_manifest.txt
/usteerd
/ap-monitor
/fakeap

51
CMakeLists.txt Normal file
View File

@@ -0,0 +1,51 @@
cmake_minimum_required(VERSION 2.8)
INCLUDE (CheckIncludeFiles)
INCLUDE(FindPkgConfig)
PROJECT(usteerd C)
IF("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND NOT NL_CFLAGS)
FIND_PROGRAM(PKG_CONFIG pkg-config)
IF(PKG_CONFIG)
EXECUTE_PROCESS(
COMMAND pkg-config --silence-errors --cflags libnl-tiny
OUTPUT_VARIABLE NL_CFLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
EXECUTE_PROCESS(
COMMAND pkg-config --silence-errors --libs libnl-tiny
OUTPUT_VARIABLE NL_LIBS
OUTPUT_STRIP_TRAILING_WHITESPACE)
ENDIF()
ENDIF()
CHECK_INCLUDE_FILES(pcap/pcap.h HAVE_PCAP_H)
IF(NOT HAVE_PCAP_H)
UNSET(HAVE_PCAP_H CACHE)
MESSAGE(FATAL_ERROR "pcap/pcap.h is not found")
ENDIF()
SET(SOURCES main.c local_node.c node.c sta.c policy.c ubus.c remote.c parse.c netifd.c timeout.c)
IF(NL_CFLAGS)
ADD_DEFINITIONS(${NL_CFLAGS})
SET(SOURCES ${SOURCES} nl80211.c)
ENDIF()
ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations)
FIND_LIBRARY(libjson NAMES json-c json)
ADD_EXECUTABLE(usteerd ${SOURCES})
ADD_EXECUTABLE(fakeap fakeap.c timeout.c)
TARGET_LINK_LIBRARIES(usteerd ubox ubus blobmsg_json
${LIBS_EXTRA} ${libjson} ${NL_LIBS})
TARGET_LINK_LIBRARIES(fakeap ubox ubus)
ADD_EXECUTABLE(ap-monitor monitor.c parse.c)
TARGET_LINK_LIBRARIES(ap-monitor ubox pcap blobmsg_json)
SET(CMAKE_INSTALL_PREFIX /usr)
INSTALL(TARGETS usteerd
RUNTIME DESTINATION sbin
)

244
fakeap.c Normal file
View File

@@ -0,0 +1,244 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <libubox/blobmsg.h>
#include <libubus.h>
#include <stdio.h>
#include <getopt.h>
#include "utils.h"
#include "timeout.h"
static struct blob_buf b;
static LIST_HEAD(stations);
static struct usteer_timeout_queue tq;
static FILE *r_fd;
static struct ubus_object bss_obj;
static struct ubus_context *ubus_ctx;
static int freq = 2412;
static int verbose;
struct var {
int cur;
int min;
int max;
};
struct sta_data {
struct list_head list;
struct usteer_timeout probe_t;
struct var probe;
struct var signal;
uint8_t addr[6];
};
static void gen_val(struct var *val)
{
int delta = val->max - val->min;
uint8_t v;
val->cur = val->min;
if (!delta)
return;
if (fread(&v, sizeof(v), 1, r_fd) != sizeof(v))
fprintf(stderr, "short read\n");
val->cur += (((unsigned int) v) * delta) / 0xff;
}
static void
blobmsg_add_macaddr(struct blob_buf *buf, const char *name, const uint8_t *addr)
{
char *s = blobmsg_alloc_string_buffer(buf, name, 20);
sprintf(s, MAC_ADDR_FMT, MAC_ADDR_DATA(addr));
blobmsg_add_string_buffer(buf);
}
static void sta_send_probe(struct sta_data *sta)
{
const char *type = "probe";
int ret;
int sig = -95 + sta->signal.cur;
blob_buf_init(&b, 0);
blobmsg_add_macaddr(&b, "address", sta->addr);
blobmsg_add_u32(&b, "freq", freq);
blobmsg_add_u32(&b, "signal", sig);
ret = ubus_notify(ubus_ctx, &bss_obj, type, b.head, 100);
if (verbose)
fprintf(stderr, "STA "MAC_ADDR_FMT" probe: %d (%d ms, signal: %d)\n",
MAC_ADDR_DATA(sta->addr), ret, sta->probe.cur, sig);
}
static void sta_schedule_probe(struct sta_data *sta)
{
gen_val(&sta->probe);
gen_val(&sta->signal);
usteer_timeout_set(&tq, &sta->probe_t, sta->probe.cur);
}
static void sta_probe(struct usteer_timeout_queue *q, struct usteer_timeout *t)
{
struct sta_data *sta = container_of(t, struct sta_data, probe_t);
sta_send_probe(sta);
sta_schedule_probe(sta);
}
static void init_station(struct sta_data *sta)
{
list_add_tail(&sta->list, &stations);
if (fread(&sta->addr, sizeof(sta->addr), 1, r_fd) != sizeof(sta->addr))
fprintf(stderr, "short read\n");
sta->addr[0] &= ~1;
sta_schedule_probe(sta);
}
static void create_stations(struct sta_data *ref, int n)
{
struct sta_data *sta;
int i;
tq.cb = sta_probe;
sta = calloc(n, sizeof(*sta));
for (i = 0; i < n; i++) {
memcpy(sta, ref, sizeof(*sta));
init_station(sta);
sta++;
}
}
static int usage(const char *prog)
{
fprintf(stderr, "Usage: %s <options>\n"
"Options:\n"
" -p <msec>[-<msec>]: probing interval (fixed or min-max)\n"
" -s <rssi>[-<rssi>]: rssi (signal strength) (fixed or min-max)\n"
" -n <n>: create <n> stations\n"
" -f <freq>: set operating frequency\n"
" uses parameters set before this option\n"
" -v: verbose\n"
"\n", prog);
return 1;
}
static bool parse_var(struct var *var, const char *str)
{
char *err;
var->min = strtoul(str, &err, 0);
var->max = var->min;
if (!*err)
return true;
if (*err != ':')
return false;
var->max = strtoul(err + 1, &err, 0);
if (!*err)
return true;
return false;
}
static int
hostapd_bss_get_clients(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
blob_buf_init(&b, 0);
ubus_send_reply(ctx, req, b.head);
return 0;
}
static const struct ubus_method bss_methods[] = {
UBUS_METHOD_NOARG("get_clients", hostapd_bss_get_clients),
};
static struct ubus_object_type bss_object_type =
UBUS_OBJECT_TYPE("hostapd_bss", bss_methods);
static struct ubus_object bss_obj = {
.name = "hostapd.wlan0",
.type = &bss_object_type,
.methods = bss_methods,
.n_methods = ARRAY_SIZE(bss_methods),
};
int main(int argc, char **argv)
{
struct sta_data sdata = {
.signal = { 0, -30, -30 },
.probe = { 0, 1000, 30000 },
};
int ch;
uloop_init();
r_fd = fopen("/dev/urandom", "r");
if (!r_fd) {
perror("fopen");
return 1;
}
usteer_timeout_init(&tq);
while ((ch = getopt(argc, argv, "p:s:f:n:v")) != -1) {
switch(ch) {
case 'p':
if (!parse_var(&sdata.probe, optarg))
goto usage;
break;
case 's':
if (!parse_var(&sdata.signal, optarg))
goto usage;
break;
case 'f':
freq = atoi(optarg);
break;
case 'n':
create_stations(&sdata, atoi(optarg));
break;
case 'v':
verbose++;
break;
default:
goto usage;
}
}
ubus_ctx = ubus_connect(NULL);
if (!ubus_ctx) {
fprintf(stderr, "Failed to connect to ubus\n");
return 1;
}
ubus_add_uloop(ubus_ctx);
if (ubus_add_object(ubus_ctx, &bss_obj)) {
fprintf(stderr, "Failed to register AP ubus object\n");
return 1;
}
uloop_run();
uloop_done();
return 0;
usage:
return usage(argv[0]);
}

511
local_node.c Normal file
View File

@@ -0,0 +1,511 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#ifdef linux
#include <netinet/ether.h>
#endif
#include <net/if.h>
#include <stdlib.h>
#include <libubox/avl-cmp.h>
#include <libubox/blobmsg_json.h>
#include "usteer.h"
#include "node.h"
AVL_TREE(local_nodes, avl_strcmp, false, NULL);
static struct blob_buf b;
static char *node_up_script;
static void
usteer_local_node_state_reset(struct usteer_local_node *ln)
{
if (ln->req_state == REQ_IDLE)
return;
ubus_abort_request(ubus_ctx, &ln->req);
ln->req_state = REQ_IDLE;
}
static void
usteer_free_node(struct ubus_context *ctx, struct usteer_local_node *ln)
{
struct usteer_node_handler *h;
list_for_each_entry(h, &node_handlers, list) {
if (!h->free_node)
continue;
h->free_node(&ln->node);
}
usteer_local_node_state_reset(ln);
usteer_sta_node_cleanup(&ln->node);
uloop_timeout_cancel(&ln->req_timer);
uloop_timeout_cancel(&ln->update);
avl_delete(&local_nodes, &ln->node.avl);
ubus_unregister_subscriber(ctx, &ln->ev);
free(ln);
}
static void
usteer_handle_remove(struct ubus_context *ctx, struct ubus_subscriber *s,
uint32_t id)
{
struct usteer_local_node *ln = container_of(s, struct usteer_local_node, ev);
usteer_free_node(ctx, ln);
}
static int
usteer_handle_event(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
enum {
EVENT_ADDR,
EVENT_SIGNAL,
EVENT_TARGET,
EVENT_FREQ,
__EVENT_MAX
};
struct blobmsg_policy policy[__EVENT_MAX] = {
[EVENT_ADDR] = { .name = "address", .type = BLOBMSG_TYPE_STRING },
[EVENT_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 },
[EVENT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
[EVENT_FREQ] = { .name = "freq", .type = BLOBMSG_TYPE_INT32 },
};
enum usteer_event_type ev_type = __EVENT_TYPE_MAX;
struct blob_attr *tb[__EVENT_MAX];
struct usteer_local_node *ln;
struct usteer_node *node;
int signal = NO_SIGNAL;
int freq = 0;
const char *addr_str;
const uint8_t *addr;
int i;
bool ret;
usteer_update_time();
for (i = 0; i < ARRAY_SIZE(event_types); i++) {
if (strcmp(method, event_types[i]) != 0)
continue;
ev_type = i;
break;
}
ln = container_of(obj, struct usteer_local_node, ev.obj);
node = &ln->node;
blobmsg_parse(policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[EVENT_ADDR] || !tb[EVENT_FREQ])
return UBUS_STATUS_INVALID_ARGUMENT;
if (tb[EVENT_SIGNAL])
signal = (int32_t) blobmsg_get_u32(tb[EVENT_SIGNAL]);
if (tb[EVENT_FREQ])
freq = blobmsg_get_u32(tb[EVENT_FREQ]);
addr_str = blobmsg_data(tb[EVENT_ADDR]);
addr = (uint8_t *) ether_aton(addr_str);
if (!addr)
return UBUS_STATUS_INVALID_ARGUMENT;
ret = usteer_handle_sta_event(node, addr, ev_type, freq, signal);
MSG(DEBUG, "received %s event from %s, signal=%d, freq=%d, handled:%s\n",
method, addr_str, signal, freq, ret ? "true" : "false");
return ret ? 0 : 17 /* WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA */;
}
static void
usteer_local_node_assoc_update(struct sta_info *si, struct blob_attr *data)
{
enum {
MSG_ASSOC,
__MSG_MAX,
};
static struct blobmsg_policy policy[__MSG_MAX] = {
[MSG_ASSOC] = { "assoc", BLOBMSG_TYPE_BOOL },
};
struct blob_attr *tb[__MSG_MAX];
blobmsg_parse(policy, __MSG_MAX, tb, blobmsg_data(data), blobmsg_data_len(data));
if (tb[MSG_ASSOC] && blobmsg_get_u8(tb[MSG_ASSOC]))
si->connected = 1;
if (si->node->freq < 4000)
si->sta->seen_2ghz = 1;
else
si->sta->seen_5ghz = 1;
}
static void
usteer_local_node_set_assoc(struct usteer_local_node *ln, struct blob_attr *cl)
{
struct usteer_node *node = &ln->node;
struct usteer_node_handler *h;
struct blob_attr *cur;
struct sta_info *si;
struct sta *sta;
int n_assoc = 0;
int rem;
list_for_each_entry(si, &node->sta_info, node_list) {
if (si->connected)
si->connected = 2;
}
blobmsg_for_each_attr(cur, cl, rem) {
uint8_t *addr = (uint8_t *) ether_aton(blobmsg_name(cur));
bool create;
if (!addr)
continue;
sta = usteer_sta_get(addr, true);
si = usteer_sta_info_get(sta, node, &create);
list_for_each_entry(h, &node_handlers, list) {
if (!h->update_sta)
continue;
h->update_sta(node, si);
}
usteer_local_node_assoc_update(si, cur);
if (si->connected == 1)
n_assoc++;
}
node->n_assoc = n_assoc;
list_for_each_entry(si, &node->sta_info, node_list) {
if (si->connected != 2)
continue;
si->connected = 0;
usteer_sta_info_update_timeout(si, config.local_sta_timeout);
MSG(VERBOSE, "station "MAC_ADDR_FMT" disconnected from node %s\n",
MAC_ADDR_DATA(si->sta->addr), usteer_node_name(node));
}
}
static void
usteer_local_node_list_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
enum {
MSG_FREQ,
MSG_CLIENTS,
__MSG_MAX,
};
static struct blobmsg_policy policy[__MSG_MAX] = {
[MSG_FREQ] = { "freq", BLOBMSG_TYPE_INT32 },
[MSG_CLIENTS] = { "clients", BLOBMSG_TYPE_TABLE },
};
struct blob_attr *tb[__MSG_MAX];
struct usteer_local_node *ln;
struct usteer_node *node;
ln = container_of(req, struct usteer_local_node, req);
node = &ln->node;
blobmsg_parse(policy, __MSG_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[MSG_FREQ] || !tb[MSG_CLIENTS])
return;
node->freq = blobmsg_get_u32(tb[MSG_FREQ]);
usteer_local_node_set_assoc(ln, tb[MSG_CLIENTS]);
}
static void
usteer_local_node_rrm_nr_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
static const struct blobmsg_policy policy = {
"value", BLOBMSG_TYPE_ARRAY
};
struct usteer_local_node *ln;
struct blob_attr *tb;
ln = container_of(req, struct usteer_local_node, req);
blobmsg_parse(&policy, 1, &tb, blob_data(msg), blob_len(msg));
if (!tb)
return;
usteer_node_set_blob(&ln->node.rrm_nr, tb);
}
static void
usteer_local_node_req_cb(struct ubus_request *req, int ret)
{
struct usteer_local_node *ln;
ln = container_of(req, struct usteer_local_node, req);
uloop_timeout_set(&ln->req_timer, 1);
}
static void
usteer_add_rrm_data(struct usteer_local_node *ln, struct usteer_node *node)
{
if (node == &ln->node)
return;
if (!node->rrm_nr)
return;
if (strcmp(ln->node.ssid, node->ssid) != 0)
return;
blobmsg_add_field(&b, BLOBMSG_TYPE_ARRAY, "",
blobmsg_data(node->rrm_nr),
blobmsg_data_len(node->rrm_nr));
}
static void
usteer_local_node_prepare_rrm_set(struct usteer_local_node *ln)
{
struct usteer_remote_node *rn;
struct usteer_node *node;
void *c;
c = blobmsg_open_array(&b, "list");
avl_for_each_element(&local_nodes, node, avl)
usteer_add_rrm_data(ln, node);
avl_for_each_element(&remote_nodes, rn, avl)
usteer_add_rrm_data(ln, &rn->node);
blobmsg_close_array(&b, c);
}
static void
usteer_local_node_state_next(struct uloop_timeout *timeout)
{
struct usteer_local_node *ln;
ln = container_of(timeout, struct usteer_local_node, req_timer);
ln->req_state++;
if (ln->req_state >= __REQ_MAX) {
ln->req_state = REQ_IDLE;
return;
}
blob_buf_init(&b, 0);
switch (ln->req_state) {
case REQ_CLIENTS:
ubus_invoke_async(ubus_ctx, ln->obj_id, "get_clients", b.head, &ln->req);
ln->req.data_cb = usteer_local_node_list_cb;
break;
case REQ_RRM_SET_LIST:
usteer_local_node_prepare_rrm_set(ln);
ubus_invoke_async(ubus_ctx, ln->obj_id, "rrm_nr_set", b.head, &ln->req);
ln->req.data_cb = NULL;
break;
case REQ_RRM_GET_OWN:
ubus_invoke_async(ubus_ctx, ln->obj_id, "rrm_nr_get_own", b.head, &ln->req);
ln->req.data_cb = usteer_local_node_rrm_nr_cb;
break;
default:
break;
}
ln->req.complete_cb = usteer_local_node_req_cb;
ubus_complete_request_async(ubus_ctx, &ln->req);
}
static void
usteer_local_node_update(struct uloop_timeout *timeout)
{
struct usteer_local_node *ln;
struct usteer_node_handler *h;
struct usteer_node *node;
ln = container_of(timeout, struct usteer_local_node, update);
node = &ln->node;
MSG_T("local_sta_udpate", "timeout (%u) expired\n",
config.local_sta_update);
list_for_each_entry(h, &node_handlers, list) {
if (!h->update_node)
continue;
h->update_node(node);
}
usteer_local_node_state_reset(ln);
uloop_timeout_set(&ln->req_timer, 1);
usteer_local_node_kick(ln);
uloop_timeout_set(timeout, config.local_sta_update);
}
static struct usteer_local_node *
usteer_get_node(struct ubus_context *ctx, const char *name)
{
struct usteer_local_node *ln;
struct usteer_node *node;
char *str;
ln = avl_find_element(&local_nodes, name, ln, node.avl);
if (ln)
return ln;
ln = calloc_a(sizeof(*ln), &str, strlen(name) + 1);
node = &ln->node;
node->type = NODE_TYPE_LOCAL;
node->avl.key = strcpy(str, name);
ln->ev.remove_cb = usteer_handle_remove;
ln->ev.cb = usteer_handle_event;
ln->update.cb = usteer_local_node_update;
ln->req_timer.cb = usteer_local_node_state_next;
ubus_register_subscriber(ctx, &ln->ev);
avl_insert(&local_nodes, &node->avl);
uloop_timeout_set(&ln->update, 1);
INIT_LIST_HEAD(&node->sta_info);
return ln;
}
static void
usteer_node_run_update_script(struct usteer_node *node)
{
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
char *val;
if (!node_up_script)
return;
val = alloca(strlen(node_up_script) + strlen(ln->iface) + 8);
sprintf(val, "%s '%s'", node_up_script, ln->iface);
if (system(val))
fprintf(stderr, "failed to execute %s\n", val);
}
static void
usteer_register_node(struct ubus_context *ctx, const char *name, uint32_t id)
{
struct usteer_local_node *ln;
struct usteer_node_handler *h;
const char *iface;
int offset = sizeof("hostapd.") - 1;
iface = name + offset;
if (strncmp(name, "hostapd.", iface - name) != 0)
return;
MSG(INFO, "Connecting to local node %s\n", name);
ln = usteer_get_node(ctx, name);
ln->obj_id = id;
ln->iface = usteer_node_name(&ln->node) + offset;
ln->ifindex = if_nametoindex(iface);
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "notify_response", 1);
ubus_invoke(ctx, id, "notify_response", b.head, NULL, NULL, 1000);
blob_buf_init(&b, 0);
blobmsg_add_u8(&b, "neighbor_report", 1);
blobmsg_add_u8(&b, "beacon_report", 1);
blobmsg_add_u8(&b, "bss_transition", 1);
ubus_invoke(ctx, id, "bss_mgmt_enable", b.head, NULL, NULL, 1000);
ubus_subscribe(ctx, &ln->ev, id);
list_for_each_entry(h, &node_handlers, list) {
if (!h->init_node)
continue;
h->init_node(&ln->node);
}
usteer_node_run_update_script(&ln->node);
}
static void
usteer_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *type, struct blob_attr *msg)
{
static const struct blobmsg_policy policy[2] = {
{ .name = "id", .type = BLOBMSG_TYPE_INT32 },
{ .name = "path", .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[2];
const char *path;
blobmsg_parse(policy, 2, tb, blob_data(msg), blob_len(msg));
if (!tb[0] || !tb[1])
return;
path = blobmsg_data(tb[1]);
usteer_register_node(ctx, path, blobmsg_get_u32(tb[0]));
}
static void
usteer_register_events(struct ubus_context *ctx)
{
static struct ubus_event_handler handler = {
.cb = usteer_event_handler
};
ubus_register_event_handler(ctx, &handler, "ubus.object.add");
}
static void
node_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv)
{
usteer_register_node(ctx, obj->path, obj->id);
}
void config_set_node_up_script(struct blob_attr *data)
{
const char *val = blobmsg_get_string(data);
struct usteer_node *node;
if (node_up_script && !strcmp(val, node_up_script))
return;
free(node_up_script);
if (!strlen(val)) {
node_up_script = NULL;
return;
}
node_up_script = strdup(val);
avl_for_each_element(&local_nodes, node, avl)
usteer_node_run_update_script(node);
}
void config_get_node_up_script(struct blob_buf *buf)
{
if (!node_up_script)
return;
blobmsg_add_string(buf, "node_up_script", node_up_script);
}
void
usteer_local_nodes_init(struct ubus_context *ctx)
{
usteer_register_events(ctx);
ubus_lookup(ctx, "hostapd.*", node_list_cb, NULL);
}

163
main.c Normal file
View File

@@ -0,0 +1,163 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>
#include "usteer.h"
struct ubus_context *ubus_ctx;
struct usteer_config config = {};
uint64_t current_time;
LIST_HEAD(node_handlers);
const char * const event_types[__EVENT_TYPE_MAX] = {
[EVENT_TYPE_PROBE] = "probe",
[EVENT_TYPE_AUTH] = "auth",
[EVENT_TYPE_ASSOC] = "assoc",
};
void debug_msg(int level, const char *func, int line, const char *format, ...)
{
va_list ap;
if (config.debug_level < level)
return;
if (!config.syslog)
fprintf(stderr, "[%s:%d] ", func, line);
va_start(ap, format);
if (config.syslog)
vsyslog(level >= MSG_DEBUG ? LOG_DEBUG : LOG_INFO, format, ap);
else
vfprintf(stderr, format, ap);
va_end(ap);
}
void debug_msg_cont(int level, const char *format, ...)
{
va_list ap;
if (config.debug_level < level)
return;
va_start(ap, format);
vfprintf(stderr, format, ap);
va_end(ap);
}
void usteer_init_defaults(void)
{
memset(&config, 0, sizeof(config));
config.sta_block_timeout = 30 * 1000;
config.local_sta_timeout = 120 * 1000;
config.local_sta_update = 1 * 1000;
config.max_retry_band = 5;
config.seen_policy_timeout = 30 * 1000;
config.band_steering_threshold = 5;
config.load_balancing_threshold = 5;
config.remote_update_interval = 1000;
config.initial_connect_delay = 0;
config.remote_node_timeout = 120 * 1000;
config.roam_kick_delay = 100;
config.roam_scan_tries = 3;
config.roam_scan_interval = 10 * 1000;
config.roam_trigger_interval = 60 * 1000;
config.load_kick_enabled = false;
config.load_kick_threshold = 75;
config.load_kick_delay = 10 * 1000;
config.load_kick_min_clients = 10;
config.load_kick_reason_code = 5; /* WLAN_REASON_DISASSOC_AP_BUSY */
config.debug_level = MSG_FATAL;
}
void usteer_update_time(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
current_time = (uint64_t) ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int usage(const char *prog)
{
fprintf(stderr, "Usage: %s [options]\n"
"Options:\n"
" -v: Increase debug level (repeat for more messages):\n"
" 1: info messages\n"
" 2: debug messages\n"
" 3: verbose debug messages\n"
" 4: include network messages\n"
" 5: include extra testing messages\n"
" -i <name>: Connect to other instances on interface <name>\n"
" -s: Output log messages via syslog instead of stderr\n"
"\n", prog);
return 1;
}
int main(int argc, char **argv)
{
int ch;
usteer_init_defaults();
while ((ch = getopt(argc, argv, "i:sv")) != -1) {
switch(ch) {
case 'v':
config.debug_level++;
break;
case 's':
config.syslog = true;
break;
case 'i':
usteer_interface_add(optarg);
break;
default:
return usage(argv[0]);
}
}
openlog("usteer", 0, LOG_USER);
usteer_update_time();
uloop_init();
ubus_ctx = ubus_connect(NULL);
if (!ubus_ctx) {
fprintf(stderr, "Failed to connect to ubus\n");
return -1;
}
ubus_add_uloop(ubus_ctx);
usteer_ubus_init(ubus_ctx);
usteer_interface_init();
usteer_local_nodes_init(ubus_ctx);
uloop_run();
uloop_done();
return 0;
}

207
monitor.c Normal file
View File

@@ -0,0 +1,207 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <pcap/pcap.h>
#include <libubox/blobmsg_json.h>
#include "usteer.h"
#include "remote.h"
static pcap_t *pcap;
static int pkt_offset;
/* IP header */
struct ip_header {
uint8_t ip_vhl; /* version << 4 | header length >> 2 */
uint8_t ip_tos; /* type of service */
uint16_t ip_len; /* total length */
uint16_t ip_id; /* identification */
uint16_t ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
uint8_t ip_ttl; /* time to live */
uint8_t ip_p; /* protocol */
uint16_t ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)
struct udp_header {
uint16_t uh_sport; /* source port */
uint16_t uh_dport; /* destination port */
uint16_t uh_ulen; /* udp length */
uint16_t uh_sum; /* udp checksum */
};
static void
decode_sta(struct blob_attr *data)
{
struct apmsg_sta msg;
if (!parse_apmsg_sta(&msg, data))
return;
fprintf(stderr, "\t\tSta "MAC_ADDR_FMT" signal=%d connected=%d timeout=%d\n",
MAC_ADDR_DATA(msg.addr), msg.signal, msg.connected, msg.timeout);
}
static void
decode_node(struct blob_attr *data)
{
struct apmsg_node msg;
struct blob_attr *cur;
int rem;
if (!parse_apmsg_node(&msg, data))
return;
fprintf(stderr, "\tNode %s, freq=%d, n_assoc=%d, noise=%d load=%d max_assoc=%d\n",
msg.name, msg.freq, msg.n_assoc, msg.noise, msg.load, msg.max_assoc);
if (msg.rrm_nr) {
fprintf(stderr, "\t\tRRM:");
blobmsg_for_each_attr(cur, msg.rrm_nr, rem) {
if (!blobmsg_check_attr(cur, false))
continue;
if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
continue;
fprintf(stderr, " %s", blobmsg_get_string(cur));
}
fprintf(stderr, "\n");
}
if (msg.script_data) {
char *data = blobmsg_format_json(msg.script_data, true);
fprintf(stderr, "\t\tScript data: %s\n", data);
free(data);
}
blob_for_each_attr(cur, msg.stations, rem)
decode_sta(cur);
}
static void
decode_packet(struct blob_attr *data)
{
struct apmsg msg;
struct blob_attr *cur;
int rem;
if (!parse_apmsg(&msg, data)) {
fprintf(stderr, "missing fields\n");
return;
}
fprintf(stderr, "id=%08x, seq=%d\n", msg.id, msg.seq);
blob_for_each_attr(cur, msg.nodes, rem)
decode_node(cur);
}
static void
recv_packet(unsigned char *user, const struct pcap_pkthdr *hdr,
const unsigned char *packet)
{
char addr[INET_ADDRSTRLEN];
struct ip_header *ip;
struct udp_header *uh;
struct blob_attr *data;
int len = hdr->caplen;
int hdrlen;
len -= pkt_offset;
packet += pkt_offset;
ip = (void *) packet;
hdrlen = IP_HL(ip) * 4;
if (hdrlen < 20 || hdrlen >= len)
return;
len -= hdrlen;
packet += hdrlen;
inet_ntop(AF_INET, &ip->ip_src, addr, sizeof(addr));
hdrlen = sizeof(*uh);
if (len <= hdrlen)
return;
uh = (void *) packet;
packet += hdrlen;
len -= hdrlen;
if (uh->uh_dport != htons(APMGR_PORT))
return;
data = (void *) packet;
fprintf(stderr, "[%s]: len=%d ", addr, len);
if (len != blob_pad_len(data)) {
fprintf(stderr, "invalid data\n");
return;
}
decode_packet(data);
}
int main(int argc, char **argv)
{
static char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
if (argc != 2) {
fprintf(stderr, "Usage: %s <interface>\n", argv[0]);
return 1;
}
pcap = pcap_open_live(argv[1], APMGR_BUFLEN, 1, 1000, errbuf);
if (!pcap) {
fprintf(stderr, "Failed to open interface %s: %s\n", argv[1], errbuf);
return 1;
}
pcap_compile(pcap, &fp, "port "APMGR_PORT_STR, 1, PCAP_NETMASK_UNKNOWN);
pcap_setfilter(pcap, &fp);
switch (pcap_datalink(pcap)) {
case DLT_EN10MB:
pkt_offset = 14;
break;
case DLT_RAW:
pkt_offset = 0;
break;
default:
fprintf(stderr, "Invalid link type\n");
return -1;
}
pcap_loop(pcap, 0, recv_packet, NULL);
pcap_close(pcap);
return 0;
}

154
netifd.c Normal file
View File

@@ -0,0 +1,154 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include "usteer.h"
#include "node.h"
static struct blob_buf b;
static void
netifd_parse_interface_config(struct usteer_local_node *ln, struct blob_attr *msg)
{
static const struct blobmsg_policy policy = {
.name = "maxassoc",
.type = BLOBMSG_TYPE_INT32,
};
struct blob_attr *cur;
int val = 0;
blobmsg_parse(&policy, 1, &cur, blobmsg_data(msg), blobmsg_data_len(msg));
if (cur)
val = blobmsg_get_u32(cur);
ln->node.max_assoc = val;
ln->netifd.status_complete = true;
}
static void
netifd_parse_interface(struct usteer_local_node *ln, struct blob_attr *msg)
{
enum {
N_IF_CONFIG,
N_IF_NAME,
__N_IF_MAX
};
static const struct blobmsg_policy policy[__N_IF_MAX] = {
[N_IF_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_TABLE },
[N_IF_NAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[__N_IF_MAX];
if (blobmsg_type(msg) != BLOBMSG_TYPE_TABLE)
return;
blobmsg_parse(policy, __N_IF_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
if (!tb[N_IF_CONFIG] || !tb[N_IF_NAME])
return;
if (strcmp(blobmsg_get_string(tb[N_IF_NAME]), ln->iface) != 0)
return;
netifd_parse_interface_config(ln, tb[N_IF_CONFIG]);
}
static void
netifd_parse_radio(struct usteer_local_node *ln, struct blob_attr *msg)
{
static const struct blobmsg_policy policy = {
.name = "interfaces",
.type = BLOBMSG_TYPE_ARRAY,
};
struct blob_attr *cur, *iface;
int rem;
if (blobmsg_type(msg) != BLOBMSG_TYPE_TABLE)
return;
blobmsg_parse(&policy, 1, &iface, blobmsg_data(msg), blobmsg_data_len(msg));
if (!iface)
return;
blobmsg_for_each_attr(cur, iface, rem)
netifd_parse_interface(ln, cur);
}
static void
netifd_status_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
struct usteer_local_node *ln;
struct blob_attr *cur;
int rem;
ln = container_of(req, struct usteer_local_node, netifd.req);
ln->netifd.req_pending = false;
blobmsg_for_each_attr(cur, msg, rem)
netifd_parse_radio(ln, cur);
}
static void netifd_update_node(struct usteer_node *node)
{
struct usteer_local_node *ln;
uint32_t id;
ln = container_of(node, struct usteer_local_node, node);
if (ln->netifd.status_complete)
return;
if (ln->netifd.req_pending)
ubus_abort_request(ubus_ctx, &ln->netifd.req);
if (ubus_lookup_id(ubus_ctx, "network.wireless", &id))
return;
blob_buf_init(&b, 0);
ubus_invoke_async(ubus_ctx, id, "status", b.head, &ln->netifd.req);
ln->netifd.req.data_cb = netifd_status_cb;
ubus_complete_request_async(ubus_ctx, &ln->netifd.req);
ln->netifd.req_pending = true;
}
static void netifd_init_node(struct usteer_node *node)
{
struct usteer_local_node *ln;
ln = container_of(node, struct usteer_local_node, node);
ln->netifd.status_complete = false;
netifd_update_node(node);
}
static void netifd_free_node(struct usteer_node *node)
{
struct usteer_local_node *ln;
ln = container_of(node, struct usteer_local_node, node);
if (ln->netifd.req_pending)
ubus_abort_request(ubus_ctx, &ln->netifd.req);
}
static struct usteer_node_handler netifd_handler = {
.init_node = netifd_init_node,
.update_node = netifd_update_node,
.free_node = netifd_free_node,
};
static void __usteer_init usteer_netifd_init(void)
{
list_add(&netifd_handler.list, &node_handlers);
}

510
nl80211.c Normal file
View File

@@ -0,0 +1,510 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#define _GNU_SOURCE
#include <linux/if_ether.h>
#include <net/if.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/nl80211.h>
#include <unl.h>
#include "usteer.h"
#include "node.h"
static struct unl unl;
static struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nl80211_survey_req {
void (*cb)(void *priv, struct usteer_survey_data *d);
void *priv;
};
struct nl80211_scan_req {
void (*cb)(void *priv, struct usteer_scan_result *r);
void *priv;
};
struct nl80211_freqlist_req {
void (*cb)(void *priv, struct usteer_freq_data *f);
void *priv;
};
static int nl80211_survey_result(struct nl_msg *msg, void *arg)
{
static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = {
[NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 },
[NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 },
[NL80211_SURVEY_INFO_CHANNEL_TIME] = { .type = NLA_U64 },
[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY] = { .type = NLA_U64 },
};
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *tb_s[NL80211_SURVEY_INFO_MAX + 1];
struct nl80211_survey_req *req = arg;
struct usteer_survey_data data = {};
struct genlmsghdr *gnlh;
gnlh = nlmsg_data(nlmsg_hdr(msg));
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_SURVEY_INFO])
return NL_SKIP;
if (nla_parse_nested(tb_s, NL80211_SURVEY_INFO_MAX,
tb[NL80211_ATTR_SURVEY_INFO], survey_policy))
return NL_SKIP;
if (!tb_s[NL80211_SURVEY_INFO_FREQUENCY])
return NL_SKIP;
data.freq = nla_get_u32(tb_s[NL80211_SURVEY_INFO_FREQUENCY]);
if (tb_s[NL80211_SURVEY_INFO_NOISE])
data.noise = (int8_t) nla_get_u8(tb_s[NL80211_SURVEY_INFO_NOISE]);
if (tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME] &&
tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]) {
data.time = nla_get_u64(tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME]);
data.time_busy = nla_get_u64(tb_s[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]);
}
req->cb(req->priv, &data);
return NL_SKIP;
}
static void nl80211_get_survey(struct usteer_node *node, void *priv,
void (*cb)(void *priv, struct usteer_survey_data *d))
{
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
struct nl80211_survey_req req = {
.priv = priv,
.cb = cb,
};
struct nl_msg *msg;
if (!ln->nl80211.present)
return;
msg = unl_genl_msg(&unl, NL80211_CMD_GET_SURVEY, true);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex);
unl_genl_request(&unl, msg, nl80211_survey_result, &req);
nla_put_failure:
return;
}
static void nl80211_update_node_result(void *priv, struct usteer_survey_data *d)
{
struct usteer_local_node *ln = priv;
uint32_t delta = 0, delta_busy = 0;
if (d->freq != ln->node.freq)
return;
if (d->noise)
ln->node.noise = d->noise;
if (ln->time) {
delta = d->time - ln->time;
delta_busy = d->time_busy - ln->time_busy;
}
ln->time = d->time;
ln->time_busy = d->time_busy;
if (delta) {
float cur = (100 * delta_busy) / delta;
if (ln->load_ewma < 0)
ln->load_ewma = cur;
else
ln->load_ewma = 0.85 * ln->load_ewma + 0.15 * cur;
ln->node.load = ln->load_ewma;
}
}
static void nl80211_update_node(struct uloop_timeout *t)
{
struct usteer_local_node *ln = container_of(t, struct usteer_local_node, nl80211.update);
uloop_timeout_set(t, 1000);
ln->ifindex = if_nametoindex(ln->iface);
nl80211_get_survey(&ln->node, ln, nl80211_update_node_result);
}
static void nl80211_init_node(struct usteer_node *node)
{
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
struct genlmsghdr *gnlh;
static bool _init = false;
struct nl_msg *msg;
if (node->type != NODE_TYPE_LOCAL)
return;
ln->nl80211.present = false;
ln->wiphy = -1;
if (!ln->ifindex) {
MSG(INFO, "No ifindex found for node %s\n", usteer_node_name(node));
return;
}
if (!_init) {
if (unl_genl_init(&unl, "nl80211") < 0) {
unl_free(&unl);
MSG(INFO, "nl80211 init failed\n");
return;
}
_init = true;
}
msg = unl_genl_msg(&unl, NL80211_CMD_GET_INTERFACE, false);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex);
unl_genl_request_single(&unl, msg, &msg);
if (!msg)
return;
gnlh = nlmsg_data(nlmsg_hdr(msg));
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_WIPHY])
goto nla_put_failure;
ln->wiphy = nla_get_u32(tb[NL80211_ATTR_WIPHY]);
if (tb[NL80211_ATTR_SSID]) {
int len = nla_len(tb[NL80211_ATTR_SSID]);
if (len >= sizeof(node->ssid))
len = sizeof(node->ssid) - 1;
memcpy(node->ssid, nla_data(tb[NL80211_ATTR_SSID]), len);
node->ssid[len] = 0;
}
MSG(INFO, "Found nl80211 phy on wdev %s, ssid=%s\n", usteer_node_name(node), node->ssid);
ln->load_ewma = -1;
ln->nl80211.present = true;
ln->nl80211.update.cb = nl80211_update_node;
nl80211_update_node(&ln->nl80211.update);
nla_put_failure:
nlmsg_free(msg);
return;
}
static void nl80211_free_node(struct usteer_node *node)
{
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
if (!ln->nl80211.present)
return;
uloop_timeout_cancel(&ln->nl80211.update);
}
static void nl80211_update_sta(struct usteer_node *node, struct sta_info *si)
{
struct nlattr *tb_sta[NL80211_STA_INFO_MAX + 1];
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
struct genlmsghdr *gnlh;
struct nl_msg *msg;
int signal = NO_SIGNAL;
if (!ln->nl80211.present)
return;
msg = unl_genl_msg(&unl, NL80211_CMD_GET_STATION, false);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex);
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, si->sta->addr);
unl_genl_request_single(&unl, msg, &msg);
if (!msg)
return;
gnlh = nlmsg_data(nlmsg_hdr(msg));
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_STA_INFO])
goto nla_put_failure;
if (nla_parse_nested(tb_sta, NL80211_STA_INFO_MAX,
tb[NL80211_ATTR_STA_INFO], NULL))
goto nla_put_failure;
if (tb_sta[NL80211_STA_INFO_SIGNAL_AVG])
signal = (int8_t) nla_get_u8(tb_sta[NL80211_STA_INFO_SIGNAL_AVG]);
usteer_sta_info_update(si, signal, true);
nla_put_failure:
nlmsg_free(msg);
return;
}
static int nl80211_scan_result(struct nl_msg *msg, void *arg)
{
static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
[NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
[NL80211_BSS_CAPABILITY] = { .type = NLA_U16 },
[NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 },
};
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *bss[NL80211_BSS_MAX + 1];
struct nl80211_scan_req *req = arg;
struct usteer_scan_result data = {
.signal = -127,
};
struct genlmsghdr *gnlh;
struct nlattr *ie_attr;
int ielen = 0;
uint8_t *ie;
gnlh = nlmsg_data(nlmsg_hdr(msg));
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_BSS])
return NL_SKIP;
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS],
bss_policy))
return NL_SKIP;
if (!bss[NL80211_BSS_BSSID] ||
!bss[NL80211_BSS_FREQUENCY])
return NL_SKIP;
data.freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
memcpy(data.bssid, nla_data(bss[NL80211_BSS_BSSID]), sizeof(data.bssid));
if (bss[NL80211_BSS_SIGNAL_MBM]) {
int32_t signal = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
data.signal = signal / 100;
}
ie_attr = bss[NL80211_BSS_INFORMATION_ELEMENTS];
if (!ie_attr)
ie_attr = bss[NL80211_BSS_BEACON_IES];
if (!ie_attr)
goto skip_ie;
ie = (uint8_t *) nla_data(ie_attr);
ielen = nla_len(ie_attr);
for (; ielen >= 2 && ielen >= ie[1];
ielen -= ie[1] + 2, ie += ie[1] + 2) {
if (ie[0] == 0) { /* SSID */
if (ie[1] > 32)
continue;
memcpy(data.ssid, ie + 2, ie[1]);
}
}
skip_ie:
req->cb(req->priv, &data);
return NL_SKIP;
}
static int nl80211_scan_event_cb(struct nl_msg *msg, void *data)
{
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
switch (gnlh->cmd) {
case NL80211_CMD_NEW_SCAN_RESULTS:
case NL80211_CMD_SCAN_ABORTED:
unl_loop_done(&unl);
break;
}
return NL_SKIP;
}
static int nl80211_scan(struct usteer_node *node, struct usteer_scan_request *req,
void *priv, void (*cb)(void *priv, struct usteer_scan_result *r))
{
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
struct nl80211_scan_req reqdata = {
.priv = priv,
.cb = cb,
};
struct nl_msg *msg;
struct nlattr *cur;
int i, ret;
if (!ln->nl80211.present)
return -ENODEV;
msg = unl_genl_msg(&unl, NL80211_CMD_TRIGGER_SCAN, false);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex);
if (!req->passive) {
cur = nla_nest_start(msg, NL80211_ATTR_SCAN_SSIDS);
NLA_PUT(msg, 1, 0, "");
nla_nest_end(msg, cur);
}
NLA_PUT_U32(msg, NL80211_ATTR_SCAN_FLAGS, NL80211_SCAN_FLAG_AP);
if (req->n_freq) {
cur = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES);
for (i = 0; i < req->n_freq; i++)
NLA_PUT_U32(msg, i, req->freq[i]);
nla_nest_end(msg, cur);
}
unl_genl_subscribe(&unl, "scan");
ret = unl_genl_request(&unl, msg, NULL, NULL);
if (ret < 0)
goto done;
unl_genl_loop(&unl, nl80211_scan_event_cb, NULL);
done:
unl_genl_unsubscribe(&unl, "scan");
if (ret < 0)
return ret;
if (!cb)
return 0;
msg = unl_genl_msg(&unl, NL80211_CMD_GET_SCAN, true);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ln->ifindex);
unl_genl_request(&unl, msg, nl80211_scan_result, &reqdata);
return 0;
nla_put_failure:
nlmsg_free(msg);
return -ENOMEM;
}
static int nl80211_wiphy_result(struct nl_msg *msg, void *arg)
{
struct nl80211_freqlist_req *req = arg;
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1];
struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
struct nlattr *nl_band;
struct nlattr *nl_freq;
struct nlattr *cur;
struct genlmsghdr *gnlh;
int rem_band;
int rem_freq;
gnlh = nlmsg_data(nlmsg_hdr(msg));
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_WIPHY_BANDS])
return NL_SKIP;
nla_for_each_nested(nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
nla_parse(tb_band, NL80211_BAND_ATTR_MAX, nla_data(nl_band),
nla_len(nl_band), NULL);
if (!tb_band[NL80211_BAND_ATTR_FREQS])
continue;
nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
rem_freq) {
struct usteer_freq_data f = {};
nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX,
nla_data(nl_freq), nla_len(nl_freq), NULL);
if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED])
continue;
if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IR])
continue;
cur = tb_freq[NL80211_FREQUENCY_ATTR_FREQ];
if (!cur)
continue;
f.freq = nla_get_u32(cur);
f.dfs = !!tb_freq[NL80211_FREQUENCY_ATTR_RADAR];
cur = tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER];
if (cur)
f.txpower = nla_get_u32(cur) / 100;
req->cb(req->priv, &f);
}
}
return NL_SKIP;
}
static void nl80211_get_freqlist(struct usteer_node *node, void *priv,
void (*cb)(void *priv, struct usteer_freq_data *f))
{
struct usteer_local_node *ln = container_of(node, struct usteer_local_node, node);
struct nl80211_freqlist_req req = {
.priv = priv,
.cb = cb
};
struct nl_msg *msg;
if (!ln->nl80211.present)
return;
msg = unl_genl_msg(&unl, NL80211_CMD_GET_WIPHY, false);
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, ln->wiphy);
NLA_PUT_FLAG(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP);
unl_genl_request(&unl, msg, nl80211_wiphy_result, &req);
return;
nla_put_failure:
nlmsg_free(msg);
}
static struct usteer_node_handler nl80211_handler = {
.init_node = nl80211_init_node,
.free_node = nl80211_free_node,
.update_sta = nl80211_update_sta,
.get_survey = nl80211_get_survey,
.get_freqlist = nl80211_get_freqlist,
.scan = nl80211_scan,
};
static void __usteer_init usteer_nl80211_init(void)
{
list_add(&nl80211_handler.list, &node_handlers);
}

38
node.c Normal file
View File

@@ -0,0 +1,38 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include "usteer.h"
void usteer_node_set_blob(struct blob_attr **dest, struct blob_attr *val)
{
int new_len;
int len;
if (!val) {
free(*dest);
*dest = NULL;
return;
}
len = *dest ? blob_pad_len(*dest) : 0;
new_len = blob_pad_len(val);
if (new_len != len)
*dest = realloc(*dest, new_len);
memcpy(*dest, val, new_len);
}

79
node.h Normal file
View File

@@ -0,0 +1,79 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#ifndef __APMGR_NODE_H
#define __APMGR_NODE_H
#include "usteer.h"
enum local_req_state {
REQ_IDLE,
REQ_CLIENTS,
REQ_RRM_SET_LIST,
REQ_RRM_GET_OWN,
__REQ_MAX
};
struct usteer_local_node {
struct usteer_node node;
struct ubus_subscriber ev;
struct uloop_timeout update;
const char *iface;
int ifindex;
int wiphy;
struct ubus_request req;
struct uloop_timeout req_timer;
int req_state;
uint32_t obj_id;
float load_ewma;
int load_thr_count;
uint64_t time, time_busy;
struct {
bool present;
struct uloop_timeout update;
} nl80211;
struct {
struct ubus_request req;
bool req_pending;
bool status_complete;
} netifd;
};
struct interface;
struct usteer_remote_node {
struct avl_node avl;
const char *name;
struct usteer_node node;
struct interface *iface;
int check;
};
extern struct avl_tree local_nodes;
extern struct avl_tree remote_nodes;
#endif

37
openwrt/usteer/Makefile Normal file
View File

@@ -0,0 +1,37 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=usteer
PKG_VERSION:=$(shell git show -s --format=%cd --date=short)
PKG_RELEASE:=1
PKG_BUILD_PARALLEL:=1
PKG_FILE_DEPENDS:=$(CURDIR)/../..
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
ln -s $(CURDIR)/../../.git $(PKG_BUILD_DIR)/.git
cd $(PKG_BUILD_DIR) && git checkout .
endef
define Package/usteer
SECTION:=net
CATEGORY:=Network
DEPENDS:=+libubox +libubus +libblobmsg-json +libnl-tiny
TITLE:=OpenWrt AP roaming assist daemon
endef
define Package/usteer/conffiles
/etc/config/usteer
endef
define Package/usteer/install
$(INSTALL_DIR) $(1)/sbin $(1)/etc/init.d $(1)/etc/config
$(CP) ./files/* $(1)/
$(CP) $(PKG_BUILD_DIR)/usteer $(1)/sbin/
endef
$(eval $(call BuildPackage,usteer))

View File

@@ -0,0 +1,4 @@
config usteer
option 'network' 'lan'
option 'syslog' '1'
option 'debug_level' '2'

View File

@@ -0,0 +1,120 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2013 OpenWrt.org
START=50
USE_PROCD=1
NAME=usteer
PROG=/sbin/usteer
. /lib/functions/network.sh
. /usr/share/libubox/jshn.sh
. /lib/functions.sh
load_ifaces() {
local network="$(uci get usteer.@usteer[-1].network)"
for n in $network; do
local device
json_load "$(ifstatus $n)"
json_get_var device l3_device
echo -n "$device "
done
}
uci_option_to_json_bool() {
local cfg="$1"
local option="$2"
local val
config_get_bool val "$cfg" $option
[ -n "$val" ] && json_add_boolean $option $val
}
uci_option_to_json_string() {
local cfg="$1"
local option="$2"
local val
config_get val "$cfg" "$option"
[ -n "$val" ] && json_add_string $option "$val"
}
uci_option_to_json() {
local cfg="$1"
local option="$2"
local val
config_get val "$cfg" $option
[ -n "$val" ] && json_add_int $option $val
}
uci_usteer() {
local cfg="$1"
uci_option_to_json_bool "$cfg" syslog
uci_option_to_json_bool "$cfg" load_kick_enabled
uci_option_to_json_string "$cfg" node_up_script
for opt in \
debug_level \
sta_block_timeout local_sta_timeout local_sta_update \
max_retry_band seen_policy_timeout \
load_balancing_threshold band_steering_threshold \
remote_update_interval \
min_connect_snr min_snr signal_diff_threshold \
initial_connect_delay \
roam_kick_delay roam_scan_tries \
roam_scan_snr roam_scan_interval \
roam_trigger_snr roam_trigger_interval \
load_kick_threshold load_kick_delay load_kick_min_clients \
load_kick_reason_code
do
uci_option_to_json "$cfg" "$opt"
done
}
load_config() {
[ "$ENABLED" -gt 0 ] || return
ubus -t 10 wait_for usteer
json_init
json_add_array interfaces
for i in $(load_ifaces); do
json_add_string "" "$i"
done
json_close_array
config_load usteer
config_foreach uci_usteer usteer
ubus call usteer set_config "$(json_dump)"
}
reload_service() {
start
load_config
}
service_started() {
load_config
}
service_triggers() {
procd_add_reload_trigger usteer
procd_add_raw_trigger "interface.*" 2000 /etc/init.d/usteer reload
}
start_service()
{
local network="$(uci -q get usteer.@usteer[-1].network)"
ENABLED="$(uci -q get usteer.@usteer[-1].enabled)"
ENABLED="${ENABLED:-1}"
[ "$ENABLED" -gt 0 ] || return
procd_open_instance
procd_set_param command "$PROG"
procd_close_instance
}

142
parse.c Normal file
View File

@@ -0,0 +1,142 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include "usteer.h"
#include "remote.h"
bool parse_apmsg(struct apmsg *msg, struct blob_attr *data)
{
static const struct blob_attr_info policy[__APMSG_MAX] = {
[APMSG_ID] = { .type = BLOB_ATTR_INT32 },
[APMSG_SEQ] = { .type = BLOB_ATTR_INT32 },
[APMSG_NODES] = { .type = BLOB_ATTR_NESTED },
};
struct blob_attr *tb[__APMSG_MAX];
blob_parse(data, tb, policy, __APMSG_MAX);
if (!tb[APMSG_ID] || !tb[APMSG_SEQ] || !tb[APMSG_NODES])
return false;
msg->id = blob_get_int32(tb[APMSG_ID]);
msg->seq = blob_get_int32(tb[APMSG_SEQ]);
msg->nodes = tb[APMSG_NODES];
return true;
}
static int
get_int32(struct blob_attr *attr)
{
if (!attr)
return 0;
return blob_get_int32(attr);
}
bool parse_apmsg_node(struct apmsg_node *msg, struct blob_attr *data)
{
static const struct blob_attr_info policy[__APMSG_NODE_MAX] = {
[APMSG_NODE_NAME] = { .type = BLOB_ATTR_STRING },
[APMSG_NODE_FREQ] = { .type = BLOB_ATTR_INT32 },
[APMSG_NODE_N_ASSOC] = { .type = BLOB_ATTR_INT32 },
[APMSG_NODE_MAX_ASSOC] = { .type = BLOB_ATTR_INT32 },
[APMSG_NODE_STATIONS] = { .type = BLOB_ATTR_NESTED },
[APMSG_NODE_NOISE] = { .type = BLOB_ATTR_INT32 },
[APMSG_NODE_LOAD] = { .type = BLOB_ATTR_INT32 },
[APMSG_NODE_RRM_NR] = { .type = BLOB_ATTR_NESTED },
[APMSG_NODE_SCRIPT_DATA] = { .type = BLOB_ATTR_NESTED },
};
struct blob_attr *tb[__APMSG_NODE_MAX];
struct blob_attr *cur;
blob_parse(data, tb, policy, __APMSG_NODE_MAX);
if (!tb[APMSG_NODE_NAME] ||
!tb[APMSG_NODE_FREQ] ||
!tb[APMSG_NODE_N_ASSOC] ||
!tb[APMSG_NODE_STATIONS] ||
!tb[APMSG_NODE_SSID])
return false;
msg->name = blob_data(tb[APMSG_NODE_NAME]);
msg->n_assoc = blob_get_int32(tb[APMSG_NODE_N_ASSOC]);
msg->freq = blob_get_int32(tb[APMSG_NODE_FREQ]);
msg->stations = tb[APMSG_NODE_STATIONS];
msg->ssid = blob_data(tb[APMSG_NODE_SSID]);
msg->noise = get_int32(tb[APMSG_NODE_NOISE]);
msg->load = get_int32(tb[APMSG_NODE_LOAD]);
msg->max_assoc = get_int32(tb[APMSG_NODE_MAX_ASSOC]);
msg->rrm_nr = NULL;
cur = tb[APMSG_NODE_RRM_NR];
if (cur && blob_len(cur) >= sizeof(struct blob_attr) &&
blob_len(cur) >= blob_pad_len(blob_data(cur))) {
int rem;
msg->rrm_nr = blob_data(cur);
blobmsg_for_each_attr(cur, msg->rrm_nr, rem) {
if (blobmsg_check_attr(cur, false))
continue;
if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
continue;
msg->rrm_nr = NULL;
break;
}
if (msg->rrm_nr &&
blobmsg_type(msg->rrm_nr) != BLOBMSG_TYPE_ARRAY)
msg->rrm_nr = NULL;
}
msg->script_data = tb[APMSG_NODE_SCRIPT_DATA];
return true;
}
bool parse_apmsg_sta(struct apmsg_sta *msg, struct blob_attr *data)
{
static const struct blob_attr_info policy[__APMSG_STA_MAX] = {
[APMSG_STA_ADDR] = { .type = BLOB_ATTR_BINARY },
[APMSG_STA_SIGNAL] = { .type = BLOB_ATTR_INT32 },
[APMSG_STA_SEEN] = { .type = BLOB_ATTR_INT32 },
[APMSG_STA_TIMEOUT] = { .type = BLOB_ATTR_INT32 },
[APMSG_STA_CONNECTED] = { .type = BLOB_ATTR_INT8 },
};
struct blob_attr *tb[__APMSG_STA_MAX];
blob_parse(data, tb, policy, __APMSG_STA_MAX);
if (!tb[APMSG_STA_ADDR] ||
!tb[APMSG_STA_SIGNAL] ||
!tb[APMSG_STA_SEEN] ||
!tb[APMSG_STA_TIMEOUT] ||
!tb[APMSG_STA_CONNECTED])
return false;
if (blob_len(tb[APMSG_STA_ADDR]) != sizeof(msg->addr))
return false;
memcpy(msg->addr, blob_data(tb[APMSG_STA_ADDR]), sizeof(msg->addr));
msg->signal = blob_get_int32(tb[APMSG_STA_SIGNAL]);
msg->seen = blob_get_int32(tb[APMSG_STA_SEEN]);
msg->timeout = blob_get_int32(tb[APMSG_STA_TIMEOUT]);
msg->connected = blob_get_int8(tb[APMSG_STA_CONNECTED]);
return true;
}

436
policy.c Normal file
View File

@@ -0,0 +1,436 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include "usteer.h"
#include "node.h"
static bool
below_assoc_threshold(struct sta_info *si_cur, struct sta_info *si_new)
{
int n_assoc_cur = si_cur->node->n_assoc;
int n_assoc_new = si_new->node->n_assoc;
bool ref_5g = si_cur->node->freq > 4000;
bool node_5g = si_new->node->freq > 4000;
if (ref_5g && !node_5g)
n_assoc_new += config.band_steering_threshold;
else if (!ref_5g && node_5g)
n_assoc_cur += config.band_steering_threshold;
n_assoc_new += config.load_balancing_threshold;
if (n_assoc_new > n_assoc_cur) {
MSG_T_STA("band_steering_threshold,load_balancing_threshold",
si_cur->sta->addr, "exeeded (bs=%u, lb=%u)\n",
config.band_steering_threshold,
config.load_balancing_threshold);
}
return n_assoc_new <= n_assoc_cur;
}
static bool
better_signal_strength(struct sta_info *si_cur, struct sta_info *si_new)
{
const bool is_better = si_new->signal - si_cur->signal
> (int) config.signal_diff_threshold;
if (!config.signal_diff_threshold)
return false;
if (is_better) {
MSG_T_STA("signal_diff_threshold", si_cur->sta->addr,
"exceeded (config=%i) (real=%i)\n",
config.signal_diff_threshold,
si_new->signal - si_cur->signal);
}
return is_better;
}
static bool
below_load_threshold(struct sta_info *si)
{
return si->node->n_assoc >= config.load_kick_min_clients &&
si->node->load > config.load_kick_threshold;
}
static bool
has_better_load(struct sta_info *si_cur, struct sta_info *si_new)
{
return !below_load_threshold(si_cur) && below_load_threshold(si_new);
}
static bool
below_max_assoc(struct sta_info *si)
{
struct usteer_node *node = si->node;
return !node->max_assoc || node->n_assoc < node->max_assoc;
}
static bool
is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new)
{
if (!below_max_assoc(si_new))
return false;
return below_assoc_threshold(si_cur, si_new) ||
better_signal_strength(si_cur, si_new) ||
has_better_load(si_cur, si_new);
}
static struct sta_info *
find_better_candidate(struct sta_info *si_ref)
{
struct sta_info *si;
struct sta *sta = si_ref->sta;
list_for_each_entry(si, &sta->nodes, list) {
if (si == si_ref)
continue;
if (current_time - si->seen > config.seen_policy_timeout) {
MSG_T_STA("seen_policy_timeout", si->sta->addr,
"timeout exceeded (%u)\n", config.seen_policy_timeout);
continue;
}
if (strcmp(si->node->ssid, si_ref->node->ssid) != 0)
continue;
if (is_better_candidate(si_ref, si) &&
!is_better_candidate(si, si_ref))
return si;
}
return NULL;
}
static int
snr_to_signal(struct usteer_node *node, int snr)
{
int noise = -95;
if (snr < 0)
return snr;
if (node->noise)
noise = node->noise;
return noise + snr;
}
bool
usteer_check_request(struct sta_info *si, enum usteer_event_type type)
{
struct sta_info *si_new;
int min_signal;
if (type == EVENT_TYPE_ASSOC)
return true;
if (si->stats[type].blocked_cur >= config.max_retry_band) {
MSG_T_STA("max_retry_band", si->sta->addr,
"max retry (%u) exceeded\n", config.max_retry_band);
return true;
}
min_signal = snr_to_signal(si->node, config.min_connect_snr);
if (si->signal < min_signal) {
if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT" due to low signal (%d < %d)\n",
event_types[type], MAC_ADDR_DATA(si->sta->addr),
si->signal, min_signal);
MSG_T_STA("min_connect_snr", si->sta->addr,
"snr to low (config=%i) (real=%i)\n",
min_signal, si->signal);
return false;
}
if (current_time - si->created < config.initial_connect_delay) {
if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT" during initial connect delay\n",
event_types[type], MAC_ADDR_DATA(si->sta->addr));
MSG_T_STA("initial_connect_delay", si->sta->addr,
"is below delay (%u)\n", config.initial_connect_delay);
return false;
}
si_new = find_better_candidate(si);
if (!si_new)
return true;
if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT", "
"node (local/remote): %s/%s, "
"signal=%d/%d, n_assoc=%d/%d\n", event_types[type],
MAC_ADDR_DATA(si->sta->addr),
usteer_node_name(si->node), usteer_node_name(si_new->node),
si->signal, si_new->signal,
si->node->n_assoc, si_new->node->n_assoc);
return false;
}
static bool
is_more_kickable(struct sta_info *si_cur, struct sta_info *si_new)
{
if (!si_cur)
return true;
if (si_new->kick_count > si_cur->kick_count)
return false;
return si_cur->signal > si_new->signal;
}
static void
usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state)
{
static const char * const state_names[] = {
#define _S(n) [ROAM_TRIGGER_##n] = #n,
__roam_trigger_states
#undef _S
};
si->roam_event = current_time;
if (si->roam_state == state) {
if (si->roam_state == ROAM_TRIGGER_IDLE) {
si->roam_tries = 0;
return;
}
si->roam_tries++;
} else {
si->roam_tries = 0;
}
si->roam_state = state;
MSG(VERBOSE, "Roam trigger SM for client "MAC_ADDR_FMT": state=%s, tries=%d, signal=%d\n",
MAC_ADDR_DATA(si->sta->addr), state_names[state], si->roam_tries, si->signal);
}
static bool
usteer_roam_trigger_sm(struct sta_info *si)
{
struct sta_info *si_new;
int min_signal;
min_signal = snr_to_signal(si->node, config.roam_trigger_snr);
switch (si->roam_state) {
case ROAM_TRIGGER_SCAN:
if (current_time - si->roam_event < config.roam_scan_interval)
break;
if (find_better_candidate(si) ||
si->roam_scan_done > si->roam_event) {
usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE);
break;
}
if (config.roam_scan_tries &&
si->roam_tries >= config.roam_scan_tries) {
usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK);
break;
}
usteer_ubus_trigger_client_scan(si);
usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
break;
case ROAM_TRIGGER_IDLE:
if (find_better_candidate(si)) {
usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE);
break;
}
usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
break;
case ROAM_TRIGGER_SCAN_DONE:
/* Check for stale scan results, kick back to SCAN state if necessary */
if (current_time - si->roam_scan_done > 2 * config.roam_scan_interval) {
usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
break;
}
si_new = find_better_candidate(si);
if (!si_new)
break;
usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK);
break;
case ROAM_TRIGGER_WAIT_KICK:
if (si->signal > min_signal)
break;
usteer_roam_set_state(si, ROAM_TRIGGER_NOTIFY_KICK);
usteer_ubus_notify_client_disassoc(si);
break;
case ROAM_TRIGGER_NOTIFY_KICK:
if (current_time - si->roam_event < config.roam_kick_delay * 100)
break;
usteer_roam_set_state(si, ROAM_TRIGGER_KICK);
break;
case ROAM_TRIGGER_KICK:
usteer_ubus_kick_client(si);
usteer_roam_set_state(si, ROAM_TRIGGER_IDLE);
return true;
}
return false;
}
static void
usteer_local_node_roam_check(struct usteer_local_node *ln)
{
struct sta_info *si;
int min_signal;
if (config.roam_scan_snr)
min_signal = config.roam_scan_snr;
else if (config.roam_trigger_snr)
min_signal = config.roam_trigger_snr;
else
return;
usteer_update_time();
min_signal = snr_to_signal(&ln->node, min_signal);
list_for_each_entry(si, &ln->node.sta_info, node_list) {
if (!si->connected || si->signal >= min_signal ||
current_time - si->roam_kick < config.roam_trigger_interval) {
usteer_roam_set_state(si, ROAM_TRIGGER_IDLE);
continue;
}
/*
* If the state machine kicked a client, other clients should wait
* until the next turn
*/
if (usteer_roam_trigger_sm(si))
return;
}
}
static void
usteer_local_node_snr_kick(struct usteer_local_node *ln)
{
struct sta_info *si;
int min_signal;
if (!config.min_snr)
return;
min_signal = snr_to_signal(&ln->node, config.min_snr);
list_for_each_entry(si, &ln->node.sta_info, node_list) {
if (!si->connected)
continue;
if (si->signal >= min_signal)
continue;
si->kick_count++;
MSG(VERBOSE, "Kicking client "MAC_ADDR_FMT" due to low SNR, signal=%d\n",
MAC_ADDR_DATA(si->sta->addr), si->signal);
usteer_ubus_kick_client(si);
return;
}
}
void
usteer_local_node_kick(struct usteer_local_node *ln)
{
struct usteer_node *node = &ln->node;
struct sta_info *kick1 = NULL, *kick2 = NULL;
struct sta_info *candidate = NULL;
struct sta_info *si;
usteer_local_node_roam_check(ln);
usteer_local_node_snr_kick(ln);
if (!config.load_kick_enabled || !config.load_kick_threshold ||
!config.load_kick_delay)
return;
if (node->load < config.load_kick_threshold) {
MSG_T("load_kick_threshold",
"is below load for this node (config=%i) (real=%i)\n",
config.load_kick_threshold, node->load);
ln->load_thr_count = 0;
return;
}
if (++ln->load_thr_count <=
DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update)) {
MSG_T("load_kick_delay", "delay kicking (config=%i)\n",
config.load_kick_delay);
return;
}
MSG(VERBOSE, "AP load threshold exceeded on %s (%d), try to kick a client\n",
usteer_node_name(node), node->load);
ln->load_thr_count = 0;
if (node->n_assoc < config.load_kick_min_clients) {
MSG_T("load_kick_min_clients",
"min limit reached, stop kicking clients on this node "
"(n_assoc=%i) (config=%i)\n",
node->n_assoc, config.load_kick_min_clients);
return;
}
list_for_each_entry(si, &ln->node.sta_info, node_list) {
struct sta_info *tmp;
if (!si->connected)
continue;
if (is_more_kickable(kick1, si))
kick1 = si;
tmp = find_better_candidate(si);
if (!tmp)
continue;
if (is_more_kickable(kick2, si)) {
kick2 = si;
candidate = tmp;
}
}
if (!kick1)
return;
if (kick2)
kick1 = kick2;
MSG(VERBOSE, "Kicking client "MAC_ADDR_FMT", signal=%d, better_candidate=%s\n",
MAC_ADDR_DATA(kick1->sta->addr), kick1->signal,
candidate ? usteer_node_name(candidate->node) : "(none)");
kick1->kick_count++;
usteer_ubus_kick_client(kick1);
}

562
remote.c Normal file
View File

@@ -0,0 +1,562 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <libubox/vlist.h>
#include <libubox/avl-cmp.h>
#include <libubox/usock.h>
#include "usteer.h"
#include "remote.h"
#include "node.h"
static uint32_t local_id;
static struct uloop_fd remote_fd;
static struct uloop_timeout remote_timer;
static struct uloop_timeout reload_timer;
static struct blob_buf buf;
static uint32_t msg_seq;
struct interface {
struct vlist_node node;
int ifindex;
};
static void
interfaces_update_cb(struct vlist_tree *tree,
struct vlist_node *node_new,
struct vlist_node *node_old);
static int remote_node_cmp(const void *k1, const void *k2, void *ptr)
{
unsigned long v1 = (unsigned long) k1;
unsigned long v2 = (unsigned long) k2;
return v2 - v1;
}
static VLIST_TREE(interfaces, avl_strcmp, interfaces_update_cb, true, true);
AVL_TREE(remote_nodes, remote_node_cmp, true, NULL);
static const char *
interface_name(struct interface *iface)
{
return iface->node.avl.key;
}
static void
interface_check(struct interface *iface)
{
iface->ifindex = if_nametoindex(interface_name(iface));
uloop_timeout_set(&reload_timer, 1);
}
static void
interface_init(struct interface *iface)
{
interface_check(iface);
}
static void
interface_free(struct interface *iface)
{
avl_delete(&interfaces.avl, &iface->node.avl);
free(iface);
}
static void
interfaces_update_cb(struct vlist_tree *tree,
struct vlist_node *node_new,
struct vlist_node *node_old)
{
struct interface *iface;
if (node_new && node_old) {
iface = container_of(node_new, struct interface, node);
free(iface);
iface = container_of(node_old, struct interface, node);
interface_check(iface);
} else if (node_old) {
iface = container_of(node_old, struct interface, node);
interface_free(iface);
} else {
iface = container_of(node_new, struct interface, node);
interface_init(iface);
}
}
void usteer_interface_add(const char *name)
{
struct interface *iface;
char *name_buf;
iface = calloc_a(sizeof(*iface), &name_buf, strlen(name) + 1);
strcpy(name_buf, name);
vlist_add(&interfaces, &iface->node, name_buf);
}
void config_set_interfaces(struct blob_attr *data)
{
struct blob_attr *cur;
int rem;
if (!blobmsg_check_attr_list(data, BLOBMSG_TYPE_STRING))
return;
vlist_update(&interfaces);
blobmsg_for_each_attr(cur, data, rem) {
usteer_interface_add(blobmsg_data(cur));
}
vlist_flush(&interfaces);
}
void config_get_interfaces(struct blob_buf *buf)
{
struct interface *iface;
void *c;
c = blobmsg_open_array(buf, "interfaces");
vlist_for_each_element(&interfaces, iface, node) {
blobmsg_add_string(buf, NULL, interface_name(iface));
}
blobmsg_close_array(buf, c);
}
static void
interface_add_station(struct usteer_remote_node *node, struct blob_attr *data)
{
struct sta *sta;
struct sta_info *si;
struct apmsg_sta msg;
bool create;
if (!parse_apmsg_sta(&msg, data)) {
MSG(DEBUG, "Cannot parse station in message\n");
return;
}
if (msg.timeout <= 0) {
MSG(DEBUG, "Refuse to add an already expired station entry\n");
return;
}
sta = usteer_sta_get(msg.addr, true);
if (!sta)
return;
si = usteer_sta_info_get(sta, &node->node, &create);
if (!si)
return;
si->connected = msg.connected;
si->signal = msg.signal;
si->seen = current_time - msg.seen;
usteer_sta_info_update_timeout(si, msg.timeout);
}
static void
remote_node_free(struct usteer_remote_node *node)
{
avl_delete(&remote_nodes, &node->avl);
usteer_sta_node_cleanup(&node->node);
free(node);
}
static struct usteer_remote_node *
interface_get_node(const char *addr, unsigned long id, const char *name)
{
struct usteer_remote_node *node;
int addr_len = strlen(addr);
char *buf;
node = avl_find_element(&remote_nodes, (void *) id, node, avl);
while (node && node->avl.key == (void *) id) {
if (!strcmp(node->name, name))
return node;
node = avl_next_element(node, avl);
}
node = calloc_a(sizeof(*node), &buf, addr_len + 1 + strlen(name) + 1);
node->avl.key = (void *) id;
node->node.type = NODE_TYPE_REMOTE;
sprintf(buf, "%s#%s", addr, name);
node->node.avl.key = buf;
node->name = buf + addr_len + 1;
INIT_LIST_HEAD(&node->node.sta_info);
avl_insert(&remote_nodes, &node->avl);
return node;
}
static void
interface_add_node(struct interface *iface, const char *addr, unsigned long id, struct blob_attr *data)
{
struct usteer_remote_node *node;
struct apmsg_node msg;
struct blob_attr *cur;
int rem;
if (!parse_apmsg_node(&msg, data)) {
MSG(DEBUG, "Cannot parse node in message\n");
return;
}
node = interface_get_node(addr, id, msg.name);
node->check = 0;
node->node.freq = msg.freq;
node->node.n_assoc = msg.n_assoc;
node->node.max_assoc = msg.max_assoc;
node->node.noise = msg.noise;
node->node.load = msg.load;
node->iface = iface;
snprintf(node->node.ssid, sizeof(node->node.ssid), "%s", msg.ssid);
usteer_node_set_blob(&node->node.rrm_nr, msg.rrm_nr);
usteer_node_set_blob(&node->node.script_data, msg.script_data);
blob_for_each_attr(cur, msg.stations, rem)
interface_add_station(node, cur);
}
static void
interface_recv_msg(struct interface *iface, struct in_addr *addr, void *buf, int len)
{
char addr_str[INET_ADDRSTRLEN];
struct blob_attr *data = buf;
struct apmsg msg;
struct blob_attr *cur;
int rem;
if (blob_pad_len(data) != len) {
MSG(DEBUG, "Invalid message length (header: %d, real: %d)\n", blob_pad_len(data), len);
return;
}
if (!parse_apmsg(&msg, data)) {
MSG(DEBUG, "Missing fields in message\n");
return;
}
if (msg.id == local_id)
return;
MSG(NETWORK, "Received message on %s (id=%08x->%08x seq=%d len=%d)\n",
interface_name(iface), msg.id, local_id, msg.seq, len);
inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));
blob_for_each_attr(cur, msg.nodes, rem)
interface_add_node(iface, addr_str, msg.id, cur);
}
static struct interface *
interface_find_by_ifindex(int index)
{
struct interface *iface;
vlist_for_each_element(&interfaces, iface, node) {
if (iface->ifindex == index)
return iface;
}
return NULL;
}
static void
interface_recv(struct uloop_fd *u, unsigned int events)
{
static char buf[APMGR_BUFLEN];
static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1];
static struct sockaddr_in sin;
static struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf)
};
static struct msghdr msg = {
.msg_name = &sin,
.msg_namelen = sizeof(sin),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_buf,
.msg_controllen = sizeof(cmsg_buf),
};
struct cmsghdr *cmsg;
int len;
do {
struct in_pktinfo *pkti = NULL;
struct interface *iface;
len = recvmsg(u->fd, &msg, 0);
if (len < 0) {
switch (errno) {
case EAGAIN:
return;
case EINTR:
continue;
default:
perror("recvmsg");
uloop_fd_delete(u);
return;
}
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_type != IP_PKTINFO)
continue;
pkti = (struct in_pktinfo *) CMSG_DATA(cmsg);
}
if (!pkti) {
MSG(DEBUG, "Received packet without ifindex\n");
continue;
}
iface = interface_find_by_ifindex(pkti->ipi_ifindex);
if (!iface) {
MSG(DEBUG, "Received packet from unconfigured interface %d\n", pkti->ipi_ifindex);
continue;
}
interface_recv_msg(iface, &sin.sin_addr, buf, len);
} while (1);
}
static void interface_send_msg(struct interface *iface, struct blob_attr *data)
{
static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1];
static struct sockaddr_in a;
static struct iovec iov;
static struct msghdr m = {
.msg_name = (struct sockaddr *) &a,
.msg_namelen = sizeof(a),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_data,
.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)),
};
struct in_pktinfo *pkti;
struct cmsghdr *cmsg;
a.sin_family = AF_INET;
a.sin_port = htons(16720);
a.sin_addr.s_addr = ~0;
memset(cmsg_data, 0, sizeof(cmsg_data));
cmsg = CMSG_FIRSTHDR(&m);
cmsg->cmsg_len = m.msg_controllen;
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
pkti = (struct in_pktinfo *) CMSG_DATA(cmsg);
pkti->ipi_ifindex = iface->ifindex;
iov.iov_base = data;
iov.iov_len = blob_pad_len(data);
if (sendmsg(remote_fd.fd, &m, 0) < 0)
perror("sendmsg");
}
static void usteer_send_sta_info(struct sta_info *sta)
{
int seen = current_time - sta->seen;
void *c;
c = blob_nest_start(&buf, 0);
blob_put(&buf, APMSG_STA_ADDR, sta->sta->addr, 6);
blob_put_int8(&buf, APMSG_STA_CONNECTED, !!sta->connected);
blob_put_int32(&buf, APMSG_STA_SIGNAL, sta->signal);
blob_put_int32(&buf, APMSG_STA_SEEN, seen);
blob_put_int32(&buf, APMSG_STA_TIMEOUT, config.local_sta_timeout - seen);
blob_nest_end(&buf, c);
}
static void usteer_send_node(struct usteer_node *node, struct sta_info *sta)
{
void *c, *s, *r;
c = blob_nest_start(&buf, 0);
blob_put_string(&buf, APMSG_NODE_NAME, usteer_node_name(node));
blob_put_string(&buf, APMSG_NODE_SSID, node->ssid);
blob_put_int32(&buf, APMSG_NODE_FREQ, node->freq);
blob_put_int32(&buf, APMSG_NODE_NOISE, node->noise);
blob_put_int32(&buf, APMSG_NODE_LOAD, node->load);
blob_put_int32(&buf, APMSG_NODE_N_ASSOC, node->n_assoc);
blob_put_int32(&buf, APMSG_NODE_MAX_ASSOC, node->max_assoc);
if (node->rrm_nr) {
r = blob_nest_start(&buf, APMSG_NODE_RRM_NR);
blobmsg_add_field(&buf, BLOBMSG_TYPE_ARRAY, "",
blobmsg_data(node->rrm_nr),
blobmsg_data_len(node->rrm_nr));
blob_nest_end(&buf, r);
}
if (node->script_data)
blob_put(&buf, APMSG_NODE_SCRIPT_DATA,
blob_data(node->script_data),
blob_len(node->script_data));
s = blob_nest_start(&buf, APMSG_NODE_STATIONS);
if (sta) {
usteer_send_sta_info(sta);
} else {
list_for_each_entry(sta, &node->sta_info, node_list)
usteer_send_sta_info(sta);
}
blob_nest_end(&buf, s);
blob_nest_end(&buf, c);
}
static void
usteer_check_timeout(void)
{
struct usteer_remote_node *node, *tmp;
int timeout = config.remote_node_timeout / config.remote_update_interval;
avl_for_each_element_safe(&remote_nodes, node, avl, tmp) {
if (node->check++ > timeout)
remote_node_free(node);
}
}
static void *
usteer_update_init(void)
{
blob_buf_init(&buf, 0);
blob_put_int32(&buf, APMSG_ID, local_id);
blob_put_int32(&buf, APMSG_SEQ, ++msg_seq);
return blob_nest_start(&buf, APMSG_NODES);
}
static void
usteer_update_send(void *c)
{
struct interface *iface;
blob_nest_end(&buf, c);
vlist_for_each_element(&interfaces, iface, node)
interface_send_msg(iface, buf.head);
}
void
usteer_send_sta_update(struct sta_info *si)
{
void *c = usteer_update_init();
usteer_send_node(si->node, si);
usteer_update_send(c);
}
static void
usteer_send_update_timer(struct uloop_timeout *t)
{
struct usteer_node *node;
void *c;
MSG_T("remote_update_interval", "start remote update (interval=%u)\n",
config.remote_update_interval);
usteer_update_time();
uloop_timeout_set(t, config.remote_update_interval);
c = usteer_update_init();
avl_for_each_element(&local_nodes, node, avl)
usteer_send_node(node, NULL);
usteer_update_send(c);
usteer_check_timeout();
}
static int
usteer_init_local_id(void)
{
FILE *f;
f = fopen("/dev/urandom", "r");
if (!f) {
perror("fopen(/dev/urandom)");
return -1;
}
if (fread(&local_id, sizeof(local_id), 1, f) < 1)
return -1;
fclose(f);
return 0;
}
static void
usteer_reload_timer(struct uloop_timeout *t)
{
int yes = 1;
int fd;
if (remote_fd.registered) {
uloop_fd_delete(&remote_fd);
close(remote_fd.fd);
}
fd = usock(USOCK_UDP | USOCK_SERVER | USOCK_NONBLOCK |
USOCK_NUMERIC | USOCK_IPV4ONLY,
"0.0.0.0", APMGR_PORT_STR);
if (fd < 0) {
perror("usock");
return;
}
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0)
perror("setsockopt(IP_PKTINFO)");
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) < 0)
perror("setsockopt(SO_BROADCAST)");
remote_fd.fd = fd;
remote_fd.cb = interface_recv;
uloop_fd_add(&remote_fd, ULOOP_READ);
}
int usteer_interface_init(void)
{
if (usteer_init_local_id())
return -1;
remote_timer.cb = usteer_send_update_timer;
remote_timer.cb(&remote_timer);
reload_timer.cb = usteer_reload_timer;
reload_timer.cb(&reload_timer);
return 0;
}

87
remote.h Normal file
View File

@@ -0,0 +1,87 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#ifndef __APMGR_REMOTE_H
#define __APMGR_REMOTE_H
#include <libubox/blob.h>
enum {
APMSG_ID,
APMSG_SEQ,
APMSG_NODES,
__APMSG_MAX
};
struct apmsg {
uint32_t id;
uint32_t seq;
struct blob_attr *nodes;
};
enum {
APMSG_NODE_NAME,
APMSG_NODE_FREQ,
APMSG_NODE_N_ASSOC,
APMSG_NODE_STATIONS,
APMSG_NODE_NOISE,
APMSG_NODE_LOAD,
APMSG_NODE_SSID,
APMSG_NODE_MAX_ASSOC,
APMSG_NODE_RRM_NR,
APMSG_NODE_SCRIPT_DATA,
__APMSG_NODE_MAX
};
struct apmsg_node {
const char *name;
const char *ssid;
int freq;
int n_assoc;
int max_assoc;
int noise;
int load;
struct blob_attr *stations;
struct blob_attr *rrm_nr;
struct blob_attr *script_data;
};
enum {
APMSG_STA_ADDR,
APMSG_STA_SIGNAL,
APMSG_STA_TIMEOUT,
APMSG_STA_SEEN,
APMSG_STA_CONNECTED,
__APMSG_STA_MAX
};
struct apmsg_sta {
uint8_t addr[6];
bool connected;
int signal;
int timeout;
int seen;
};
bool parse_apmsg(struct apmsg *msg, struct blob_attr *data);
bool parse_apmsg_node(struct apmsg_node *msg, struct blob_attr *data);
bool parse_apmsg_sta(struct apmsg_sta *msg, struct blob_attr *data);
#endif

210
sta.c Normal file
View File

@@ -0,0 +1,210 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include "usteer.h"
static int
avl_macaddr_cmp(const void *k1, const void *k2, void *ptr)
{
return memcmp(k1, k2, 6);
}
AVL_TREE(stations, avl_macaddr_cmp, false, NULL);
static struct usteer_timeout_queue tq;
static void
usteer_sta_del(struct sta *sta)
{
MSG(DEBUG, "Delete station " MAC_ADDR_FMT "\n",
MAC_ADDR_DATA(sta->addr));
avl_delete(&stations, &sta->avl);
free(sta);
}
static void
usteer_sta_info_del(struct sta_info *si)
{
struct sta *sta = si->sta;
MSG(DEBUG, "Delete station " MAC_ADDR_FMT " entry for node %s\n",
MAC_ADDR_DATA(sta->addr), usteer_node_name(si->node));
usteer_timeout_cancel(&tq, &si->timeout);
list_del(&si->list);
list_del(&si->node_list);
free(si);
if (list_empty(&sta->nodes))
usteer_sta_del(sta);
}
void
usteer_sta_node_cleanup(struct usteer_node *node)
{
struct sta_info *si, *tmp;
free(node->rrm_nr);
node->rrm_nr = NULL;
list_for_each_entry_safe(si, tmp, &node->sta_info, node_list)
usteer_sta_info_del(si);
}
static void
usteer_sta_info_timeout(struct usteer_timeout_queue *q, struct usteer_timeout *t)
{
struct sta_info *si = container_of(t, struct sta_info, timeout);
MSG_T_STA("local_sta_timeout", si->sta->addr,
"timeout expired, deleting sta info\n");
usteer_sta_info_del(si);
}
struct sta_info *
usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create)
{
struct sta_info *si;
list_for_each_entry(si, &sta->nodes, list) {
if (si->node != node)
continue;
if (create)
*create = false;
return si;
}
if (!create)
return NULL;
MSG(DEBUG, "Create station " MAC_ADDR_FMT " entry for node %s\n",
MAC_ADDR_DATA(sta->addr), usteer_node_name(node));
si = calloc(1, sizeof(*si));
si->node = node;
si->sta = sta;
list_add(&si->list, &sta->nodes);
list_add(&si->node_list, &node->sta_info);
si->created = current_time;
*create = true;
return si;
}
void
usteer_sta_info_update_timeout(struct sta_info *si, int timeout)
{
if (si->connected == 1)
usteer_timeout_cancel(&tq, &si->timeout);
else if (timeout > 0)
usteer_timeout_set(&tq, &si->timeout, timeout);
else
usteer_sta_info_del(si);
}
struct sta *
usteer_sta_get(const uint8_t *addr, bool create)
{
struct sta *sta;
sta = avl_find_element(&stations, addr, sta, avl);
if (sta)
return sta;
if (!create)
return NULL;
MSG(DEBUG, "Create station entry " MAC_ADDR_FMT "\n", MAC_ADDR_DATA(addr));
sta = calloc(1, sizeof(*sta));
memcpy(sta->addr, addr, sizeof(sta->addr));
sta->avl.key = sta->addr;
avl_insert(&stations, &sta->avl);
INIT_LIST_HEAD(&sta->nodes);
return sta;
}
void
usteer_sta_info_update(struct sta_info *si, int signal, bool avg)
{
/* ignore probe request signal when connected */
if (si->connected == 1 && si->signal != NO_SIGNAL && !avg)
signal = NO_SIGNAL;
if (signal != NO_SIGNAL)
si->signal = signal;
si->seen = current_time;
usteer_sta_info_update_timeout(si, config.local_sta_timeout);
}
bool
usteer_handle_sta_event(struct usteer_node *node, const uint8_t *addr,
enum usteer_event_type type, int freq, int signal)
{
struct sta *sta;
struct sta_info *si;
uint32_t diff;
bool ret;
bool create;
sta = usteer_sta_get(addr, true);
if (!sta)
return -1;
if (freq < 4000)
sta->seen_2ghz = 1;
else
sta->seen_5ghz = 1;
si = usteer_sta_info_get(sta, node, &create);
usteer_sta_info_update(si, signal, false);
si->roam_scan_done = current_time;
si->stats[type].requests++;
diff = si->stats[type].blocked_last_time - current_time;
if (diff > config.sta_block_timeout) {
si->stats[type].blocked_cur = 0;
MSG_T_STA("sta_block_timeout", addr, "timeout expired\n");
}
ret = usteer_check_request(si, type);
if (!ret) {
si->stats[type].blocked_cur++;
si->stats[type].blocked_total++;
si->stats[type].blocked_last_time = current_time;
} else {
si->stats[type].blocked_cur = 0;
}
if (create)
usteer_send_sta_update(si);
return ret;
}
static void __usteer_init usteer_sta_init(void)
{
usteer_timeout_init(&tq);
tq.cb = usteer_sta_info_timeout;
}

160
timeout.c Normal file
View File

@@ -0,0 +1,160 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <string.h>
#include <libubox/utils.h>
#include "timeout.h"
static int usteer_timeout_cmp(const void *k1, const void *k2, void *ptr)
{
uint32_t ref = (uint32_t) (intptr_t) ptr;
int32_t t1 = (uint32_t) (intptr_t) k1 - ref;
int32_t t2 = (uint32_t) (intptr_t) k2 - ref;
if (t1 < t2)
return -1;
else if (t1 > t2)
return 1;
else
return 0;
}
static int32_t usteer_timeout_delta(struct usteer_timeout *t, uint32_t time)
{
uint32_t val = (uint32_t) (intptr_t) t->node.key;
return val - time;
}
static void usteer_timeout_recalc(struct usteer_timeout_queue *q, uint32_t time)
{
struct usteer_timeout *t;
int32_t delta;
if (avl_is_empty(&q->tree)) {
uloop_timeout_cancel(&q->timeout);
return;
}
t = avl_first_element(&q->tree, t, node);
delta = usteer_timeout_delta(t, time);
if (delta < 1)
delta = 1;
uloop_timeout_set(&q->timeout, delta);
}
static uint32_t ampgr_timeout_current_time(void)
{
struct timespec ts;
uint32_t val;
clock_gettime(CLOCK_MONOTONIC, &ts);
val = ts.tv_sec * 1000;
val += ts.tv_nsec / 1000000;
return val;
}
static void usteer_timeout_cb(struct uloop_timeout *timeout)
{
struct usteer_timeout_queue *q;
struct usteer_timeout *t, *tmp;
bool found;
uint32_t time;
q = container_of(timeout, struct usteer_timeout_queue, timeout);
do {
found = false;
time = ampgr_timeout_current_time();
avl_for_each_element_safe(&q->tree, t, node, tmp) {
if (usteer_timeout_delta(t, time) > 0)
break;
usteer_timeout_cancel(q, t);
if (q->cb)
q->cb(q, t);
found = true;
}
} while (found);
usteer_timeout_recalc(q, time);
}
void usteer_timeout_init(struct usteer_timeout_queue *q)
{
avl_init(&q->tree, usteer_timeout_cmp, true, NULL);
q->timeout.cb = usteer_timeout_cb;
}
static void __usteer_timeout_cancel(struct usteer_timeout_queue *q,
struct usteer_timeout *t)
{
avl_delete(&q->tree, &t->node);
}
void usteer_timeout_set(struct usteer_timeout_queue *q, struct usteer_timeout *t,
int msecs)
{
uint32_t time = ampgr_timeout_current_time();
uint32_t val = time + msecs;
bool recalc = false;
q->tree.cmp_ptr = (void *) (intptr_t) time;
if (usteer_timeout_isset(t)) {
if (avl_is_first(&q->tree, &t->node))
recalc = true;
__usteer_timeout_cancel(q, t);
}
t->node.key = (void *) (intptr_t) val;
avl_insert(&q->tree, &t->node);
if (avl_is_first(&q->tree, &t->node))
recalc = true;
if (recalc)
usteer_timeout_recalc(q, time);
}
void usteer_timeout_cancel(struct usteer_timeout_queue *q,
struct usteer_timeout *t)
{
if (!usteer_timeout_isset(t))
return;
__usteer_timeout_cancel(q, t);
memset(&t->node.list, 0, sizeof(t->node.list));
}
void usteer_timeout_flush(struct usteer_timeout_queue *q)
{
struct usteer_timeout *t, *tmp;
uloop_timeout_cancel(&q->timeout);
avl_remove_all_elements(&q->tree, t, node, tmp) {
memset(&t->node.list, 0, sizeof(t->node.list));
if (q->cb)
q->cb(q, t);
}
}

49
timeout.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#ifndef __APMGR_TIMEOUT_H
#define __APMGR_TIMEOUT_H
#include <libubox/avl.h>
#include <libubox/uloop.h>
struct usteer_timeout {
struct avl_node node;
};
struct usteer_timeout_queue {
struct avl_tree tree;
struct uloop_timeout timeout;
void (*cb)(struct usteer_timeout_queue *q, struct usteer_timeout *t);
};
static inline bool
usteer_timeout_isset(struct usteer_timeout *t)
{
return t->node.list.prev != NULL;
}
void usteer_timeout_init(struct usteer_timeout_queue *q);
void usteer_timeout_set(struct usteer_timeout_queue *q, struct usteer_timeout *t,
int msecs);
void usteer_timeout_cancel(struct usteer_timeout_queue *q,
struct usteer_timeout *t);
void usteer_timeout_flush(struct usteer_timeout_queue *q);
#endif

419
ubus.c Normal file
View File

@@ -0,0 +1,419 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#ifdef linux
#include <netinet/ether.h>
#endif
#include "usteer.h"
#include "node.h"
static struct blob_buf b;
static int
usteer_ubus_get_clients(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct sta_info *si;
struct sta *sta;
char str[20];
void *_s, *_cur_n;
blob_buf_init(&b, 0);
avl_for_each_element(&stations, sta, avl) {
sprintf(str, MAC_ADDR_FMT, MAC_ADDR_DATA(sta->addr));
_s = blobmsg_open_table(&b, str);
list_for_each_entry(si, &sta->nodes, list) {
_cur_n = blobmsg_open_table(&b, usteer_node_name(si->node));
blobmsg_add_u8(&b, "connected", si->connected);
blobmsg_add_u32(&b, "signal", si->signal);
blobmsg_close_table(&b, _cur_n);
}
blobmsg_close_table(&b, _s);
}
ubus_send_reply(ctx, req, b.head);
return 0;
}
static struct blobmsg_policy client_arg[] = {
{ .name = "address", .type = BLOBMSG_TYPE_STRING, },
};
static void
usteer_ubus_add_stats(struct sta_info_stats *stats, const char *name)
{
void *s;
s = blobmsg_open_table(&b, name);
blobmsg_add_u32(&b, "requests", stats->requests);
blobmsg_add_u32(&b, "blocked_cur", stats->blocked_cur);
blobmsg_add_u32(&b, "blocked_total", stats->blocked_total);
blobmsg_close_table(&b, s);
}
static int
usteer_ubus_get_client_info(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct sta_info *si;
struct sta *sta;
struct blob_attr *mac_str;
uint8_t *mac;
void *_n, *_cur_n, *_s;
int i;
blobmsg_parse(client_arg, 1, &mac_str, blob_data(msg), blob_len(msg));
if (!mac_str)
return UBUS_STATUS_INVALID_ARGUMENT;
mac = (uint8_t *) ether_aton(blobmsg_data(mac_str));
if (!mac)
return UBUS_STATUS_INVALID_ARGUMENT;
sta = usteer_sta_get(mac, false);
if (!sta)
return UBUS_STATUS_NOT_FOUND;
blob_buf_init(&b, 0);
blobmsg_add_u8(&b, "2ghz", sta->seen_2ghz);
blobmsg_add_u8(&b, "5ghz", sta->seen_5ghz);
_n = blobmsg_open_table(&b, "nodes");
list_for_each_entry(si, &sta->nodes, list) {
_cur_n = blobmsg_open_table(&b, usteer_node_name(si->node));
blobmsg_add_u8(&b, "connected", si->connected);
blobmsg_add_u32(&b, "signal", si->signal);
_s = blobmsg_open_table(&b, "stats");
for (i = 0; i < __EVENT_TYPE_MAX; i++)
usteer_ubus_add_stats(&si->stats[EVENT_TYPE_PROBE], event_types[i]);
blobmsg_close_table(&b, _s);
blobmsg_close_table(&b, _cur_n);
}
blobmsg_close_table(&b, _n);
ubus_send_reply(ctx, req, b.head);
return 0;
}
enum cfg_type {
CFG_BOOL,
CFG_I32,
CFG_U32,
CFG_ARRAY_CB,
CFG_STRING_CB,
};
struct cfg_item {
enum cfg_type type;
union {
bool *BOOL;
uint32_t *U32;
int32_t *I32;
struct {
void (*set)(struct blob_attr *data);
void (*get)(struct blob_buf *buf);
} CB;
} ptr;
};
#define __config_items \
_cfg(BOOL, syslog), \
_cfg(U32, debug_level), \
_cfg(U32, sta_block_timeout), \
_cfg(U32, local_sta_timeout), \
_cfg(U32, local_sta_update), \
_cfg(U32, max_retry_band), \
_cfg(U32, seen_policy_timeout), \
_cfg(U32, load_balancing_threshold), \
_cfg(U32, band_steering_threshold), \
_cfg(U32, remote_update_interval), \
_cfg(I32, min_connect_snr), \
_cfg(I32, min_snr), \
_cfg(I32, roam_scan_snr), \
_cfg(U32, roam_scan_tries), \
_cfg(U32, roam_scan_interval), \
_cfg(I32, roam_trigger_snr), \
_cfg(U32, roam_trigger_interval), \
_cfg(U32, roam_kick_delay), \
_cfg(U32, signal_diff_threshold), \
_cfg(U32, initial_connect_delay), \
_cfg(BOOL, load_kick_enabled), \
_cfg(U32, load_kick_threshold), \
_cfg(U32, load_kick_delay), \
_cfg(U32, load_kick_min_clients), \
_cfg(U32, load_kick_reason_code), \
_cfg(ARRAY_CB, interfaces), \
_cfg(STRING_CB, node_up_script)
enum cfg_items {
#define _cfg(_type, _name) CFG_##_name
__config_items,
#undef _cfg
__CFG_MAX,
};
static const struct blobmsg_policy config_policy[__CFG_MAX] = {
#define _cfg_policy(_type, _name) [CFG_##_name] = { .name = #_name, .type = BLOBMSG_TYPE_ ## _type }
#define _cfg_policy_BOOL(_name) _cfg_policy(BOOL, _name)
#define _cfg_policy_U32(_name) _cfg_policy(INT32, _name)
#define _cfg_policy_I32(_name) _cfg_policy(INT32, _name)
#define _cfg_policy_ARRAY_CB(_name) _cfg_policy(ARRAY, _name)
#define _cfg_policy_STRING_CB(_name) _cfg_policy(STRING, _name)
#define _cfg(_type, _name) _cfg_policy_##_type(_name)
__config_items,
#undef _cfg
};
static const struct cfg_item config_data[__CFG_MAX] = {
#define _cfg_data_BOOL(_name) .ptr.BOOL = &config._name
#define _cfg_data_U32(_name) .ptr.U32 = &config._name
#define _cfg_data_I32(_name) .ptr.I32 = &config._name
#define _cfg_data_ARRAY_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name }
#define _cfg_data_STRING_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name }
#define _cfg(_type, _name) [CFG_##_name] = { .type = CFG_##_type, _cfg_data_##_type(_name) }
__config_items,
#undef _cfg
};
static int
usteer_ubus_get_config(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
int i;
blob_buf_init(&b, 0);
for (i = 0; i < __CFG_MAX; i++) {
switch(config_data[i].type) {
case CFG_BOOL:
blobmsg_add_u8(&b, config_policy[i].name,
*config_data[i].ptr.BOOL);
break;
case CFG_I32:
case CFG_U32:
blobmsg_add_u32(&b, config_policy[i].name,
*config_data[i].ptr.U32);
break;
case CFG_ARRAY_CB:
case CFG_STRING_CB:
config_data[i].ptr.CB.get(&b);
break;
}
}
ubus_send_reply(ctx, req, b.head);
return 0;
}
static int
usteer_ubus_set_config(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__CFG_MAX];
int i;
if (!strcmp(method, "set_config"))
usteer_init_defaults();
blobmsg_parse(config_policy, __CFG_MAX, tb, blob_data(msg), blob_len(msg));
for (i = 0; i < __CFG_MAX; i++) {
if (!tb[i])
continue;
switch(config_data[i].type) {
case CFG_BOOL:
*config_data[i].ptr.BOOL = blobmsg_get_u8(tb[i]);
break;
case CFG_I32:
case CFG_U32:
*config_data[i].ptr.U32 = blobmsg_get_u32(tb[i]);
break;
case CFG_ARRAY_CB:
case CFG_STRING_CB:
config_data[i].ptr.CB.set(tb[i]);
break;
}
}
return 0;
}
static void
usteer_dump_node_info(struct usteer_node *node)
{
void *c;
c = blobmsg_open_table(&b, usteer_node_name(node));
blobmsg_add_u32(&b, "freq", node->freq);
blobmsg_add_u32(&b, "n_assoc", node->n_assoc);
blobmsg_add_u32(&b, "noise", node->noise);
blobmsg_add_u32(&b, "load", node->load);
blobmsg_add_u32(&b, "max_assoc", node->max_assoc);
if (node->rrm_nr)
blobmsg_add_field(&b, BLOBMSG_TYPE_ARRAY, "rrm_nr",
blobmsg_data(node->rrm_nr),
blobmsg_data_len(node->rrm_nr));
blobmsg_close_table(&b, c);
}
static int
usteer_ubus_local_info(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct usteer_node *node;
blob_buf_init(&b, 0);
avl_for_each_element(&local_nodes, node, avl)
usteer_dump_node_info(node);
ubus_send_reply(ctx, req, b.head);
return 0;
}
static int
usteer_ubus_remote_info(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct usteer_remote_node *rn;
blob_buf_init(&b, 0);
avl_for_each_element(&remote_nodes, rn, avl)
usteer_dump_node_info(&rn->node);
ubus_send_reply(ctx, req, b.head);
return 0;
}
static const struct ubus_method usteer_methods[] = {
UBUS_METHOD_NOARG("local_info", usteer_ubus_local_info),
UBUS_METHOD_NOARG("remote_info", usteer_ubus_remote_info),
UBUS_METHOD_NOARG("get_clients", usteer_ubus_get_clients),
UBUS_METHOD("get_client_info", usteer_ubus_get_client_info, client_arg),
UBUS_METHOD_NOARG("get_config", usteer_ubus_get_config),
UBUS_METHOD("set_config", usteer_ubus_set_config, config_policy),
UBUS_METHOD("update_config", usteer_ubus_set_config, config_policy),
};
static struct ubus_object_type usteer_obj_type =
UBUS_OBJECT_TYPE("usteer", usteer_methods);
static struct ubus_object usteer_obj = {
.name = "usteer",
.type = &usteer_obj_type,
.methods = usteer_methods,
.n_methods = ARRAY_SIZE(usteer_methods),
};
static void
usteer_add_nr_entry(struct usteer_node *ln, struct usteer_node *node)
{
struct blobmsg_policy policy[3] = {
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_STRING },
{ .type = BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[3];
if (!node->rrm_nr)
return;
if (strcmp(ln->ssid, node->ssid) != 0)
return;
blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb,
blobmsg_data(node->rrm_nr),
blobmsg_data_len(node->rrm_nr));
if (!tb[2])
return;
blobmsg_add_field(&b, BLOBMSG_TYPE_STRING, "",
blobmsg_data(tb[2]),
blobmsg_data_len(tb[2]));
}
int usteer_ubus_notify_client_disassoc(struct sta_info *si)
{
struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
struct usteer_remote_node *rn;
struct usteer_node *node;
void *c;
blob_buf_init(&b, 0);
blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
blobmsg_add_u32(&b, "duration", config.roam_kick_delay);
c = blobmsg_open_array(&b, "neighbors");
avl_for_each_element(&local_nodes, node, avl)
usteer_add_nr_entry(si->node, node);
avl_for_each_element(&remote_nodes, rn, avl)
usteer_add_nr_entry(si->node, &rn->node);
blobmsg_close_array(&b, c);
return ubus_invoke(ubus_ctx, ln->obj_id, "wnm_disassoc_imminent", b.head, NULL, 0, 100);
}
int usteer_ubus_trigger_client_scan(struct sta_info *si)
{
struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
si->scan_band = !si->scan_band;
MSG_T_STA("load_kick_reason_code", si->sta->addr,
"tell hostapd to issue a client beacon request (5ghz: %d)\n",
si->scan_band);
blob_buf_init(&b, 0);
blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
blobmsg_add_u32(&b, "mode", 1);
blobmsg_add_u32(&b, "duration", 65535);
blobmsg_add_u32(&b, "channel", 255);
blobmsg_add_u32(&b, "op_class", si->scan_band ? 1 : 12);
return ubus_invoke(ubus_ctx, ln->obj_id, "rrm_beacon_req", b.head, NULL, 0, 100);
}
void usteer_ubus_kick_client(struct sta_info *si)
{
struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
MSG_T_STA("load_kick_reason_code", si->sta->addr,
"tell hostapd to kick client with reason code %u\n",
config.load_kick_reason_code);
blob_buf_init(&b, 0);
blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
blobmsg_add_u32(&b, "reason", config.load_kick_reason_code);
blobmsg_add_u8(&b, "deauth", 1);
ubus_invoke(ubus_ctx, ln->obj_id, "del_client", b.head, NULL, 0, 100);
si->connected = 0;
si->roam_kick = current_time;
}
void usteer_ubus_init(struct ubus_context *ctx)
{
ubus_add_object(ctx, &usteer_obj);
}

265
usteer.h Normal file
View File

@@ -0,0 +1,265 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#ifndef __APMGR_H
#define __APMGR_H
#include <libubox/avl.h>
#include <libubox/blobmsg.h>
#include <libubox/uloop.h>
#include <libubox/utils.h>
#include <libubus.h>
#include "utils.h"
#include "timeout.h"
#define NO_SIGNAL 0xff
#define __STR(x) #x
#define _STR(x) __STR(x)
#define APMGR_PORT 16720 /* AP */
#define APMGR_PORT_STR _STR(APMGR_PORT)
#define APMGR_BUFLEN (64 * 1024)
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
enum usteer_event_type {
EVENT_TYPE_PROBE,
EVENT_TYPE_ASSOC,
EVENT_TYPE_AUTH,
__EVENT_TYPE_MAX,
};
enum usteer_node_type {
NODE_TYPE_LOCAL,
NODE_TYPE_REMOTE,
};
struct sta_info;
struct usteer_local_node;
struct usteer_node {
struct avl_node avl;
struct list_head sta_info;
enum usteer_node_type type;
struct blob_attr *rrm_nr;
struct blob_attr *script_data;
char ssid[33];
int freq;
int noise;
int n_assoc;
int max_assoc;
int load;
};
struct usteer_scan_request {
int n_freq;
int *freq;
bool passive;
};
struct usteer_scan_result {
uint8_t bssid[6];
char ssid[33];
int freq;
int signal;
};
struct usteer_survey_data {
uint16_t freq;
int8_t noise;
uint64_t time;
uint64_t time_busy;
};
struct usteer_freq_data {
uint16_t freq;
uint8_t txpower;
bool dfs;
};
struct usteer_node_handler {
struct list_head list;
void (*init_node)(struct usteer_node *);
void (*free_node)(struct usteer_node *);
void (*update_node)(struct usteer_node *);
void (*update_sta)(struct usteer_node *, struct sta_info *);
void (*get_survey)(struct usteer_node *, void *,
void (*cb)(void *priv, struct usteer_survey_data *d));
void (*get_freqlist)(struct usteer_node *, void *,
void (*cb)(void *priv, struct usteer_freq_data *f));
int (*scan)(struct usteer_node *, struct usteer_scan_request *,
void *, void (*cb)(void *priv, struct usteer_scan_result *r));
};
struct usteer_config {
bool syslog;
uint32_t debug_level;
uint32_t sta_block_timeout;
uint32_t local_sta_timeout;
uint32_t local_sta_update;
uint32_t max_retry_band;
uint32_t seen_policy_timeout;
uint32_t band_steering_threshold;
uint32_t load_balancing_threshold;
uint32_t remote_update_interval;
uint32_t remote_node_timeout;
int32_t min_snr;
int32_t min_connect_snr;
uint32_t signal_diff_threshold;
int32_t roam_scan_snr;
uint32_t roam_scan_tries;
uint32_t roam_scan_interval;
int32_t roam_trigger_snr;
uint32_t roam_trigger_interval;
uint32_t roam_kick_delay;
uint32_t initial_connect_delay;
bool load_kick_enabled;
uint32_t load_kick_threshold;
uint32_t load_kick_delay;
uint32_t load_kick_min_clients;
uint32_t load_kick_reason_code;
const char *node_up_script;
};
struct sta_info_stats {
uint32_t requests;
uint32_t blocked_cur;
uint32_t blocked_total;
uint32_t blocked_last_time;
};
#define __roam_trigger_states \
_S(IDLE) \
_S(SCAN) \
_S(SCAN_DONE) \
_S(WAIT_KICK) \
_S(NOTIFY_KICK) \
_S(KICK)
enum roam_trigger_state {
#define _S(n) ROAM_TRIGGER_##n,
__roam_trigger_states
#undef _S
};
struct sta_info {
struct list_head list;
struct list_head node_list;
struct usteer_node *node;
struct sta *sta;
struct usteer_timeout timeout;
struct sta_info_stats stats[__EVENT_TYPE_MAX];
uint64_t created;
uint64_t seen;
int signal;
enum roam_trigger_state roam_state;
uint8_t roam_tries;
uint64_t roam_event;
uint64_t roam_kick;
uint64_t roam_scan_done;
int kick_count;
uint8_t scan_band : 1;
uint8_t connected : 2;
};
struct sta {
struct avl_node avl;
struct list_head nodes;
uint8_t seen_2ghz : 1;
uint8_t seen_5ghz : 1;
uint8_t addr[6];
};
extern struct ubus_context *ubus_ctx;
extern struct usteer_config config;
extern struct list_head node_handlers;
extern struct avl_tree stations;
extern uint64_t current_time;
extern const char * const event_types[__EVENT_TYPE_MAX];
void usteer_update_time(void);
void usteer_init_defaults(void);
bool usteer_handle_sta_event(struct usteer_node *node, const uint8_t *addr,
enum usteer_event_type type, int freq, int signal);
void usteer_local_nodes_init(struct ubus_context *ctx);
void usteer_local_node_kick(struct usteer_local_node *ln);
void usteer_ubus_init(struct ubus_context *ctx);
void usteer_ubus_kick_client(struct sta_info *si);
int usteer_ubus_trigger_client_scan(struct sta_info *si);
int usteer_ubus_notify_client_disassoc(struct sta_info *si);
struct sta *usteer_sta_get(const uint8_t *addr, bool create);
struct sta_info *usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create);
void usteer_sta_info_update_timeout(struct sta_info *si, int timeout);
void usteer_sta_info_update(struct sta_info *si, int signal, bool avg);
static inline const char *usteer_node_name(struct usteer_node *node)
{
return node->avl.key;
}
void usteer_node_set_blob(struct blob_attr **dest, struct blob_attr *val);
bool usteer_check_request(struct sta_info *si, enum usteer_event_type type);
void config_set_interfaces(struct blob_attr *data);
void config_get_interfaces(struct blob_buf *buf);
void config_set_node_up_script(struct blob_attr *data);
void config_get_node_up_script(struct blob_buf *buf);
int usteer_interface_init(void);
void usteer_interface_add(const char *name);
void usteer_sta_node_cleanup(struct usteer_node *node);
void usteer_send_sta_update(struct sta_info *si);
int usteer_lua_init(void);
int usteer_lua_ubus_init(void);
void usteer_run_hook(const char *name, const char *arg);
#endif

56
utils.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) 2020 embedd.ch
* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2020 John Crispin <john@phrozen.org>
*/
#ifndef __APMGR_UTILS_H
#define __APMGR_UTILS_H
#define MSG(_nr, _format, ...) debug_msg(MSG_##_nr, __func__, __LINE__, _format, ##__VA_ARGS__)
#define MSG_CONT(_nr, _format, ...) debug_msg_cont(MSG_##_nr, _format, ##__VA_ARGS__)
#define MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
#define MAC_ADDR_DATA(_a) \
((const uint8_t *)(_a))[0], \
((const uint8_t *)(_a))[1], \
((const uint8_t *)(_a))[2], \
((const uint8_t *)(_a))[3], \
((const uint8_t *)(_a))[4], \
((const uint8_t *)(_a))[5]
#define MSG_T_STA(_option, _sta_addr, _format, ...) \
MSG(DEBUG_ALL, "TESTCASE=" _option ",STA=" MAC_ADDR_FMT ": " _format, \
MAC_ADDR_DATA(_sta_addr), ##__VA_ARGS__)
#define MSG_T(_option, _format, ...) \
MSG(DEBUG_ALL, "TESTCASE=" _option ": " _format, ##__VA_ARGS__)
enum usteer_debug {
MSG_FATAL,
MSG_INFO,
MSG_VERBOSE,
MSG_DEBUG,
MSG_NETWORK,
MSG_DEBUG_ALL,
};
extern void debug_msg(int level, const char *func, int line, const char *format, ...);
extern void debug_msg_cont(int level, const char *format, ...);
#define __usteer_init __attribute__((constructor))
#endif