8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/Makefile
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
*.cmake
|
||||
install_manifest.txt
|
||||
/usteerd
|
||||
/ap-monitor
|
||||
/fakeap
|
51
CMakeLists.txt
Normal file
51
CMakeLists.txt
Normal 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
244
fakeap.c
Normal 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
511
local_node.c
Normal 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
163
main.c
Normal 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
207
monitor.c
Normal 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
154
netifd.c
Normal 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
510
nl80211.c
Normal 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
38
node.c
Normal 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
79
node.h
Normal 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
37
openwrt/usteer/Makefile
Normal 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))
|
4
openwrt/usteer/files/etc/config/usteer
Normal file
4
openwrt/usteer/files/etc/config/usteer
Normal file
@@ -0,0 +1,4 @@
|
||||
config usteer
|
||||
option 'network' 'lan'
|
||||
option 'syslog' '1'
|
||||
option 'debug_level' '2'
|
120
openwrt/usteer/files/etc/init.d/usteer
Executable file
120
openwrt/usteer/files/etc/init.d/usteer
Executable 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
142
parse.c
Normal 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
436
policy.c
Normal 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
562
remote.c
Normal 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
87
remote.h
Normal 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
210
sta.c
Normal 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
160
timeout.c
Normal 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
49
timeout.h
Normal 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
419
ubus.c
Normal 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
265
usteer.h
Normal 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
56
utils.h
Normal 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
|
Reference in New Issue
Block a user