mirror of
https://dev.iopsys.eu/hal/wifimngr.git
synced 2025-12-20 01:11:11 +08:00
523 lines
12 KiB
C
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;
|
|
}
|
|
|
|
|