Reviewed-on: #36 Co-authored-by: Koda YeenBean <n122330@gmail.com>
This commit was merged in pull request #36.
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Shared translations and validators used by Wi-Fi provisioning and
|
||||
* telemetry. This unit does not commit controller configuration itself.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <uci.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
#include "wlan.h"
|
||||
#include "ufmodel.h"
|
||||
#include "crypto.h"
|
||||
#include "wlan_internal.h"
|
||||
|
||||
|
||||
const char *wlan_security_to_uci(const char *uf)
|
||||
{
|
||||
if (!uf || !strcmp(uf,"open")) return "none";
|
||||
if (!strcmp(uf,"wpapsk")) return "psk";
|
||||
if (!strcmp(uf,"wpa2psk")) return "psk2";
|
||||
if (!strcmp(uf,"wpapskwpa2psk")) return "psk-mixed";
|
||||
if (!strcmp(uf,"wpa3")) return "sae";
|
||||
if (!strcmp(uf,"wpa3transition")) return "sae-mixed";
|
||||
if (!strcmp(uf,"wpa2enterprise")) return "wpa2";
|
||||
if (!strcmp(uf,"wpa3enterprise")) return "wpa3";
|
||||
return "psk2"; /* default */
|
||||
}
|
||||
|
||||
/* OpenWrt UCI encryption names to UniFi security names. */
|
||||
const char *wlan_security_to_unifi(const char *uci)
|
||||
{
|
||||
if (!uci || !strcmp(uci,"none")) return "open";
|
||||
if (!strcmp(uci,"psk")) return "wpapsk";
|
||||
if (!strcmp(uci,"psk2")) return "wpa2psk";
|
||||
if (!strcmp(uci,"psk-mixed")) return "wpapskwpa2psk";
|
||||
if (!strcmp(uci,"sae")) return "wpa3";
|
||||
if (!strcmp(uci,"sae-mixed")) return "wpa3transition";
|
||||
if (!strcmp(uci,"wpa2")) return "wpa2enterprise";
|
||||
if (!strcmp(uci,"wpa3")) return "wpa3enterprise";
|
||||
return "wpa2psk";
|
||||
}
|
||||
|
||||
/* Return true only for the 24-character hexadecimal IDs used by UniFi. */
|
||||
int wlan_valid_object_id(const char *id)
|
||||
{
|
||||
if (!id || strlen(id) != 24)
|
||||
return 0;
|
||||
for (size_t i = 0; i < 24; i++)
|
||||
if (!((id[i] >= '0' && id[i] <= '9') ||
|
||||
(id[i] >= 'a' && id[i] <= 'f') ||
|
||||
(id[i] >= 'A' && id[i] <= 'F')))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
/*
|
||||
* UniFi uses a 24-character hexadecimal MongoDB ObjectId to associate VAP
|
||||
* telemetry and stations with a WLAN. Older legacy system_cfg payloads do
|
||||
* not include that ID, so allocate a local one and keep it stable in UCI.
|
||||
* All radio instances of the same WLAN must report the same ID.
|
||||
*/
|
||||
int wlan_ensure_vap_ids(struct json_object *vaps)
|
||||
{
|
||||
if (!vaps || !json_object_is_type(vaps, json_type_array))
|
||||
return 0;
|
||||
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
struct uci_package *pkg = NULL;
|
||||
if (ctx)
|
||||
uci_load(ctx, "wireless", &pkg);
|
||||
|
||||
int count = json_object_array_length(vaps);
|
||||
for (int i = 0; i < count; i++) {
|
||||
struct json_object *vap = json_object_array_get_idx(vaps, i);
|
||||
struct json_object *value;
|
||||
if (!vap)
|
||||
continue;
|
||||
|
||||
const char *id = NULL;
|
||||
const char *id_keys[] = { "_id", "id", "wlanconf_id" };
|
||||
for (size_t n = 0; n < sizeof(id_keys) / sizeof(id_keys[0]); n++) {
|
||||
if (json_object_object_get_ex(vap, id_keys[n], &value)) {
|
||||
const char *candidate = json_object_get_string(value);
|
||||
if (wlan_valid_object_id(candidate)) {
|
||||
id = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (id)
|
||||
continue;
|
||||
|
||||
const char *ssid = "";
|
||||
if (json_object_object_get_ex(vap, "essid", &value))
|
||||
ssid = json_object_get_string(value);
|
||||
|
||||
/* Reuse an ID already assigned to this WLAN in the same payload. */
|
||||
for (int j = 0; j < i && !id; j++) {
|
||||
struct json_object *previous = json_object_array_get_idx(vaps, j);
|
||||
struct json_object *previous_ssid;
|
||||
struct json_object *previous_id;
|
||||
if (previous &&
|
||||
json_object_object_get_ex(previous, "essid", &previous_ssid) &&
|
||||
!strcmp(json_object_get_string(previous_ssid), ssid) &&
|
||||
json_object_object_get_ex(previous, "id", &previous_id) &&
|
||||
wlan_valid_object_id(json_object_get_string(previous_id)))
|
||||
id = json_object_get_string(previous_id);
|
||||
}
|
||||
|
||||
/* Reuse the ID committed by an earlier provisioning cycle. */
|
||||
if (!id && pkg) {
|
||||
struct uci_element *element;
|
||||
uci_foreach_element(&pkg->sections, element) {
|
||||
struct uci_section *section = uci_to_section(element);
|
||||
const char *stored_ssid;
|
||||
const char *stored_id;
|
||||
if (strcmp(section->type, "wifi-iface") ||
|
||||
strncmp(section->e.name, "openuf_", 7))
|
||||
continue;
|
||||
stored_ssid = uci_lookup_option_string(ctx, section, "ssid");
|
||||
stored_id = uci_lookup_option_string(ctx, section,
|
||||
"openuf_vap_id");
|
||||
if (stored_ssid && !strcmp(stored_ssid, ssid) &&
|
||||
wlan_valid_object_id(stored_id)) {
|
||||
id = stored_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char generated[25];
|
||||
if (!id) {
|
||||
if (crypto_random_hex((unsigned char *)generated, 12) != 0) {
|
||||
printf("[openuf] Failed to generate a VAP ID for '%s'\n", ssid);
|
||||
if (pkg) uci_unload(ctx, pkg);
|
||||
if (ctx) uci_free_context(ctx);
|
||||
return -1;
|
||||
}
|
||||
id = generated;
|
||||
printf("[openuf] Generated persistent VAP ID %s for '%s'\n",
|
||||
id, ssid);
|
||||
}
|
||||
|
||||
json_object_object_add(vap, "id", json_object_new_string(id));
|
||||
json_object_object_add(vap, "wlanconf_id", json_object_new_string(id));
|
||||
}
|
||||
|
||||
if (pkg) uci_unload(ctx, pkg);
|
||||
if (ctx) uci_free_context(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Build one stable 802.11r mobility domain shared by every AP for an SSID. */
|
||||
void wlan_mobility_domain_for_ssid(const char *ssid, char out[5])
|
||||
{
|
||||
unsigned int hash = 2166136261u;
|
||||
const unsigned char *p = (const unsigned char *)(ssid ? ssid : "");
|
||||
while (*p) {
|
||||
hash ^= *p++;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
snprintf(out, 5, "%04x", (hash ^ (hash >> 16)) & 0xffffu);
|
||||
}
|
||||
|
||||
/* Return true when an OpenWrt radio is backed by the ath9k kernel driver. */
|
||||
int wlan_radio_uses_ath9k(const char *device_name)
|
||||
{
|
||||
int phy_index;
|
||||
if (!device_name || sscanf(device_name, "radio%d", &phy_index) != 1)
|
||||
return 0;
|
||||
|
||||
char path[128];
|
||||
char target[256];
|
||||
snprintf(path, sizeof(path),
|
||||
"/sys/class/ieee80211/phy%d/device/driver", phy_index);
|
||||
ssize_t length = readlink(path, target, sizeof(target) - 1);
|
||||
if (length < 0)
|
||||
return 0;
|
||||
target[length] = '\0';
|
||||
return strstr(target, "ath9k") != NULL;
|
||||
}
|
||||
|
||||
/* Read a UniFi boolean while accepting names used by controller versions. */
|
||||
int wlan_json_boolean_any(struct json_object *object,
|
||||
const char *const *keys, size_t key_count)
|
||||
{
|
||||
struct json_object *value;
|
||||
for (size_t i = 0; i < key_count; i++) {
|
||||
if (!json_object_object_get_ex(object, keys[i], &value))
|
||||
continue;
|
||||
if (json_object_is_type(value, json_type_string)) {
|
||||
const char *text = json_object_get_string(value);
|
||||
if (!text || !text[0] || !strcasecmp(text, "disabled") ||
|
||||
!strcasecmp(text, "false") || !strcasecmp(text, "off") ||
|
||||
!strcasecmp(text, "none") || !strcmp(text, "0"))
|
||||
return 0;
|
||||
/* Also accepts controller modes such as "prefer_5g". */
|
||||
return 1;
|
||||
}
|
||||
return json_object_get_boolean(value) ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Interpret Boolean text and UniFi feature modes such as "prefer_5g". */
|
||||
int wlan_feature_enabled(const char *text)
|
||||
{
|
||||
return text && text[0] && strcasecmp(text, "disabled") &&
|
||||
strcasecmp(text, "false") && strcasecmp(text, "off") &&
|
||||
strcasecmp(text, "none") && strcmp(text, "0");
|
||||
}
|
||||
|
||||
/* Safe UCI identifier fragment (maximum 15 characters). */
|
||||
void wlan_safe_section_name(const char *ssid, char *out, size_t sz)
|
||||
{
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; ssid[i] && j < sz-1 && j < 15; i++) {
|
||||
char c = ssid[i];
|
||||
if ((c>='a'&&c<='z')||(c>='A'&&c<='Z')||
|
||||
(c>='0'&&c<='9')||c=='_')
|
||||
out[j++] = c;
|
||||
else
|
||||
out[j++] = '_';
|
||||
}
|
||||
out[j] = '\0';
|
||||
}
|
||||
Reference in New Issue
Block a user