Files
wlan-ap/feeds/ucentral/usteer/files/usr/libexec/uchannel.uc
Matthew Hagan a4a7c1f9f3 usteer: uchannel.uc: check host_info exists
If another host has not set a status, for example when autochannel is
disabled, it will not show host_info when remote_hosts is called. This
fix adds a check for this condition.

Signed-off-by: Matthew Hagan <mnhagan88@gmail.com>
2022-05-05 09:50:35 +02:00

330 lines
6.6 KiB
Ucode
Executable File

#!/usr/bin/ucode
let fs = require("fs");
let ubus = require("ubus");
let conn = ubus.connect();
let phys = [];
let block_list = {
"2G": {},
"5G": {}
};
let channel_masks = {
"40": [ 5180, 5220, 5260, 5300, 5500, 5540, 5580, 5620, 5660, 5700, 5745, 5785, 5825 ],
"80": [ 5180, 5260, 5500, 5580, 5660, 5745 ],
"160": [ 5180, 5500, ],
};
function uptime_get() {
let info = conn.call("system", "info");
return info.uptime;
}
function remote_info() {
let info = conn.call("usteer", "remote_info");
return info || {};
}
function local_info() {
let info = conn.call("usteer", "local_info");
return info || {};
}
function remote_hosts() {
let hosts = conn.call("usteer", "remote_hosts");
return hosts || {};
}
let uptime = uptime_get();
let info = local_info();
let remote = remote_info();
let hosts = remote_hosts();
function state_get() {
let file = fs.open("/tmp/uchannel.json", "r");
let state = file ? json(file.read("all")) : {};
if (file)
file.close();
return state;
}
function state_set(state) {
let file = fs.open("/tmp/uchannel.json", "w");
state.executed = uptime;
file.write(state);
file.close();
conn.call("usteer", "set_node_data", {
node: "*",
data: {
status: state.status,
uptime: state.executed,
}
});
printf("entering %s state\n", state.status);
}
function freq2chan(freq) {
if (freq == 2484)
return 14;
else if (freq < 2484)
return (freq - 2407) / 5;
else if (freq >= 4910 && freq <= 4980)
return (freq - 4000) / 5;
else if(freq >= 56160 + 2160 * 1 && freq <= 56160 + 2160 * 6)
return (freq - 56160) / 2160;
else
return (freq - 5000) / 5;
}
function freq2band(freq) {
if (freq < 2500)
return "2G";
return "5G";
}
function chan2freq(band, channel) {
if (band == '2G' && channel >= 1 && channel <= 13)
return 2407 + channel * 5;
else if (band == '2G' && channel == 14)
return 2484;
else if (band == '5G' && channel >= 36 && channel <= 177)
return 5000 + channel * 5;
else if (band == '5G' && channel >= 183 && channel <= 196)
return 4000 + channel * 5;
else if (band == '60G' && channel >= 1 && channel <= 6)
return 56160 + channel * 2160;
return null;
}
function center_freq(freq, bandwidth) {
if (bandwidth == 40)
return +freq + 10;
if (bandwidth == 80)
return +freq + 30;
if (bandwidth == 160)
return +freq + 70;
return +freq;
}
function channel_overlap() {
let overlap = {};
let peers = {
"local": {}
};
for (let node, r in remote)
peers[split(node, "#")] = {};
for (let id, i in info) {
peers.local[i.freq] = true;
block_list[freq2band(freq)][i.freq] = 0;
}
for (let node, r in remote) {
peers[split(node, "#")][r.freq] = true;
block_list[freq2band(freq)][r.freq] = 0;
}
for (let id, peer in peers)
for (let freq, val in peer)
block_list[freq2band(freq)][freq]++;
for (let id, i in info)
for (let node, r in remote)
if (i.freq == r.freq) {
overlap[i.freq] = id;
break;
}
return overlap;
}
function phy_lookup() {
let status = conn.call("network.wireless", "status");
for (let id, radio in status) {
let htmode = match(radio.config.htmode, /^([A-Z]+)(.+)$/);
let phy = {
path: radio.config.path,
htmode: lc(htmode[1]),
bandwidth: htmode[2],
iface: [],
sta: false,
};
for (let i, iface in radio.interfaces) {
push(phy.iface, iface.ifname);
if (iface.config.mode != 'ap')
phy.sta = true;
}
push(phys, phy);
}
}
function phy_find(iface) {
for (let idx, phy in phys)
if (index(phy.iface, iface) >= 0)
return phy;
return {};
}
function channel_mask(band, bandwidth) {
if (band == "2G")
return [ 2412, 2437, 2462 ];
return channel_masks[bandwidth];
}
function channel_scan(band) {
conn.call("wifi", "scan", { band });
sleep(5000);
let survey_data = conn.call("wifi", "survey", { band });
let scan_data = conn.call("wifi", "scan_dump", { band });
let channels = {};
for (let survey in survey_data.survey) {
channels[survey.channel] = survey;
channels[survey.channel].bss = 0;
}
for (let scan in scan_data.scan)
channels[scan.channel].bss++;
return channels;
}
function channel_new(band, channels, mask) {
let new = [];
for (let chan, data in channels) {
if (data.in_use)
continue;
let freq = chan2freq(band, chan);
if (length(mask) && index(mask, freq) < 0)
continue;
if (block_list[band][freq])
continue;
push(new, {
freq,
bss: data.bss,
airtime: data.busy_ms * 100 / data.active_ms,
});
}
print("available free channels :" + new + "\n");
let best;
for (let id, data in new) {
if (!length(best))
best = data;
if (best.bss > data.bss)
best = data;
else if (best.bss == data.bss &&
best.airtime > data.airtime)
best = data;
}
return best || {};
}
function channel_balance(band, mask) {
let lowest = {
freq: mask[0],
count: 1000,
};
let highest = {
freq: mask[0],
count: 0,
};
for (let freq, count in block_list[band]) {
if (lowest.count > count)
lowest = { freq, count };
if (highest.count < count)
highest = { freq, count };
}
if (highest.count - lowest.cont >= 2)
return lowest;
return {};
}
function youngest() {
for (let ip, host in hosts) {
if (host.host_info?.status == "overlap" &&
host.host_info?.uptime < uptime) {
print("Found a younger host\n");
return 1;
}
}
print("We are the youngest host\n");
return 0;
}
let state = state_get();
if (state.status == "waiting" &&
(uptime - state.changed < (12 * 60 * 60))) {
state_set(state);
return;
}
phy_lookup();
print("discovered devices: " + phys + "\n");
let overlap = channel_overlap();
print("list of blocked channels: " + block_list + "\n");
print("list of overlapping channels: " + overlap + "\n");
if (!length(overlap)) {
state.status = "happy";
state_set(state);
return;
}
if (state.status != "overlap" || youngest()) {
state.status = "overlap";
state_set(state);
return;
}
for (let freq, obj in overlap) {
let phy = phy_find(split(obj, ".")[1]);
let band = freq2band(freq);
let channels = channel_scan(band);
let mask = channel_mask(band, phy.bandwidth);
let new;
if (phy.sta) {
print("phy has a STA interface cannot change channel\n");
return;
}
new = channel_new(band, channels, mask);
if (!length(new))
new = channel_balance(band, mask);
if (!length(new)) {
print("no alternative channel found\n");
continue;
}
printf("selected channel: " + new + " for %s\n", obj);
conn.call(obj, "switch_chan", {
freq: +new.freq,
center_freq1: center_freq(new.freq, phy.bandwidth),
bcn_count: 10,
force: true
});
}
state.status = "waiting";
state.changed = uptime;
state_set(state);