Files
usteer/local_node.c
David Bauer 8c35c8e6a0 local-node: don't read pointer after clear
The name pointer provided by ubus get's cleared after the first call to
ubus_register_subscriber in usteer_get_node. The leads to an incorrect
ifindex returned by if_nametoindex due to the 0 characters long string.

Work around this issue by reusing the interface name already stored in
the local node struct.

Fixes querying the wrong interface with netlink, resulting in incorrect
SSIDs used for the nodes.

Signed-off-by: David Bauer <mail@david-bauer.net>
2021-09-21 19:46:05 +02:00

569 lines
14 KiB
C

/*
* 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);
uloop_timeout_cancel(&ln->req_timer);
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->update);
avl_delete(&local_nodes, &ln->node.avl);
ubus_unregister_subscriber(ctx, &ln->ev);
kvlist_free(&ln->node_info);
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");
for_each_local_node(node)
usteer_add_rrm_data(ln, node);
for_each_remote_node(rn)
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;
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);
kvlist_init(&ln->node_info, kvlist_blob_len);
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))
MSG(INFO, "failed to execute %s\n", val);
}
static void
usteer_check_node_enabled(struct usteer_local_node *ln)
{
bool ssid_disabled = config.ssid_list;
struct blob_attr *cur;
int rem;
blobmsg_for_each_attr(cur, config.ssid_list, rem) {
if (strcmp(blobmsg_get_string(cur), ln->node.ssid) != 0)
continue;
ssid_disabled = false;
break;
}
if (ln->node.disabled == ssid_disabled)
return;
ln->node.disabled = ssid_disabled;
if (ssid_disabled) {
MSG(INFO, "Disconnecting from local node %s\n", usteer_node_name(&ln->node));
usteer_local_node_state_reset(ln);
usteer_sta_node_cleanup(&ln->node);
uloop_timeout_cancel(&ln->update);
ubus_unsubscribe(ubus_ctx, &ln->ev, ln->obj_id);
return;
}
MSG(INFO, "Connecting to local node %s\n", usteer_node_name(&ln->node));
ubus_subscribe(ubus_ctx, &ln->ev, ln->obj_id);
uloop_timeout_set(&ln->update, 1);
usteer_node_run_update_script(&ln->node);
}
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, "Creating 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(ln->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);
list_for_each_entry(h, &node_handlers, list) {
if (!h->init_node)
continue;
h->init_node(&ln->node);
}
ln->node.disabled = true;
usteer_check_node_enabled(ln);
}
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;
struct usteer_node *node;
if (!data)
return;
val = blobmsg_get_string(data);
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);
for_each_local_node(node)
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 config_set_ssid_list(struct blob_attr *data)
{
struct usteer_local_node *ln;
free(config.ssid_list);
if (data && blobmsg_len(data))
config.ssid_list = blob_memdup(data);
else
config.ssid_list = NULL;
avl_for_each_element(&local_nodes, ln, node.avl)
usteer_check_node_enabled(ln);
}
void config_get_ssid_list(struct blob_buf *buf)
{
if (config.ssid_list)
blobmsg_add_blob(buf, config.ssid_list);
}
void
usteer_local_nodes_init(struct ubus_context *ctx)
{
usteer_register_events(ctx);
ubus_lookup(ctx, "hostapd.*", node_list_cb, NULL);
}