Files
wifimngr/uci.c
2025-12-15 10:06:25 +01:00

523 lines
12 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* uci.c - helper functions for UCI config file.
*
* Copyright (C) 2024 Iopsys Software Solutions AB. All rights reserved.
* Copyright (C) 2025 Genexis AB.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <limits.h>
#include <net/if.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <uci.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <easy/easy.h>
#include <wifi.h>
#include <wifidefs.h>
#include "wifimngr.h"
#include "debug.h"
static enum wifi_band uci_to_radio_band(const char *uci_band)
{
if (!uci_band)
return BAND_UNKNOWN;
if (!strcmp(uci_band, "2g"))
return BAND_2;
else if (!strcmp(uci_band, "5g"))
return BAND_5;
else if (!strcmp(uci_band, "6g"))
return BAND_6;
else
return BAND_UNKNOWN;
}
static void uci_add_option(struct uci_context *ctx, struct uci_package *p,
struct uci_section *s, const char *option,
void *value, bool is_list)
{
struct uci_ptr ptr = { 0 };
ptr.p = p;
ptr.s = s;
ptr.package = p->e.name;
ptr.section = s->e.name;
ptr.option = option;
ptr.target = UCI_TYPE_OPTION;
ptr.flags |= UCI_LOOKUP_EXTENDED;
ptr.value = (char *)value;
if (is_list)
uci_add_list(ctx, &ptr);
else
uci_set(ctx, &ptr);
}
static int uci_get_wifi_mld_netdev(const char *mld, char *netdev)
{
struct uci_context *ctx;
struct uci_element *e;
struct uci_ptr ptr;
char uci_path[128] = {};
ctx = uci_alloc_context();
if (!ctx)
return -1;
snprintf(uci_path, sizeof(uci_path), "wireless.%s.ifname", mld);
if (uci_lookup_ptr(ctx, &ptr, uci_path, true) != UCI_OK) {
uci_free_context(ctx);
return -1;
}
e = ptr.last;
if (e->type != UCI_TYPE_OPTION) {
uci_free_context(ctx);
return -1;
}
if (ptr.o->type != UCI_TYPE_STRING) {
uci_free_context(ctx);
return -1;
}
memset(netdev, 0, 15);
strncpy(netdev, ptr.o->v.string, 15);
uci_free_context(ctx);
return 0;
}
static enum wifi_band uci_get_iface_band(struct wifimngr *w, const char *device)
{
int i;
for (i = 0; i < w->num_wifi_device; i++) {
if (strcmp(device, w->wdev[i].device))
continue;
return w->wdev[i].band;
}
return BAND_UNKNOWN;
}
static bool uci_get_iface_mlo(struct wifimngr *w, const char *device)
{
int i;
for (i = 0; i < w->num_wifi_device; i++) {
if (strcmp(device, w->wdev[i].device))
continue;
return w->wdev[i].mlo;
}
return false;
}
int uci_add_wifi_iface(char **argv)
{
struct uci_context *ctx;
struct uci_package *pkg;
struct uci_section *sec;
struct uci_element *e;
struct uci_option *op;
char *ifname = NULL;
bool found = false;
int i = 0;
ctx = uci_alloc_context();
if (!ctx)
return -1;
if (uci_load(ctx, "wireless", &pkg)) {
uci_free_context(ctx);
return -1;
}
while (argv[i]) {
if (!strcmp(argv[i], "ifname")) {
ifname = argv[i + 1];
break;
}
i += 2;
}
if (ifname) {
uci_foreach_element(&pkg->sections, e) {
struct uci_element *x, *tmp;
sec = uci_to_section(e);
if (strcmp(sec->type, "wifi-iface"))
continue;
uci_foreach_element_safe(&sec->options, tmp, x) {
op = uci_to_option(x);
if (op->type == UCI_TYPE_STRING &&
!strncmp(x->name, "ifname", 6) &&
!strncmp(op->v.string, ifname, 15)) {
found = true;
break;
}
}
if (found) {
wifimngr_warn("%s %s already added\n", __func__, ifname);
break;
}
}
}
if (!found)
uci_add_section(ctx, pkg, "wifi-iface", &sec);
i = 0;
while (argv[i]) {
uci_add_option(ctx, pkg, sec, argv[i], argv[i + 1], false);
i += 2;
}
uci_commit(ctx, &pkg, false);
uci_free_context(ctx);
return 0;
}
int uci_del_wifi_iface(char *ifname)
{
struct uci_context *ctx;
struct uci_package *pkg;
struct uci_element *e;
struct uci_option *op;
bool found = false;
ctx = uci_alloc_context();
if (!ctx)
return -1;
if (uci_load(ctx, "wireless", &pkg)) {
uci_free_context(ctx);
return -1;
}
uci_foreach_element(&pkg->sections, e) {
struct uci_section *s = uci_to_section(e);
struct uci_element *x, *tmp;
if (strcmp(s->type, "wifi-iface"))
continue;
uci_foreach_element_safe(&s->options, tmp, x) {
op = uci_to_option(x);
if (op->type == UCI_TYPE_STRING &&
!strncmp(x->name, "ifname", 6) &&
!strncmp(op->v.string, ifname, 15)) {
found = true;
break;
}
}
if (found) {
struct uci_ptr ptr = { 0 };
ptr.p = pkg;
ptr.s = s;
ptr.package = pkg->e.name;
ptr.section = s->e.name;
ptr.option = NULL;
ptr.target = UCI_TYPE_SECTION;
ptr.flags |= UCI_LOOKUP_EXTENDED;
ptr.value = "wifi-iface";
uci_delete(ctx, &ptr);
uci_commit(ctx, &pkg, false);
break;
}
}
uci_free_context(ctx);
return found ? 0 : -1;
}
int uci_get_wifi_devices(struct wifimngr *w, const char *conffile)
{
struct uci_context *ctx;
struct uci_package *pkg;
struct uci_element *e;
ctx = uci_alloc_context();
if (!ctx)
return -1;
if (uci_load(ctx, conffile, &pkg)) {
uci_free_context(ctx);
return -1;
}
uci_foreach_element(&pkg->sections, e) {
struct uci_section *s = uci_to_section(e);
if (!strcmp(s->type, "wifi-device")) {
struct uci_element *x;
struct uci_option *op;
//int idx;
//FIXME
//idx = wifimngr_lookup_wifi_device(w, s->e.name);
strncpy(w->wdev[w->num_wifi_device].device, s->e.name, 15);
strncpy(w->wdev[w->num_wifi_device].phy, s->e.name, 15);
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "path", 4) &&
op->type == UCI_TYPE_STRING) {
char phy[16] = {0};
int ret;
ret = find_phy_from_device_path(op->v.string, phy, sizeof(phy) - 1);
if (!ret) {
memset(w->wdev[w->num_wifi_device].phy, 0,
sizeof(w->wdev[w->num_wifi_device].phy));
strncpy(w->wdev[w->num_wifi_device].phy, phy,
sizeof(w->wdev[w->num_wifi_device].phy));
}
} else if (!strncmp(x->name, "macaddr", 7) &&
op->type == UCI_TYPE_STRING) {
uint8_t macaddr[6] = {0};
if (hwaddr_aton(op->v.string, macaddr) && !hwaddr_is_zero(macaddr))
memcpy(w->wdev[w->num_wifi_device].macaddr, macaddr, 6);
wifimngr_dbg("%s: device = %s, macaddr = " MACFMT"\n", __func__,
w->wdev[w->num_wifi_device].device,
MAC2STR(w->wdev[w->num_wifi_device].macaddr));
}
}
/* Get band */
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "band", 4) &&
op->type == UCI_TYPE_STRING) {
w->wdev[w->num_wifi_device].band = uci_to_radio_band(op->v.string);
}
}
/* Get disabled */
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "disabled", 8) &&
op->type == UCI_TYPE_STRING) {
w->wdev[w->num_wifi_device].disabled = atoi(op->v.string);
}
}
/* Get mlo_capable */
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "mlo_capable", 11) &&
op->type == UCI_TYPE_STRING) {
w->wdev[w->num_wifi_device].mlo_capable = atoi(op->v.string);
} else if (!strncmp(x->name, "mlo", 3) &&
op->type == UCI_TYPE_STRING) {
w->wdev[w->num_wifi_device].mlo = atoi(op->v.string);
}
}
/* Get country */
memset(w->wdev[w->num_wifi_device].country, 0, sizeof(w->wdev[w->num_wifi_device].country));
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "country", 7) &&
op->type == UCI_TYPE_STRING) {
strncpy(w->wdev[w->num_wifi_device].country, op->v.string, 2);
}
}
w->num_wifi_device++;
if (w->num_wifi_device == WIFI_DEV_MAX_NUM)
break;
}
}
uci_free_context(ctx);
return w->num_wifi_device;
}
int uci_get_wifi_interfaces(struct wifimngr *w, const char *conffile)
{
struct uci_context *ctx;
struct uci_package *pkg;
struct uci_element *e;
ctx = uci_alloc_context();
if (!ctx)
return -1;
if (uci_load(ctx, conffile, &pkg)) {
uci_free_context(ctx);
return -1;
}
uci_foreach_element(&pkg->sections, e) {
struct uci_section *s = uci_to_section(e);
if (!strcmp(s->type, "wifi-iface")) {
struct uci_element *x;
struct uci_option *op;
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "ifname", 6) &&
op->type == UCI_TYPE_STRING) {
strncpy(w->ifs[w->num_wifi_iface].iface, op->v.string, 15);
} else if (!strncmp(x->name, "device", 6) &&
op->type == UCI_TYPE_STRING) {
strncpy(w->ifs[w->num_wifi_iface].device, op->v.string, 15);
w->ifs[w->num_wifi_iface].band = uci_get_iface_band(w, w->ifs[w->num_wifi_iface].device);
w->ifs[w->num_wifi_iface].mlo = uci_get_iface_mlo(w, w->ifs[w->num_wifi_iface].device);
} else if (!strncmp(x->name, "disabled", 8) &&
op->type == UCI_TYPE_STRING) {
w->ifs[w->num_wifi_iface].disabled = !!atoi(op->v.string);
} else if (!strncmp(x->name, "mode", 4) &&
op->type == UCI_TYPE_STRING) {
if (!strncmp(op->v.string, "ap", 2))
w->ifs[w->num_wifi_iface].mode = WIFI_MODE_AP;
else if (!strncmp(op->v.string, "wet", 3))
w->ifs[w->num_wifi_iface].mode = WIFI_MODE_STA;
else if (!strncmp(op->v.string, "sta", 3))
w->ifs[w->num_wifi_iface].mode = WIFI_MODE_STA;
else
w->ifs[w->num_wifi_iface].mode = WIFI_MODE_UNKNOWN;
} else if (!strncmp(x->name, "mld", 3) &&
op->type == UCI_TYPE_STRING) {
/* Check if radio/device MLO enabled */
if (uci_get_iface_mlo(w, w->ifs[w->num_wifi_iface].device)) {
strncpy(w->ifs[w->num_wifi_iface].mld, op->v.string, 15);
uci_get_wifi_mld_netdev(w->ifs[w->num_wifi_iface].mld,
w->ifs[w->num_wifi_iface].mld_netdev);
}
}
}
if (strlen(w->ifs[w->num_wifi_iface].iface) == 0)
snprintf(w->ifs[w->num_wifi_iface].iface, 16, "idx%d", w->num_wifi_iface);
w->num_wifi_iface++;
if (w->num_wifi_iface == WIFI_IF_MAX_NUM)
break;
}
}
uci_free_context(ctx);
return w->num_wifi_iface;
}
static enum wifi_mode uci_get_mld_mode_from_iface(struct wifimngr *w, char *mldname)
{
enum wifi_mode mode = WIFI_MODE_UNKNOWN;
int i;
for (i = 0; i < w->num_wifi_iface; i++) {
if (!strlen(w->ifs[i].mld))
continue;
if (strcmp(mldname, w->ifs[i].mld_netdev))
continue;
mode = w->ifs[i].mode;
break;
}
return mode;
}
int uci_get_wifi_mlds(struct wifimngr *w, const char *conffile)
{
struct uci_context *ctx;
struct uci_package *pkg;
struct uci_element *e;
ctx = uci_alloc_context();
if (!ctx)
return -1;
if (uci_load(ctx, conffile, &pkg)) {
uci_free_context(ctx);
return -1;
}
uci_foreach_element(&pkg->sections, e) {
struct uci_section *s = uci_to_section(e);
if (!strcmp(s->type, "wifi-mld")) {
struct wifimngr_mld *mld;
struct uci_element *x;
struct uci_option *op;
mld = &w->mld[w->num_wifi_mld];
uci_foreach_element(&s->options, x) {
op = uci_to_option(x);
if (!strncmp(x->name, "ifname", 6) &&
op->type == UCI_TYPE_STRING) {
strncpy(mld->ifname, op->v.string, 15);
}
if (!strncmp(x->name, "mode", 4) &&
op->type == UCI_TYPE_STRING) {
if (!strcmp(op->v.string, "sta"))
mld->mode = WIFI_MODE_STA;
else if (!strcmp(op->v.string, "ap"))
mld->mode = WIFI_MODE_AP;
else
mld->mode = WIFI_MODE_UNKNOWN;
}
}
if (strlen(mld->ifname) == 0)
snprintf(mld->ifname, 16, "mldif.%d", w->num_wifi_mld);
/* If not set directly in mld, fallback to wifi-iface */
if (mld->mode == WIFI_MODE_UNKNOWN)
mld->mode = uci_get_mld_mode_from_iface(w, mld->ifname);
w->num_wifi_mld++;
if (w->num_wifi_mld == WIFI_MLD_MAX_NUM)
break;
}
}
uci_free_context(ctx);
return w->num_wifi_mld;
}