[OLS-545] Removal of AP specific schema elements from ols-ucentral-schema

Remove the AP specific elements of the schema (configuration), and state (reporting)
Remove the ucode files that are not used
Regenerate the json and documentation files to reflect schema and state changes

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
This commit is contained in:
Mike Hansen
2025-01-09 09:51:36 -05:00
parent a03b5620c5
commit 0ed83ba0a5
153 changed files with 2862 additions and 21541 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
/jsdoc.conf.json
docs/
node_modules

View File

@@ -1,90 +0,0 @@
#!/usr/bin/ucode
push(REQUIRE_SEARCH_PATH, '/usr/share/ucentral/*.uc');
let fs = require("fs");
let uci = require("uci");
let ubus = require("ubus");
let capabfile = fs.open("/etc/ucentral/capabilities.json", "r");
let capab = json(capabfile.read("all"));
let pipe = fs.popen('fw_printenv developer');
let developer = replace(pipe.read("all"), '\n', '');
pipe.close();
let restrict = {};
if (developer != 'developer=1') {
let restrictfile = fs.open("/etc/ucentral/restrictions.json", "r");
restrict = restrictfile ? json(restrictfile.read("all")) : {};
}
let cmdfile = fs.open(ARGV[0], "r");
let cmd = json(cmdfile.read("all"));
let id = ARGV[1];
let ctx = ubus.connect();
if (!ctx) {
warn("Unable to connect to ubus: " + ubus.error() + "\n");
exit(1);
}
/* Convenience logger outputting to both stderr and remote central log */
function log(fmt, ...args) {
let msg = sprintf(fmt, ...args);
warn(msg + "\n");
ctx.call("ucentral", "log", { msg: msg });
}
function result(code, fmt, ...args) {
let text = sprintf(fmt, ...args);
ctx.call("ucentral", "result", {
"status": {
"error": code,
"text": text
}, "id": +id
});
warn(text + "\n");
}
function result_json(status) {
ctx.call("ucentral", "result", {"id": +id, "status": status});
if (status.text)
warn(status.text + "\n");
if (status.resultText)
warn(status.resultText + "\n");
}
/* Scope of functions and ressources the command includes have access to */
let scope = {
/* ressources */
uci,
cursor: uci.cursor(),
ctx,
fs,
restrict,
/* log helper */
log,
/* result helpers */
result,
result_json,
/* command argument object */
args: (cmd.payload || {}),
/* cmd id */
id: (id || 0)
};
if (match(cmd.command, /^[A-Za-z0-9_]+$/)) {
try {
include(sprintf("cmd_%s.uc", cmd.command), scope);
}
catch (e) {
log("Exception invoking '%s' command module: %s\n%s\n",
cmd.cmd, e, e.stacktrace[0].context);
}
}
else {
log("Invalid command module name specified");
}

View File

@@ -1,29 +0,0 @@
let tar = b64dec(args.certificates);
if (!tar || !fs.writefile('/tmp/certs.tar', tar)) {
result_json({
"error": 2,
"text": 'failed to extract certificates'
});
return;
}
if (system('/sbin/certupdate')) {
result_json({
"error": 2,
"text": 'failed to update certificates'
});
return;
}
include('reboot_cause.uc', { reason: 'certupdate' });
ctx.call("ucentral", "result", {
"status": {
"error": 0,
"text": 'Success'
}, "id": +id
});
sleep(5000);
system("(sleep 10; jffs2reset -y -r)&");
system("/etc/init.d/ucentral stop");

View File

@@ -1,50 +0,0 @@
let reset_cmdline = [ 'jffs2reset', '-r', '-y' ];
if (length(args) && args.keep_redirector) {
let archive_cmdline = [
'tar', 'czf', '/sysupgrade.tgz',
"/etc/config/ucentral"
];
let files = [
"/etc/ucentral/cas.pem", "/etc/ucentral/cert.pem",
"/etc/ucentral/gateway.json", "/etc/ucentral/dev-id",
"/etc/ucentral/key.pem", "/etc/ucentral/profile.json"
];
for (let f in files)
if (fs.stat(f))
push(archive_cmdline, f);
let active_config = fs.readlink("/etc/ucentral/ucentral.active");
if (active_config)
push(archive_cmdline, '/etc/ucentral/ucentral.active', active_config);
else
result_json({
"error": 2,
"text": sprintf("Unable to determine active configuration: %s", fs.error())
});
let rc = system(archive_cmdline);
if (rc != 0) {
result_json({
"error": 2,
"text": sprintf("Archive command %s exited with non-zero code %d", archive_cmdline, rc)
});
return;
}
push(reset_cmdline, '-k');
}
include('reboot_cause.uc', { reason: 'factory' });
let rc = system(reset_cmdline);
if (rc != 0)
result_json({
"error": 2,
"text": sprintf("Reset command %s exited with non-zero code %d", reset_cmdline, rc)
});

View File

@@ -1,15 +0,0 @@
function set_led(val, path)
{
let cursor = uci.cursor(path);
cursor.set("system", "@system[-1]", "leds_off", val);
cursor.commit();
}
let val = 0;
if (args.pattern == "off")
val = 1;
set_led(val);
set_led(val, "/etc/config-shadow/");
system("/etc/init.d/led restart");

View File

@@ -1,15 +0,0 @@
let rc = system(args.command);
if (rc != 0) {
result_json({
"error": 2,
"text": "Command returned an error",
"resultCode": rc
});
return;
}
result_json({
"error": 0,
"text": "Command was executed"
});

View File

@@ -1,11 +0,0 @@
log("Initiating reboot");
include('reboot_cause.uc', { reason: 'reboot' });
system("(sleep 10; reboot)&");
system("/etc/init.d/ucentral stop");
let err = ctx.error();
if (err != null)
result(2, "Reboot call failed with status %s", err);

View File

@@ -1,66 +0,0 @@
function log(msg) {
system('logger RRM: ' + msg );
}
let handlers = {
// ubus call usteer2 command '{"action": "kick", "addr": "1c:57:dc:37:3c:b1", "reason": 5, "ban_time": 30 }'
kick: function(params) {
if (!params.addr)
return false;
params.reason ??= 5;
params.ban_time ??= 30;
return true;
},
// ubus call usteer2 command '{"action": "beacon_request", "addr": "4e:7f:3e:2c:8a:68", "channel": 36 }'
// ubus call usteer2 command '{"action": "beacon_request", "addr": "4e:7f:3e:2c:8a:68", "ssid": "Cockney" }'
beacon_request: function(params) {
if (!params.addr)
return false;
return true;
},
// ubus call usteer2 command '{"action": "channel_switch", "bssid": "34:eF:b6:aF:48:b1", "params": "channel": 4, "band": "2G"}'
channel_switch: function(params) {
if (!params.bssid || !params.channel)
return false;
return true;
},
// ubus call usteer2 command '{"action": "tx_power", "bssid": "34:eF:b6:aF:48:b1", "level": 20 }'
tx_power: function(params) {
if (!params.bssid || !params.level)
return false;
return true;
},
// ubus call usteer2 command '{"action": "bss_transition", "addr": "4e:7f:3e:2c:8a:68", "params": "neighbors": ["34:ef:b6:af:48:b1"] }'
bss_transition: function(params) {
if (!params.addr || !params.neighbors)
return false;
for (let neighbor in params.neighbors)
if (type(neighbor) != 'string')
return false;
return true;
},
// ubus call usteer2 command '{"action": "neighbors", "neighbors": [ [ "00:11:22:33:44:55", "OpenWifi", "34efb6af48b1af4900005301070603010300" ], [ "aa:bb:cc:dd:ee:ff", "OpenWifi2", "34efb6af48b1af4900005301070603010300" ] ] }'
neighbors: function(params) {
if (!params.neighbors)
return false;
return true;
}
};
if (type(args.actions) != 'array')
return;
for (let action in args.actions) {
if (type(action) != 'object')
continue;
if (!handlers[action.action] || !handlers[action.action](action))
continue;
action.event = true;
let result = ctx.call('rrm', 'command', action);
log(result);
}

View File

@@ -1,35 +0,0 @@
if (!args.id || !args.server || !args.port || !args.token || !args.timeout) {
result_json({
"error": 2,
"text": "Invalid parameters.",
"resultCode": -1
});
return;
}
if (restrict.rtty) {
result_json({
"error": 2,
"text": "RTTY is restricted.",
"resultCode": -1
});
return;
}
cursor.load("rtty");
cursor.set("rtty", "@rtty[-1]", "enable", 1);
cursor.set("rtty", "@rtty[-1]", "id", args.id);
cursor.set("rtty", "@rtty[-1]", "host", args.server);
cursor.set("rtty", "@rtty[-1]", "port", args.port);
cursor.set("rtty", "@rtty[-1]", "token", args.token);
cursor.set("rtty", "@rtty[-1]", "timeout", args.timeout);
cursor.commit();
system("/etc/init.d/rtty restart");
result_json({
"error": 0,
"text": "Command was executed"
});

View File

@@ -1,93 +0,0 @@
let uloop = require('uloop');
let fs = require('fs');
let result;
let abort;
let signature = require('signature');
if (args.type == 'diagnostic') {
system('cp /usr/share/ucentral/diagnostic.uc /tmp/script.cmd');
} else {
let decoded = b64dec(args.script);
if (!decoded) {
result_json({
"error": 2,
"result": "invalid base64"
});
return;
}
let script = fs.open("/tmp/script.cmd", "w");
script.write(decoded);
script.close();
fs.chmod("/tmp/script.cmd", 700);
}
if (args.type != 'diagnostic' &&
restrict.commands &&
!signature.verify("/tmp/script.cmd", args.signature)) {
result_json({
"error": 3,
"result": "invalid signature"
});
return;
}
let out = '';
if (args.uri) {
result_json({ error: 0, result: 'pending'});
out = `/tmp/bundle.${id}.tar.gz`;
}
uloop.init();
let t = uloop.task(
function(pipe) {
switch (args.type) {
case 'diagnostic':
case 'bundle':
let bundle = require('bundle');
bundle.init(id);
try {
include('/tmp/script.cmd', { bundle });
} catch(e) {
//e.stacktrace[0].context
};
bundle.complete();
return;
default:
let stdout = fs.popen("/tmp/script.cmd " + out);
let result = stdout.read("all");
let error = stdout.close();
return { result, error };
}
},
function(res) {
result = res;
uloop.end();
}
);
if (args.timeout)
uloop.timer(args.timeout * 1000, function() {
t.kill();
uloop.end();
abort = true;
});
uloop.run();
if (abort)
result = {
"error": 255,
"result": "timed out"
};
if (args.uri && !fs.stat(out)) {
result_json({ error: 1,
result: 'script did not generate any output'});
} else if (args.uri) {
ctx.call("ucentral", "upload", {file: out, uri: args.uri, uuid: args.serial});
result_json({ error: 0,
result: 'done'});
} else
result_json(result || { result: 255, error: 'unknown'});

View File

@@ -1 +0,0 @@
include("./state.uc", { stats: args.stats });

View File

@@ -1,20 +0,0 @@
let log_data = ctx.call("log", "read", {
lines: +args.lines || 100,
oneshot: true,
stream: false
});
if (!log_data || !log_data.log) {
result(2, "Unable to obtain system log contents: %s", ubus.error());
return;
}
warn(sprintf("Read %d lines\n", length(log_data.log)));
result_json({
"error": 0,
"text": "Success",
"resultCode": 0,
"resultData": log_data
});

View File

@@ -1,65 +0,0 @@
let serial = cursor.get("ucentral", "config", "serial");
if (!serial)
return;
if (args.network) {
let net = ctx.call("network.interface", "status", { interface: args.network });
if (net && net.l3_device)
args.interface = net.l3_device;
}
if (!args.interface || !length(args.interface))
args.interface = args.network;
if (!match(args.interface, /^[^\/]+$/) || (args.interface != "any" && !fs.stat("/sys/class/net/" + args.interface))) {
result_json({
"error": 1,
"text": "Failed",
"resultCode": 1,
"resultText": "Invalid network device specified"
});
return;
}
let duration = +args.duration || 0;
let packets = +args.packets || 0;
let filename = sprintf("/tmp/pcap-%s-%d", serial, time());
let sys = ctx.call('system', 'info');
let command = [
'tcpdump_timeout',
duration,
'-C', sys.memory.free / 4,
'-W', '1',
'-w', filename,
'-i', args.interface
];
if (!duration) {
push(command, '-c');
push(command, packets);
}
let rc = system(command);
if (rc != 0) {
result_json({
"error": 1,
"text": "Failed",
"resultCode": rc,
"resultText": "tcpdump command exited with non-zero code"
});
return;
}
ctx.call("ucentral", "upload", {file: filename, uri: args.uri, uuid: args.serial});
result_json({
"error": 0,
"text": "Success",
"resultCode": 0,
"resultText": "Uploading file"
});

View File

@@ -1,20 +0,0 @@
log("Initiating gateway transfer");
if (!args.server || !args.port) {
result(2, "invalid arguments");
return;
}
fs.writefile('/etc/ucentral/gateway.json', { server: args.server, port: args.port });
system('cp /etc/ucentral/ucentral.cfg.0000000001 /etc/ucentral/ucentral.cfg.0000000002');
system('rm /etc/ucentral/ucentral.cfg.1* /etc/ucentral/ucentral.active');
include('reboot_cause.uc', { reason: 'transfer' });
system("(sleep 10; reboot)&");
system("/etc/init.d/ucentral stop");
let err = ctx.error();
if (err != null)
result(2, "Reboot call failed with status %s", err);

View File

@@ -1,91 +0,0 @@
let image_path = "/tmp/ucentral.upgrade";
if (!args.uri) {
result(2, "No firmware URL provided");
return;
}
let download_cmdline = [ 'wget', '-O', image_path, args.uri ];
let rc = system(download_cmdline);
if (rc != 0) {
result(2, "Download command %s exited with non-zero code %d", download_cmdline, rc);
return;
}
let validation_result = ctx.call("system", "validate_firmware_image", { path: image_path });
if (!validation_result) {
result(2, "Validation call failed with status %s", ubus.error());
return;
}
else if (!validation_result.valid) {
result_json({
"error": 2,
"text": "Firmware image validation failed",
"data": sprintf("Archive command %s exited with non-zero code %d", archive_cmdline, rc)
});
warn(sprintf("ucentral-upgrade: firmware file validation failed: %J\n", validation_result));
return;
}
if (restrict.sysupgrade) {
let signature = require('signature');
if (!signature.verify(image_path, args.signature)) {
result_json({
"error": 2,
"text": "Invalid signature",
"resultCode": -1
});
return;
}
}
let archive_cmdline = [
'tar', 'czf', '/upgrade.tgz',
'/etc/config/ucentral'
];
let files = [
"/etc/ucentral/cas.pem", "/etc/ucentral/cert.pem",
"/etc/ucentral/redirector.json", "/etc/ucentral/dev-id",
"/etc/ucentral/key.pem", "/etc/ucentral/gateway.json",
"/etc/ucentral/profile.json", "/etc/ucentral/restrictions.json",
];
for (let f in files)
if (fs.stat(f))
push(archive_cmdline, f);
if (args.keep_redirector) {
let active_config = fs.readlink("/etc/ucentral/ucentral.active");
if (active_config)
push(archive_cmdline, '/etc/ucentral/ucentral.active', active_config);
else
result(2, "Unable to determine active configuration: %s", fs.error());
}
let rc = system(archive_cmdline);
if (rc != 0) {
result(2, "Archive command %s exited with non-zero code %d", archive_cmdline, rc);
return;
}
include('reboot_cause.uc', { reason: 'upgrade' });
let sysupgrade_cmdline = sprintf("sysupgrade %s %s",
args.keep_redirector ? "-f /upgrade.tgz" : "-n",
image_path);
warn("Upgrading firmware\n");
system("touch /ucentral.upgrade");
system("(sleep 10; /etc/init.d/network stop; " + sysupgrade_cmdline + ")&");
system("/etc/init.d/ucentral stop");

View File

@@ -1,285 +0,0 @@
let verbose = args?.verbose == null ? true : args.verbose;
let active = args?.active ? true : false;
let bandwidth = args?.bandwidth || 0;
let override_dfs = args?.override_dfs ? true : false;
let nl = require("nl80211");
let rtnl = require("rtnl");
let def = nl.const;
if (!ctx) {
ubus = require("ubus");
ctx = ubus.connect();
}
const SCAN_FLAG_AP = (1<<2);
const frequency_list_2g = [ 2412, 2417, 2422, 2427, 2432, 2437, 2442,
2447, 2452, 2457, 2462, 2467, 2472, 2484 ];
const frequency_list_5g = { '3': [ 5180, 5260, 5500, 5580, 5660, 5745 ],
'2': [ 5180, 5220, 5260, 5300, 5500, 5540,
5580, 5620, 5660, 5745, 5785, 5825,
5865, 5920, 5960 ],
'1': [ 5180, 5200, 5220, 5240, 5260, 5280,
5300, 5320, 5500, 5520, 5540, 5560,
5580, 5600, 5620, 5640, 5660, 5680,
5700, 5720, 5745, 5765, 5785, 5805,
5825, 5845, 5865, 5885 ],
};
const frequency_offset = { '80': 30, '40': 10 };
const frequency_width = { '80': 3, '40': 2, '20': 1 };
const IFTYPE_STATION = 2;
const IFTYPE_AP = 3;
const IFTYPE_MESH = 7;
const IFF_UP = 1;
function frequency_to_channel(freq) {
/* see 802.11-2007 17.3.8.3.2 and Annex J */
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 < 5935) /* DMG band lower limit */
return (freq - 5000) / 5;
else if (freq == 5935)
return 2;
else if (freq >= 5955 && freq <= 7115)
return ((freq - 5955) / 5) + 1;
else if (freq >= 58320 && freq <= 64800)
return (freq - 56160) / 2160;
return 0;
}
function iface_get(wdev) {
let params = { dev: wdev };
let res = nl.request(def.NL80211_CMD_GET_INTERFACE, wdev ? null : def.NLM_F_DUMP, wdev ? params : null);
if (res === false)
warn("Unable to lookup interface: " + nl.error() + "\n");
return res || [];
}
function iface_find(wiphy, types, ifaces) {
if (!ifaces)
ifaces = iface_get();
for (let iface in ifaces) {
if (iface.wiphy != wiphy)
continue;
if (iface.iftype in types)
return iface;
}
return;
}
function scan_trigger(wdev, frequency, width) {
let params = { dev: wdev, scan_flags: SCAN_FLAG_AP };
if (frequency && type(frequency) == 'array') {
params.scan_frequencies = frequency;
}
else if (frequency && width) {
params.wiphy_freq = frequency;
params.center_freq1 = frequency + frequency_offset[width];
params.channel_width = frequency_width[width];
}
if (active)
params.scan_ssids = [ '' ];
//printf("%.J\n", params);
let res = nl.request(def.NL80211_CMD_TRIGGER_SCAN, 0, params);
if (res === false)
die("Unable to trigger scan: " + nl.error() + "\n");
else
res = nl.waitfor([
def.NL80211_CMD_NEW_SCAN_RESULTS,
def.NL80211_CMD_SCAN_ABORTED
], (frequency && width) ? 500 : 5000);
if (!res)
warn("Netlink error while awaiting scan results: " + nl.error() + "\n");
else if (res.cmd == def.NL80211_CMD_SCAN_ABORTED)
warn("Scan aborted by kernel\n");
}
function trigger_scan_width(wdev, freqs, width) {
for (let freq in freqs)
scan_trigger(wdev, freq, width);
}
function phy_get(wdev) {
let res = nl.request(def.NL80211_CMD_GET_WIPHY, def.NLM_F_DUMP, { split_wiphy_dump: true });
if (res === false)
warn("Unable to lookup phys: " + nl.error() + "\n");
return res;
}
function phy_get_frequencies(phy) {
let freqs = [];
for (let band in phy.wiphy_bands) {
for (let freq in band?.freqs || [])
if (!freq.disabled)
push(freqs, freq.freq);
}
return freqs;
}
function phy_frequency_dfs(phy, curr) {
let freqs = [];
for (let band in phy.wiphy_bands) {
for (let freq in band?.freqs || [])
if (freq.freq == curr && freq.dfs_state >= 0)
return true;
}
return false;
}
let phys = phy_get();
let ifaces = iface_get();
function intersect(list, filter) {
let res = [];
for (let item in list)
if (index(filter, item) >= 0)
push(res, item);
return res;
}
function wifi_scan() {
let scan = [];
for (let phy in phys) {
let iface = iface_find(phy.wiphy, [ IFTYPE_STATION, IFTYPE_AP ], ifaces);
let scan_iface = false;
if (!iface) {
warn('no valid interface found for phy' + phy.wiphy + '\n');
nl.request(def.NL80211_CMD_NEW_INTERFACE, 0, { wiphy: phy.wiphy, ifname: 'scan', iftype: IFTYPE_STATION });
nl.waitfor([ def.NL80211_CMD_NEW_INTERFACE ], 1000);
scan_iface = true;
iface = {
dev: 'scan',
channel_width: 1,
};
rtnl.request(rtnl.const.RTM_NEWLINK, 0, { dev: 'scan', flags: IFF_UP, change: 1});
sleep(1000);
}
printf("scanning on phy%d\n", phy.wiphy);
let freqs = phy_get_frequencies(phy);
if (length(intersect(freqs, frequency_list_2g)))
scan_trigger(iface.dev, frequency_list_2g);
let ch_width = iface.channel_width;
if (frequency_width[bandwith])
ch_width = frequency_width[bandwith];
let freqs_5g = intersect(freqs, frequency_list_5g[ch_width]);
if (length(freqs_5g)) {
if (override_dfs && !scan_iface && phy_frequency_dfs(phy, iface.wiphy_freq)) {
ctx.call(sprintf('hostapd.%s', iface.dev), 'switch_chan', { freq: 5180, bcn_count: 10 });
sleep(2000)
}
trigger_scan_width(iface.dev, freqs_5g, ch_width);
}
let res = nl.request(def.NL80211_CMD_GET_SCAN, def.NLM_F_DUMP, { dev: iface.dev });
for (let bss in res) {
bss = bss.bss;
let res = {
bssid: bss.bssid,
frequency: +bss.frequency,
channel: frequency_to_channel(+bss.frequency),
signal: +bss.signal_mbm / 100,
};
if (verbose) {
res.tsf = +bss.tsf;
res.last_seen = +bss.seen_ms_ago;
res.capability = +bss.capability;
res.ies = [];
}
for (let ie in bss.beacon_ies) {
switch (ie.type) {
case 0:
res.ssid = ie.data;
break;
case 114:
if (verbose)
res.meshid = ie.data;
break;
case 0x3d:
if (verbose)
res.ht_oper = b64enc(ie.data);
break;
case 0xc0:
if (verbose)
res.vht_oper = b64enc(ie.data);
break;
case 0xdd:
let oui = hexenc(substr(ie.data, 0, 3));
let type = ord(ie.data, 3);
let data = substr(ie.data, 4);
switch (oui) {
case '48d017':
res.tip_oui = true;
switch(type) {
case 1:
if (data)
res.tip_name = data;
break;
case 2:
if (data)
res.tip_serial = data;
break;
case 3:
if (data)
res.tip_network_id = data;
break;
}
break;
}
break;
default:
if (verbose)
push(res.ies, { type: ie.type, data: b64enc(ie.data) });
break;
}
}
if (args.periodic && !args.information_elements)
delete res.ies;
push(scan, res);
}
if (scan_iface) {
warn('removing temporary interface\n');
nl.request(def.NL80211_CMD_DEL_INTERFACE, 0, { dev: 'scan' });
}
}
printf("%.J\n", scan);
return scan;
}
let scan = wifi_scan();
if (args.periodic) {
ctx.call('ucentral', 'send', {
method: 'wifiscan',
params: { data: scan }
});
return;
}
result_json({
error: 0,
text: "Success",
resultCode: 1,
scan,
});

181
docs/schema_doc.css Normal file
View File

@@ -0,0 +1,181 @@
body {
font: 16px/1.5em "Overpass", "Open Sans", Helvetica, sans-serif;
color: #333;
font-weight: 300;
padding: 40px;
}
.btn.btn-link {
font-size: 18px;
user-select: text;
}
.jsfh-animated-property {
animation: eclair;
animation-iteration-count: 1;
animation-fill-mode: forwards;
animation-duration: .75s;
}
@keyframes eclair {
0%,100% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
}
.btn.btn-primary {
margin: 10px;
}
.btn.example-show.collapsed:before {
content: "show"
}
.btn.example-show:before {
content: "hide"
}
.description.collapse:not(.show) {
max-height: 100px !important;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.description.collapsing {
min-height: 100px !important;
}
.collapse-description-link.collapsed:after {
content: '+ Read More';
}
.collapse-description-link:not(.collapsed):after {
content: '- Read Less';
}
.badge {
font-size: 100%;
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.badge.value-type {
font-size: 120%;
margin-right: 5px;
margin-bottom: 10px;
}
.badge.default-value {
font-size: 120%;
margin-left: 5px;
margin-bottom: 10px;
}
.badge.restriction {
display: inline-block;
}
.badge.required-property,.badge.deprecated-property,.badge.pattern-property,.badge.no-additional {
font-size: 100%;
margin-left: 10px;
}
.accordion div.card:only-child {
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
}
.examples {
padding: 1rem !important;
}
.examples pre {
margin-bottom: 0;
}
.highlight.jumbotron {
padding: 1rem !important;
}
.generated-by-footer {
margin-top: 1em;
text-align: right;
}
/* From https://github.com/richleland/pygments-css/blob/master/friendly.css, see https://github.com/trentm/python-markdown2/wiki/fenced-code-blocks */
.highlight { background: #e9ecef; } /* Changed from #f0f0f0 in the original style to be the same as bootstrap's jumbotron */
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #60a0b0; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #007020 } /* Comment.Preproc */
.highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #902000 } /* Keyword.Type */
.highlight .m { color: #40a070 } /* Literal.Number */
.highlight .s { color: #4070a0 } /* Literal.String */
.highlight .na { color: #4070a0 } /* Name.Attribute */
.highlight .nb { color: #007020 } /* Name.Builtin */
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.highlight .no { color: #60add5 } /* Name.Constant */
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #007020 } /* Name.Exception */
.highlight .nf { color: #06287e } /* Name.Function */
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #40a070 } /* Literal.Number.Bin */
.highlight .mf { color: #40a070 } /* Literal.Number.Float */
.highlight .mh { color: #40a070 } /* Literal.Number.Hex */
.highlight .mi { color: #40a070 } /* Literal.Number.Integer */
.highlight .mo { color: #40a070 } /* Literal.Number.Oct */
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
.highlight .sr { color: #235388 } /* Literal.String.Regex */
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #06287e } /* Name.Function.Magic */
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */

1
docs/schema_doc.min.js vendored Normal file
View File

@@ -0,0 +1 @@
$(document).on("click",'a[href^="#"]',function(event){event.preventDefault();history.pushState({},"",this.href)});function flashElement(elementId){myElement=document.getElementById(elementId);myElement.classList.add("jsfh-animated-property");setTimeout(function(){myElement.classList.remove("jsfh-animated-property")},1e3)}function setAnchor(anchorLinkDestination){history.pushState({},"",anchorLinkDestination)}function anchorOnLoad(){let linkTarget=decodeURIComponent(window.location.hash.split("?")[0].split("&")[0]);if(linkTarget[0]==="#"){linkTarget=linkTarget.substr(1)}if(linkTarget.length>0){anchorLink(linkTarget)}}function anchorLink(linkTarget){const target=$("#"+linkTarget);target.parents().addBack().filter(".collapse:not(.show), .tab-pane, [role='tab']").each(function(index){if($(this).hasClass("collapse")){$(this).collapse("show")}else if($(this).hasClass("tab-pane")){const tabToShow=$("a[href='#"+$(this).attr("id")+"']");if(tabToShow){tabToShow.tab("show")}}else if($(this).attr("role")==="tab"){$(this).tab("show")}});setTimeout(function(){let targetElement=document.getElementById(linkTarget);if(targetElement){targetElement.scrollIntoView({block:"center",behavior:"smooth"});setTimeout(function(){flashElement(linkTarget)},500)}},1e3)}

117
docs/ucentral-schema.html Normal file

File diff suppressed because one or more lines are too long

28
docs/ucentral-state.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,650 +0,0 @@
/*
* ucode-transpiler.js - JSDoc plugin to naively transpile ucode into JS.
*
* Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
'use strict';
function skipString(s) {
let q = s.charAt(0);
let esc = false;
for (let i = 1; i < s.length; i++) {
let c = s.charAt(i);
if (esc) {
esc = false;
continue;
}
else if (c == '\\') {
esc = true;
continue;
}
else if (c == q) {
// consume regex literal flags
while (q == '/' && s.charAt(i + 1).match(/[gis]/))
i++;
return [ s.substring(0, i + 1), s.substring(i + 1) ];
}
}
throw 'Unterminated string literal';
}
function skipComment(s) {
let q = s.charAt(1),
end = (q == '/') ? '\n' : '*/',
esc = false;
for (let i = 2; i < s.length; i++) {
let c = s.charAt(i);
if (esc) {
esc = false;
continue;
}
else if (c == '\\') {
esc = true;
continue;
}
else if (s.substring(i, i + end.length) == end) {
return [ s.substring(0, i + end.length), s.substring(i + end.length) ];
}
}
if (q == '*')
throw 'Unterminated multiline comment';
return [ s, '' ];
}
function escapeString(s) {
return "'" + s.replace(/[\\\n']/g, '\\$&') + "';";
}
const keywords = [
'break',
'case',
'catch',
'const',
'continue',
'default',
'delete',
'elif',
'else',
'endfor',
'endfunction',
'endif',
'endwhile',
'false',
'for',
'function',
'if',
'in',
'let',
'null',
'return',
'switch',
'this',
'true',
'try',
'while'
];
const reserved = [
'await',
'class',
'debugger',
'enum',
'export',
'extends',
'finally',
'implements',
'import',
'instanceof',
'interface',
'new',
'package',
'private',
'protected',
'public',
'super',
'throw',
'typeof',
'var',
'void',
'with',
'yield'
];
function Transpiler(s, raw) {
this.source = s;
this.offset = 0;
this.tokens = [];
if (raw) {
this.state = 'identify_token';
this.block = 'block_statement';
}
else {
this.state = 'identify_block';
}
let token = null;
do {
token = this.parse();
switch (token.type) {
case '-}}':
case '-%}':
case '}}':
case '%}':
if (raw)
throw 'Unexpected token "' + token.type + '"';
break;
}
this.tokens.push(token);
}
while (token.type != 'eof');
}
Transpiler.prototype = {
parse: function() {
let m;
switch (this.state) {
case 'identify_block':
m = this.source.match(/^((?:.|\n)*?)((\{[{%#])(?:.|\n)*)$/);
if (m) {
switch (m[3]) {
case '{#':
this.state = 'block_comment';
break;
case '{{':
this.state = 'block_expression';
break;
case '{%':
this.state = 'block_statement';
break;
}
this.source = m[2];
return { type: 'text', value: escapeString(m[1]), prefix: '' };
}
else if (this.source.length) {
let t = { type: 'text', value: escapeString(this.source), prefix: '' };
this.source = '';
return t;
}
else {
return { type: 'eof', value: '', prefix: '' };
}
break;
case 'block_comment':
m = this.source.match(/^((?:.|\n)*?#\})((?:.|\n)*)$/);
if (!m)
throw 'Unterminated comment block';
this.source = m[2];
this.state = 'identify_block';
this.block = null;
return {
type: 'comment',
value: m[1].replace(/\*\}/g, '*\\}').replace(/^\{##/, '/**').replace(/^\{#/, '/*').replace(/#\}$/, '*/'),
prefix: ''
};
case 'block_expression':
this.state = 'identify_token';
this.block = 'expression';
this.source = this.source.replace(/^\{\{[+-]?/, '');
return this.parse();
case 'block_statement':
this.state = 'identify_token';
this.block = 'statement';
this.source = this.source.replace(/^\{%[+-]?/, '');
return this.parse();
case 'identify_token':
let t = this.parsetoken();
if ((this.block == 'expression' && (t.type == '-}}' || t.type == '}}')) ||
(this.block == 'statement' && (t.type == '-%}' || t.type == '%}'))) {
this.state = 'identify_block';
this.block = null;
return {
type: ';',
value: ';',
prefix: t.prefix
};
}
if (this.block == 'expression' && t.type == 'eof')
throw 'Unterminated expression block';
return t;
}
},
parsetoken: function() {
let token = this.source.match(/^((?:\s|\n)*)(-[%}]\}|<<=|>>=|===|!==|\.\.\.|\?\.\[|\?\.\(|[%}]\}|\/\*|\/\/|&&|[+&|^\/%*-=!<>]=|--|\+\+|<<|>>|\|\||=>|\?\.|[+=&|[\]\^{}:,~\/>!<%*()?;.'"-]|(\d+(?:\.\d+)?)|(\w+))((?:.|\n)*)$/);
let rv, r, t;
if (token) {
switch (token[2]) {
case '"':
case "'":
r = skipString(token[2] + token[5]);
rv = r[0];
t = 'string';
this.source = r[1];
break;
case '//':
case '/*':
r = skipComment(token[2] + token[5]);
rv = r[0];
t = 'comment';
this.source = r[1];
break;
case '/':
case '/=':
if (this.lastToken.match(/[(,=:[!&|?{};]/)) {
r = skipString(token[2] + token[5]);
rv = r[0];
t = 'regexp';
this.source = r[1];
}
else {
rv = token[2];
t = token[2];
this.source = token[5];
}
break;
default:
this.source = token[5];
if (token[3]) {
rv = token[3];
if (token[3].indexOf('.') != -1)
t = 'double';
else
t = 'number';
}
else if (token[4]) {
rv = token[4];
if (keywords.indexOf(token[4]) != -1) {
t = token[4];
}
else {
t = 'label';
if (reserved.indexOf(token[4]) != -1)
rv += '_';
}
}
else {
rv = token[2];
t = token[2];
}
break;
}
this.lastToken = token[2];
return {
type: t,
value: rv,
prefix: token[1]
};
}
else if (this.source.match(/^\s*$/)) {
rv = this.source;
this.source = '';
return {
type: 'eof',
value: '',
prefix: rv
};
}
else {
throw 'Unrecognized character near [...' + this.source + ']';
}
},
next: function() {
let idx = this.offset++;
return this.tokens[Math.min(idx, this.tokens.length - 1)];
},
skip_statement: function(tokens, ends) {
let nest = 0;
while (true) {
let token = this.next();
if (token.type == 'eof') {
this.offset--;
break;
}
if (nest == 0 && ends.indexOf(token.type) != -1) {
this.offset--;
break;
}
switch (token.type) {
case '(':
case '[':
case '{':
nest++;
break;
case ')':
case ']':
case '}':
nest--;
break;
}
tokens.push(token);
if (token.type == ';')
break;
}
return tokens;
},
skip_paren: function(tokens) {
let token = this.next();
let depth = 0;
if (token.type != '(')
throw 'Unexpected token, expected "(", got "' + token.type + '"';
do {
tokens.push(token);
switch (token.type) {
case '(':
depth++;
break;
case ')':
depth--;
if (depth == 0)
return token;
break;
case 'eof':
throw 'Unexpected EOF';
}
token = this.next();
}
while (depth != 0);
},
assert_token: function(tokens, type) {
let token = this.next();
if (token.type != type)
throw 'Unexpected token, expected "' + type + '", got "' + token.type + '"';
tokens.push(token);
return tokens;
},
check_token: function(tokens, type) {
let token = this.next();
if (token.type != type) {
this.offset--;
return false;
}
tokens.push(token);
return true;
},
patch: function(tokens, type, value) {
tokens[tokens.length - 1].type = type;
tokens[tokens.length - 1].value = (value != null) ? value : type;
},
skip_block: function(tokens, ends) {
while (true) {
let off = tokens.length;
if (this.check_token(tokens, 'if')) {
this.skip_paren(tokens);
if (this.check_token(tokens, ':')) {
this.patch(tokens, '{');
this.skip_block(tokens, ['else', 'elif', 'endif']);
while (tokens[tokens.length - 1].type == 'elif') {
let elif = tokens.pop();
tokens.push(
{ type: '}', value: '}', prefix: '' },
{ type: 'else', value: 'else', prefix: elif.prefix },
{ type: 'if', value: 'if', prefix: ' ' }
);
this.skip_paren(tokens);
this.assert_token(tokens, ':');
this.patch(tokens, '{');
this.skip_block(tokens, ['elif', 'else', 'endif']);
}
if (tokens[tokens.length - 1].type == 'else') {
let else_ = tokens.pop();
tokens.push(
{ type: '}', value: '}', prefix: '' },
{ type: 'else', value: 'else', prefix: else_.prefix },
{ type: '{', value: '{', prefix: ' ' }
);
this.skip_block(tokens, ['endif']);
}
this.patch(tokens, '}');
}
else if (this.check_token(tokens, '{')) {
this.skip_block(tokens, ['}']);
if (!this.check_token(tokens, 'else'))
continue;
if (this.check_token(tokens, '{'))
this.skip_block(tokens, ['}']);
else
this.skip_statement(tokens, ends);
}
else {
this.skip_statement(tokens, ends);
}
}
else if (this.check_token(tokens, 'for')) {
let cond = [];
this.skip_paren(cond);
// Transform `for (x, y in ...)` into `for (x/*, y*/ in ...)`
if (cond.length > 5 &&
cond[1].type == 'label' &&
cond[2].type == ',' &&
cond[3].type == 'label' &&
cond[4].type == 'in') {
cond[2].type = 'comment';
cond[2].value = '/*' + cond[2].value;
cond[3].type = 'comment';
cond[3].value = cond[3].value + '*/';
}
// Transform `for (let x, y in ...)` into `for (let x/*, y*/ in ...)`
else if (cond.length > 6 &&
cond[1].type == 'let' &&
cond[2].type == 'label' &&
cond[3].type == ',' &&
cond[4].type == 'label' &&
cond[5].type == 'in') {
cond[3].type = 'comment';
cond[3].value = '/*' + cond[3].value;
cond[4].type = 'comment';
cond[4].value = cond[4].value + '*/';
}
tokens.push(...cond);
if (this.check_token(tokens, ':')) {
this.patch(tokens, '{');
this.skip_block(tokens, ['endfor']);
this.patch(tokens, '}');
}
else if (this.check_token(tokens, '{'))
this.skip_block(tokens, ['}']);
else
this.skip_statement(tokens, ends);
}
else if (this.check_token(tokens, 'while')) {
this.skip_paren(tokens);
if (this.check_token(tokens, ':')) {
this.patch(tokens, '{');
this.skip_block(tokens, ['endwhile']);
this.patch(tokens, '}');
}
else if (this.check_token(tokens, '{'))
this.skip_block(tokens, ['}']);
else
this.skip_statement(tokens, ends);
}
else if (this.check_token(tokens, 'function')) {
this.check_token(tokens, 'label');
this.skip_paren(tokens);
if (this.check_token(tokens, ':')) {
this.patch(tokens, '{');
this.skip_block(tokens, ['endfunction']);
this.patch(tokens, '}');
}
else if (this.check_token(tokens, '{'))
this.skip_block(tokens, ['}']);
}
else if (this.check_token(tokens, 'try')) {
this.assert_token(tokens, '{');
this.skip_block(tokens, ['}']);
this.assert_token(tokens, 'catch');
// Transform `try { ... } catch { ... }` into `try { ... } catch(e) { ... }`
if (this.tokens[this.offset].type == '(')
this.skip_paren(tokens);
else
tokens.push(
{ type: '(', value: '(', prefix: '' },
{ type: 'label', value: 'e', prefix: '' },
{ type: ')', value: ')', prefix: '' }
);
this.assert_token(tokens, '{');
this.skip_block(tokens, ['}']);
}
else if (this.check_token(tokens, 'switch')) {
this.skip_paren(tokens);
this.assert_token(tokens, '{');
this.skip_block(tokens, ['}']);
}
else if (this.check_token(tokens, '{')) {
this.skip_block(tokens, ['}']);
}
else if (this.check_token(tokens, 'text')) {
/* pass */
}
else if (this.check_token(tokens, 'comment')) {
/* pass */
}
else {
this.skip_statement(tokens, ends);
}
for (let type of ends)
if (this.check_token(tokens, type))
return tokens;
if (this.check_token([], 'eof'))
break;
}
throw 'Unexpected EOF';
},
transpile: function() {
let tokens = [];
this.skip_block(tokens, ['eof']);
return tokens.map(t => t.prefix + t.value).join('');
}
};
exports.handlers = {
beforeParse: function(e) {
let raw = !e.source.match(/\{[{%]/) || e.source.match(/^#!([a-z\/]*)ucode[ \t]+-[A-Z]*R/),
t = new Transpiler(e.source, raw);
e.source = t.transpile();
}
};

View File

@@ -1,153 +0,0 @@
{
"classes": {
"network_services": {
"ingress": "CS3",
"eggress": "CS3",
"bulk-pps": 100,
"bulk-timeout": 5,
"bulk-dscp": "CS0"
},
"conferencing": {
"source": "https://support.zoom.us/hc/en-us/articles/207368756-Using-QoS-DSCP-Marking",
"ingress": "EF",
"egress": "EF"
},
"telephony": {
"source": "https://support.zoom.us/hc/en-us/articles/207368756-Using-QoS-DSCP-Marking",
"ingress": "EF",
"egress": "EF"
},
"streaming": {
"ingress": "AF41",
"egress": "AF41"
},
"browsing": {
"ingress": "CS0",
"egress": "CS0"
}
},
"services": {
"networking": {
"classifier": "network_services",
"tcp": [ 22, 123, 53, 5353 ],
"udp": [ 53, 5353 ]
},
"browsing": {
"classifier": "browsing",
"tcp": [ 80, 443 ],
"udp": [ 80, 443 ]
},
"youtube": {
"source": "https://services.google.com/fh/files/blogs/enabling_remote_working_with_hangouts_meet_quick_deployment_guide.pdf",
"classifier": "streaming",
"fqdn": [ "*.googlevideo.com", "*.youtube-nocookie.com", "*.ytimg.com" ],
"uses": [ "rtmp" ]
},
"netflix": {
"source": "https://www.netify.ai/resources/applications/netflix",
"classifier": "streaming",
"fqdn": [ "*.nflxvideo.com", "*.nflxvideo.net" ]
},
"amazon-prime": {
"source": "https://www.netify.ai/resources/applications/amazon-prime",
"classifier": "streaming",
"fqdn": [
"*aiv-cdn.net", "*aiv-delivery.net", "*amazonvideo.com", "*atv-ext.amazon.com", "*atv-ext-eu.amazon.com",
"atv-ext-fe.amazon.com", "atv-ps.amazon.com", "atv-ps-eu.amazon.com", "atv-ps-eu.amazon.co.uk",
"atv-ps-fe.amazon.co.jp", "atv-ps-fe.amazon.com", "primevideo.com", "pv-cdn.net", "video.a2z.com"
]
},
"disney-plus": {
"source": "https://www.netify.ai/resources/applications/disney-plus",
"classifier": "streaming",
"fqdn": [
"*disneyplus.com", "*disneyplus.disney.co.jp", "*disney-plus.net", "*disneystreaming.service-now.com",
"*disney-vod-na-west-1.top.comcast.net", "*dssott.com"
]
},
"hbo": {
"source": "https://www.netify.ai/resources/applications/hbo",
"classifier": "streaming",
"fqdn": [
"*hbo.com", "*hbogoasia.com", "*hbogoasia.id", "*hbogoasia.ph", "*hbogo.com", "*hbogo.co.th", "*hbogo.eu", "*hbomaxcdn.com",
"*hbomax.com", "*hbomax-images.warnermediacdn.com", "*hbonow.com", "maxgo.com"
]
},
"rtmp": {
"comment": "RTMP (YouTube Live, Twitch, Vimeo and LinkedIn Live)",
"classifier": "streaming",
"tcp": [ "1935-1936", 2396, 2935 ]
},
"stun": {
"comment": "STUN (Session Traversal Utilities for NAT)",
"classifier": "conferencing",
"udp": [ "3478-3497" ]
},
"zoom": {
"source": "https://support.zoom.us/hc/en-us/articles/201362683-Zoom-network-firewall-or-proxy-server-settings",
"classifier": "conferencing",
"fqdn": [ "*zoom.us" ],
"tcp": [ "8801-8802" ],
"udp": [ "8801-8810" ],
"uses": [ "stun" ]
},
"facetime": {
"source": "https://support.apple.com/en-us/HT202078",
"classifier": "conferencing",
"udp": [ "16384-16387", "16393-16402" ]
},
"webex": {
"source": "https://help.webex.com/en-us/article/WBX264/How-Do-I-Allow-Webex-Meetings-Traffic-on-My-Network?#id_135400",
"classifier": "conferencing",
"tcp": [ 5004 ],
"udp": [ 9000 ]
},
"jitsi": {
"source": "https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart/#setup-and-configure-your-firewall",
"classifier": "conferencing",
"tcp": [ 5349 ],
"udp": [ 10000 ],
"uses": [ "stun" ]
},
"google-meet": {
"source": "https://services.google.com/fh/files/blogs/enabling_remote_working_with_hangouts_meet_quick_deployment_guide.pdf",
"classifier": "conferencing",
"udp": [ "19302-19309" ]
},
"teams": {
"source": "https://learn.microsoft.com/en-us/microsoft-365/enterprise/urls-and-ip-address-ranges?view=o365-worldwide#skype-for-business-online-and-microsoft-teams",
"classifier": "conferencing",
"uses": [ "stun" ]
},
"voip": {
"classifier": "telephony",
"tcp": [ "5060-5061" ],
"udp": [ "5060-5061" ]
},
"vowifi": {
"classifier": "telephony",
"udp": [ 500, 4500 ]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
let fs = require('fs');
let key_info = {
'dummy_static': function(file, signature) {
return signature == 'aaaaaaaaaa';
},
'cig_sha256': function(file, signature) {
// Decrypt from base64 to binary and write to a tmp file
let decoded = b64dec(signature);
if (!decoded) {
return false;
}
let pub_key_file_name = "/etc/ucentral/sign_pubkey.pem";
let sign_file_name = "/tmp/sign_file.txt";
let sign_file = fs.open(sign_file_name, "w");
sign_file.write(decoded);
sign_file.close();
// Verify the signature
let sign_verify_cmd = "openssl dgst -sha256 -verify " + pub_key_file_name + " -signature " + sign_file_name + " " + file;
let pipe = fs.popen(sign_verify_cmd);
let result = pipe.read("all");
let retcode = pipe.close();
// Return code of 0 is valid signature
if (retcode == 0) {
return true;
} else {
return false;
}
},
};
return {
verify: function(file, signature) {
let func = key_info[restrict?.key_info?.vendor + '_' + restrict?.key_info?.algo];
if (!func)
return false;
return func(file, signature);
},
}

View File

@@ -1,42 +0,0 @@
{%
let admin_ui = state.services?.admin_ui;
if (!admin_ui?.wifi_ssid)
return;
let interface = {
admin_ui: true,
name: 'Admin-UI',
role: 'downstream',
auto_start: 0,
services: [ 'ssh', 'http' ],
ipv4: {
addressing: 'static',
subnet: '10.254.254.1/24',
dhcp: {
lease_first: 10,
lease_count: 10,
lease_time: '6h'
}
},
ssids: [
{
name: admin_ui.wifi_ssid,
wifi_bands: [ '2G', '5G' ],
bss_mode: 'ap',
encryption: {
proto: 'none'
}
}
],
};
if (admin_ui.wifi_bands)
interface.ssids[0].wifi_bands = admin_ui.wifi_bands;
if (admin_ui.wifi_key) {
interface.ssids[0].encryption.proto = 'psk2';
interface.ssids[0].encryption.key = admin_ui.wifi_key;
}
push(state.interfaces, interface);
%}
set state.ui.offline_trigger={{ admin_ui.offline_trigger }}

View File

@@ -1,57 +0,0 @@
{%
let roles = (state.switch && state.switch.loop_detection &&
state.switch.loop_detection.roles) ?
state.switch.loop_detection.roles : [];
services.set_enabled("ustpd", length(roles));
function loop_detect(role) {
return (index(roles, role) >= 0) ? 1 : 0;
}
%}
# Basic configuration
set network.loopback=interface
set network.loopback.ifname='lo'
set network.loopback.proto='static'
set network.loopback.ipaddr='127.0.0.1'
set network.loopback.netmask='255.0.0.0'
set network.up=device
set network.up.name=up
set network.up.type=bridge
set network.up.stp={{ loop_detect("upstream") }}
set network.up.igmp_snooping='1'
{% if (capab.platform != "switch"): %}
set network.down=device
set network.down.name=down
set network.down.type=bridge
set network.down.stp={{ loop_detect("downstream") }}
set network.down.igmp_snooping='1'
{% endif %}
set network.up_none=interface
set network.up_none.ifname=up
set network.up_none.proto=none
{% for (let k, v in capab.macaddr): %}
add network device
set network.@device[-1].name={{ s(k) }}
set network.@device[-1].macaddr={{ s(v) }}
{% endfor %}
{% for (let k, v in capab.switch): %}
add network switch
set network.@switch[-1].name={{ s(v.name) }}
set network.@switch[-1].reset={{ b(v.reset) }}
set network.@switch[-1].enable_vlan={{ b(v.enable) }}
{% endfor %}
{% for (let k, port in ethernet.ports): %}
{% if (!port.switch) continue; %}
add network switch_vlan
set network.@switch_vlan[-1].device={{ s(port.switch.name) }}
set network.@switch_vlan[-1].vlan={{ s(port.vlan) }}
set network.@switch_vlan[-1].ports={{s(port.switch.port + 't ' + port.swconfig)}}
{% endfor %}

View File

@@ -1,100 +0,0 @@
{%
/* find and replace the first upstream interface using vid with
* the broadband settings */
let uplink = ethernet.get_interface("upstream", 0);
if (!uplink)
return;
if (index([ "wwan", "pppoe", "static", "dhcp", "wds" ], broadband.protocol) < 0)
return;
for (let k, v in uplink)
if (index([ "vlan", "index" ], k) < 0)
delete uplink[k];
uplink.name = "ISP_UPLINK";
uplink.role = "upstream";
uplink.vlan = { id: 0 };
uplink.metric = 1;
if (broadband.protocol == "wwan") {
let wwan = { };
wwan.protocol = 'wwan';
wwan.modem_type = broadband['modem-type'] || 'wwan';
wwan.pin_code = broadband['pin-code'] || '';
wwan.access_point_name = broadband['access-point-name'] || '';
wwan.packet_data_protocol = broadband['packet-data-protocol'] || 'dual-stack';
wwan.authentication_type = broadband['authentication-type'] || 'none';
wwan.username = broadband.username || '';
wwan.password = broadband.password || '';
uplink.broad_band = wwan;
}
if (broadband.protocol == "pppoe") {
let pppoe = { };
pppoe.protocol = 'pppoe';
pppoe.username = broadband['user-name'] || '';
pppoe.password = broadband.password || '';
pppoe.timeout = broadband.timeout || '30';
uplink.broad_band = pppoe;
uplink.ethernet = [
{
"select_ports": [ "WAN*" ]
}
];
}
if (broadband.protocol == "static") {
uplink.ethernet = [
{
"select_ports": [ "WAN*" ]
}
];
uplink.ipv4 = {
addressing: "static",
subnet: broadband['ipv4-address'],
gateway: broadband['ipv4-gateway'],
use_dns: broadband['use-dns'],
};
}
if (broadband.protocol == "dhcp") {
uplink.ethernet = [
{
"select_ports": [ "WAN*" ]
}
];
uplink.ipv4 = {
addressing: "dynamic",
};
}
if (broadband.protocol == "wds") {
uplink.ipv4 = {
addressing: "dynamic",
};
let wds = {
"name": broadband.ssid,
"wifi_bands": [
"2G", "5G"
],
"bss_mode": "wds-sta",
"encryption": {
"proto": broadband.encryption,
"key": broadband.passphrase,
"ieee80211w": "optional"
}
};
if (!uplink.ssids)
uplink.ssids = [ wds ];
else
push(uplink.ssids, wds);
}
%}

View File

@@ -1,5 +0,0 @@
# Raw Configuration
{% for (let config in config_raw): %}
{{ config[0] }} {{ config[1] }}{{config[2] ? '=' + config[2] : ''}}
{% endfor %}

View File

@@ -1,4 +0,0 @@
{% for (let user in users): %}
"{{ user.user_name }}" PWD "{{ user.password }}"
{% endfor %}
* TLS,TTLS

View File

@@ -1,13 +0,0 @@
{% let eth_ports = ethernet.lookup_by_select_ports(ports.select_ports) %}
{% for (let port in eth_ports):
port = replace(port, '.', '_');
%}
set network.{{ port }}=device
set network.{{ port }}.name={{ s(port) }}
set network.{{ port }}.ifname={{ s(port) }}
set network.{{ port }}.enabled={{ ports.enabled }}
{% if (!ports.speed && !ports.duplex) continue %}
set network.{{ port }}.speed={{ ports.speed }}
set network.{{ port }}.duplex={{ ports.duplex == "full" ? true : false }}
{% endfor %}

View File

@@ -1,216 +0,0 @@
{%
let has_downstream_relays = false;
let dest;
// Skip interfaces previously marked as conflicting.
if (interface.conflicting) {
warn("Skipping conflicting interface declaration");
return;
}
// Skip upstream interfaces that try to use a wireguard overlay
if (interface.role == 'upstream' && 'wireguard-overlay' in interface.services) {
warn("Skipping interface. wireguard-overlay is not allowed on upstream interfaces.");
return;
}
// Check this interface for role/vlan uniqueness...
let this_vid = interface.vlan.id || interface.vlan.dyn_id;
for (let other_interface in state.interfaces) {
if (other_interface == interface)
continue;
if (!other_interface.ethernet && length(interface.ssids) == 1)
continue;
let other_vid = other_interface.vlan.id || '';
if (interface.role === other_interface.role && this_vid === other_vid) {
warn("Multiple interfaces with same role and VLAN ID defined, ignoring conflicting interface");
other_interface.conflicting = true;
}
if (other_interface.role == 'downstream' &&
other_interface.ipv6 &&
other_interface.ipv6.dhcpv6 &&
other_interface.ipv6.dhcpv6.mode == 'relay')
has_downstream_relays = true;
}
// check if a downstream interface with a vlan has a matching upstream interface
if (ethernet.has_vlan(interface) && interface.role == "downstream" && index(vlans, this_vid) < 0) {
warn("Trying to create a downstream interface with a VLAN ID, without matching upstream interface.");
return;
}
// reject static config that has no subnet
if (interface.role == 'upstream' && interface.ipv4?.addressing == 'static')
if (!interface.ipv4?.subnet || !interface.ipv4?.use_dns || !interface.ipv4?.gateway)
die('invalid static interface settings');
// resolve auto prefixes
if (wildcard(interface.ipv4?.subnet, 'auto/*')) {
try {
interface.ipv4.subnet = ipcalc.generate_prefix(state, interface.ipv4.subnet, false);
}
catch (e) {
warn("Unable to allocate a suitable IPv4 prefix: %s, ignoring interface", e);
return;
}
}
if (wildcard(interface.ipv6?.subnet, 'auto/*')) {
try {
interface.ipv6.subnet = ipcalc.generate_prefix(state, interface.ipv6.subnet, true);
}
catch (e) {
warn("Unable to allocate a suitable IPv6 prefix: %s, ignoring interface", e);
return;
}
}
// Captive Portal is only supported on downstream interfaces
if (interface.captive && interface.role != 'downstream') {
warn("Trying to create a Captive Portal on a none downstream interface.");
return;
}
// Port forwardings are only supported on downstream interfaces
if ((interface.ipv4?.port_forward || interface.ipv6?.port_forward) && interface.role != 'downstream') {
warn("Port forwardings are only supported on downstream interfaces.");
return;
}
// Traffic accept rules are only supported on downstream interfaces
if (interface.ipv6?.traffic_allow && interface.role != 'downstream') {
warn("Traffic accept rules are only supported on downstream interfaces.");
return;
}
// Gather related BSS modes and ethernet ports.
let bss_modes = map(interface.ssids, ssid => ssid.bss_mode);
let eth_ports = ethernet.lookup_by_interface_vlan(interface);
let swconfig;
if (interface.role == 'upstream')
swconfig = ethernet.switch_by_interface_vlan(interface);
// If at least one station mode SSID is part of this interface then we must
// not bridge at all. Having any other SSID or any number of matching ethernet
// ports in such a case is a semantic error.
if ('sta' in bss_modes && (length(eth_ports) > 0 || length(bss_modes) > 1)) {
warn("Station mode SSIDs cannot be bridged with ethernet ports or other SSIDs, ignoring interface");
return;
}
// Compute unique logical name and netdev name to use
let name = ethernet.calculate_name(interface);
let bridgedev = 'up';
if (capab.platform != "switch" && interface.role == "downstream")
bridgedev = 'down';
let netdev = name;
let network = name;
// Determine the IPv4 and IPv6 configuration modes and figure out if we
// can set them both in a single interface (automatic) or whether we need
// two logical interfaces due to different protocols.
let ipv4_mode = interface.ipv4 ? interface.ipv4.addressing : 'none';
let ipv6_mode = interface.ipv6 ? interface.ipv6.addressing : 'none';
// If no metric is defined explicitly, any upstream interfaces will default
// to 5 and downstream interfaces will default to 10
if (!interface.metric && interface.role == "upstream")
interface.metric = 5;
if (!interface.metric && interface.role == "downstream")
interface.metric = 10;
// If this interface is a tunnel, we need to create the interface
// in a different way
let tunnel_proto = interface.tunnel ? interface.tunnel.proto : '';
//
// Create the actual UCI sections
//
if (interface.broad_band) {
include("interface/broadband.uc", { interface, name, location, eth_ports, raw_ports });
return;
}
// tunnel interfaces need additional sections
if (tunnel_proto in [ "mesh", "l2tp", "vxlan", "gre", "gre6" ])
include("interface/" + tunnel_proto + ".uc", { interface, name, eth_ports, location, netdev, ipv4_mode, ipv6_mode, this_vid });
if (!interface.ethernet && length(interface.ssids) == 1 && !tunnel_proto && !("vxlan-overlay" in interface.services)) {
if (interface.role == 'downstream')
interface.type = 'bridge';
netdev = '';
} else if (tunnel_proto == 'vxlan') {
netdev = '@' + name + '_vx';
interface.type = 'bridge';
} else if (tunnel_proto != 'gre' && tunnel_proto != 'gre6')
// anything else requires a bridge-vlan
include("interface/bridge-vlan.uc", { interface, name, eth_ports, this_vid, bridgedev, swconfig });
if (interface.role == "downstream" && "wireguard-overlay" in interface.services)
dest = 'unet';
include("interface/common.uc", {
name, this_vid, netdev,
ipv4_mode, ipv4: interface.ipv4 || {},
ipv6_mode, ipv6: interface.ipv6 || {}
});
include('interface/firewall.uc', { name, ipv4_mode, ipv6_mode, dest });
if (interface.ipv4 || interface.ipv6) {
include('interface/dhcp.uc', {
ipv4: interface.ipv4 || {},
ipv6: interface.ipv6 || {},
has_downstream_relays
});
}
let count = 0;
for (let i, ssid in interface.ssids) {
let modes = (ssid.bss_mode == "wds-repeater") ?
[ "wds-sta", "wds-ap" ] : [ ssid.bss_mode ];
for (let mode in modes) {
include('interface/ssid.uc', {
location: location + '/ssids/' + i,
ssid: { ...ssid, bss_mode: mode },
count,
name,
network,
});
if (ssid?.encryption?.proto == 'owe-transition') {
ssid.encryption.proto = 'none';
include('interface/ssid.uc', {
location: location + '/ssids/' + i + '_owe',
ssid: { ...ssid, bss_mode: mode },
count,
name,
network,
owe: true,
});
}
count++;
}
}
if (interface.captive)
include('interface/captive.uc', { name });
%}
{% if (tunnel_proto == 'mesh'): %}
set network.{{ name }}.batman=1
{% endif %}
{% if (interface.role == "downstream" && "wireguard-overlay" in interface.services): %}
add network rule
set network.@rule[-1].in='{{name}}'
set network.@rule[-1].lookup='{{ routing_table.get('wireguard_overlay') }}'
{% endif %}

View File

@@ -1,64 +0,0 @@
add network bridge-vlan
set network.@bridge-vlan[-1].device={{ bridgedev }}
set network.@bridge-vlan[-1].vlan={{ this_vid }}
{% for (let port in keys(eth_ports)): %}
add_list network.@bridge-vlan[-1].ports={{ port }}{{ ethernet.port_vlan(interface, eth_ports[port]) }}
{% endfor %}
{% if (interface.tunnel?.proto == "mesh"): %}
add_list network.@bridge-vlan[-1].ports=batman{{ ethernet.has_vlan(interface) ? "." + this_vid + ":t" : '' }}
{% endif %}
{% if (interface.tunnel?.proto == "vxlan"): %}
add_list network.@bridge-vlan[-1].ports={{ name }}_vx
{% endif %}
{% if (interface.tunnel?.proto == "gre"): %}
add_list network.@bridge-vlan[-1].ports=gre4t-gre.{{ interface.vlan.id }}
{% endif %}
{% if (interface.tunnel?.proto == "gre6"): %}
add_list network.@bridge-vlan[-1].ports=gre6t-greip6.{{ interface.vlan.id }}
{% endif %}
{% if ('vxlan-overlay' in interface.services): %}
add_list network.@bridge-vlan[-1].ports=vx-unet
{% endif %}
{% if (interface.bridge): %}
set network.@bridge-vlan[-1].txqueuelen={{ interface.bridge.tx_queue_len }}
set network.@bridge-vlan[-1].isolate={{interface.bridge.isolate_ports }}
set network.@bridge-vlan[-1].mtu={{ interface.bridge.mtu }}
{% endif %}
add network device
set network.@device[-1].type=8021q
set network.@device[-1].name={{ name }}
set network.@device[-1].ifname={{ bridgedev }}
set network.@device[-1].vid={{ this_vid }}
{% if (interface.vlan_awareness?.first): %}
{% let vlan = interface.vlan_awareness.first;
if (interface.vlan_awareness.last)
vlan += '-' + interface.vlan_awareness.last; %}
{% for (let port in keys(eth_ports)): %}
add network device
set network.@device[-1].name={{ port }}
set network.@device[-1].vlan={{ vlan }}
{% endfor %}
{% if (interface.role == 'upstream'): %}
set network.up.vlan={{ vlan }}
{% endif %}
{% if (interface.role == 'downstream'): %}
set network.down.vlan={{ vlan }}
{% endif %}
{% endif %}
{% if (interface.role == 'upstream'): %}
{% for (let port in keys(eth_ports)): %}
set udevstats.{{ port }}=device
set udevstats.{{ port }}.name={{ s(port) }}
add_list udevstats.{{ port }}.vlan={{ s(interface.vlan.id || 0) }}
{% endfor %}
{% endif %}
{% if (interface.vlan.id && swconfig): %}
add network switch_vlan
set network.@switch_vlan[-1].device={{ s(swconfig.name) }}
set network.@switch_vlan[-1].vlan={{ s(this_vid) }}
set network.@switch_vlan[-1].ports={{s(swconfig.ports)}}
{% endif %}

View File

@@ -1,45 +0,0 @@
{% if (interface.broad_band.protocol == 'wwan'): %}
{%
function match_pdptype() {
let pdptypes = {
'ipv4': 'ip',
'ipv6': 'ipv6',
'dual-stack': 'ipv4v6'
};
return pdptypes[interface.broad_band.packet_data_protocol];
}
function match_authtype() {
if (interface.broad_band.authentication_type == 'papchap')
return 'both';
return interface.broad_band.authentication_type;
}
%}
set network.{{ name }}=interface
set network.{{ name }}.ucentral_name={{ s(interface.name) }}
set network.{{ name }}.ucentral_path={{ s(location) }}
set network.{{ name }}.proto={{ s(interface.broad_band.modem_type) }}
set network.{{ name }}.pincode={{ s(interface.broad_band.pin_code) }}
set network.{{ name }}.apn={{ s(interface.broad_band.access_point_name) }}
set network.{{ name }}.device='/dev/cdc-wdm0'
set network.{{ name }}.pdptype={{ s(match_pdptype()) }}
set network.{{ name }}.auth={{ s(match_authtype()) }}
set network.{{ name }}.username={{ s(interface.broad_band.user_name) }}
set network.{{ name }}.password={{ s(interface.broad_band.password) }}
{% endif %}
{% if (interface.broad_band.protocol == 'pppoe'): %}
set network.{{ name }}=interface
set network.{{ name }}.ucentral_name={{ s(interface.name) }}
set network.{{ name }}.ucentral_path={{ s(location) }}
set network.{{ name }}.ifname={{ s(keys(eth_ports)[0]) }}
set network.{{ name }}.proto='pppoe'
set network.{{ name }}.username={{ s(interface.broad_band.user_name) }}
set network.{{ name }}.password={{ s(interface.broad_band.password) }}
set network.{{ name }}.timeout={{ s(interface.broad_band.timeout) }}
{% endif %}
{% include('firewall.uc', { name, ipv4: true, ipv6: true }); %}

View File

@@ -1,119 +0,0 @@
{%
if (config.radius_gw_proxy)
services.set_enabled("radius-gw-proxy", true);
function radius_proxy_tlv(server, port, name) {
let tlv = replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1$2$3$4$5$6") + sprintf(":%s:%s:%s", server, port, name);
return tlv;
}
captive.interface(section, config);
let name = split(section, '_')[0];
if (!captive.web_root)
system('cp -r /www-uspot /tmp/ucentral/');
else {
let fs = require('fs');
fs.mkdir('/tmp/ucentral/www-uspot');
let web_root = fs.open('/tmp/ucentral/web-root.tar', 'w');
web_root.write(b64dec(captive.web_root));
web_root.close();
system('tar x -C /tmp/ucentral/www-uspot -f /tmp/ucentral/web-root.tar');
}
if (captive.radius_gw_proxy)
services.set_enabled("radius-gw-proxy", true);
%}
# Captive Portal service configuration
set uspot.{{ section }}=uspot
set uspot.{{ section }}.auth_mode={{ s(config.auth_mode) }}
set uspot.{{ section }}.web_root={{ b(config.web_root) }}
set uspot.{{ section }}.idle_timeout={{ config.idle_timeout }}
set uspot.{{ section }}.session_timeout={{ config.session_timeout }}
{% if (config.auth_mode in [ 'radius', 'uam']): %}
{% if (config.radius_gw_proxy): %}
set uspot.{{ section }}.auth_server='127.0.0.1'
set uspot.{{ section }}.auth_port='1812'
set uspot.{{ section }}.auth_proxy={{ s(radius_proxy_tlv(config.auth_server, config.auth_port, 'captive')) }}
{% if (config.acct_server): %}
set uspot.{{ section }}.acct_server='127.0.0.1'
set uspot.{{ section }}.acct_port='1813'
set uspot.{{ section }}.acct_proxy={{ s(radius_proxy_tlv(config.acct_server, config.acct_port, 'captive')) }}
{% endif %}
{% else %}
set uspot.{{ section }}.auth_server={{ s(config.auth_server) }}
set uspot.{{ section }}.auth_port={{ s(config.auth_port) }}
set uspot.{{ section }}.acct_server={{ s(config.acct_server) }}
set uspot.{{ section }}.acct_port={{ s(config.acct_port) }}
{% endif %}
set uspot.{{ section }}.auth_secret={{ s(config.auth_secret) }}
set uspot.{{ section }}.acct_secret={{ s(config.acct_secret) }}
set uspot.{{ section }}.acct_interval={{ config.acct_interval }}
{% endif %}
{% if (config.auth_mode == 'credentials'): %}
{% for (let cred in config.credentials): %}
add uspot credentials
set uspot.@credentials[-1].username={{ s(cred.username) }}
set uspot.@credentials[-1].password={{ s(cred.password) }}
set uspot.@credentials[-1].interface={{ s(section) }}
{% endfor %}
{% endif %}
{% if (config.auth_mode == 'uam'): %}
{%
let math = require('math');
let challenge = "";
for (let i = 0; i < 16; i++)
challenge += sprintf('%02x', math.rand() % 255);
%}
set uspot.{{ section }}.challenge={{ s(challenge) }}
set uspot.{{ section }}.uam_port={{ s(config.uam_port) }}
set uspot.{{ section }}.uam_secret={{ s(config.uam_secret) }}
set uspot.{{ section }}.uam_server={{ s(config.uam_server) }}
set uspot.{{ section }}.nasid={{ s(config.nasid) }}
set uspot.{{ section }}.nasmac={{ s(config.nasmac || serial) }}
set uspot.{{ section }}.ssid={{ s(config.ssid) }}
set uspot.{{ section }}.mac_format={{ s(config.mac_format) }}
set uspot.{{ section }}.final_redirect_url={{ s(config.final_redirect_url) }}
set uspot.{{ section }}.mac_auth={{ b(config.mac_auth) }}
set uhttpd.uam{{ config.uam_port }}=uhttpd
set uhttpd.@uhttpd[-1].redirect_https='0'
set uhttpd.@uhttpd[-1].rfc1918_filter='1'
set uhttpd.@uhttpd[-1].max_requests='5'
set uhttpd.@uhttpd[-1].max_connections='100'
set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt'
set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key'
set uhttpd.@uhttpd[-1].script_timeout='60'
set uhttpd.@uhttpd[-1].network_timeout='30'
set uhttpd.@uhttpd[-1].http_keepalive='20'
set uhttpd.@uhttpd[-1].tcp_keepalive='1'
add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:{{ config.uam_port }}'
add_list uhttpd.@uhttpd[-1].listen_http='[::]:{{ config.uam_port }}'
set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot
add_list uhttpd.@uhttpd[-1].ucode_prefix='/logon=/usr/share/uspot/handler-uam.uc'
add_list uhttpd.@uhttpd[-1].ucode_prefix='/logoff=/usr/share/uspot/handler-uam.uc'
add_list uhttpd.@uhttpd[-1].ucode_prefix='/logout=/usr/share/uspot/handler-uam.uc'
set firewall.{{ name + config.uam_port}}_1=rule
set firewall.@rule[-1].name='Allow-UAM-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='{{ config.uam_port }}'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].mark='1/127'
set firewall.{{ name + config.uam_port}}_2=rule
set firewall.@rule[-1].name='Allow-UAM-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='{{ config.uam_port }}'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].mark='2/127'
{% endif %}

View File

@@ -1,39 +0,0 @@
{% let afnames = ethernet.calculate_names(interface) %}
{% if (length(afnames) >= 2): %}
set network.{{ netdev }}=interface
set network.{{ netdev }}.ucentral_name={{ s(interface.name) }}
set network.{{ netdev }}.ucentral_path={{ s(location) }}
set network.{{ netdev }}.ifname={{ netdev }}
set network.{{ netdev }}.metric={{ interface.metric }}
set network.{{ netdev }}.proto=none
{% endif %}
{% for (let afidx, afname in afnames): %}
set network.{{ afname }}=interface
set network.{{ afname }}.ucentral_name={{ s(interface.name) }}
set network.{{ afname }}.ucentral_path={{ s(location) }}
set network.{{ afname }}.ifname={{ netdev }}
set network.{{ afname }}.metric={{ interface.metric }}
set network.{{ afname }}.mtu={{ interface.mtu }}
set network.{{ afname }}.type={{ interface.type }}
set network.{{ afname }}.auto={{ interface.auto_start }}
{% if (ipv4_mode == 'static' || ipv6_mode == 'static'): %}
set network.{{ afname }}.proto=static
{% elif ((length(afnames) == 1 || afidx == 0) && ipv4_mode == 'dynamic'): %}
set network.{{ afname }}.proto=dhcp
{% elif ((length(afnames) == 1 || afidx == 1) && ipv6_mode == 'dynamic'): %}
set network.{{ afname }}.proto=dhcpv6
{% else %}
set network.{{ afname }}.proto=none
{% endif %}
{% if (interface.role == "downstream" && ethernet.has_vlan(interface)): %}
add network rule
set network.@rule[-1].in={{ afname }}
set network.@rule[-1].lookup={{ routing_table.get(interface.vlan.id) }}
{% endif %}
{% if ((length(afnames) == 1 && ipv4_mode != 'none') || (afidx == 0 && ipv4_mode != 'none')): %}
{% include('ipv4.uc', { name: afname }) %}
{% endif %}
{% if ((length(afnames) == 1 && ipv6_mode != 'none') || (afidx == 1 && ipv6_mode != 'none')): %}
{% include('ipv6.uc', { name: afname }) %}
{% endif %}
{% endfor %}

View File

@@ -1,60 +0,0 @@
{% let name = ethernet.calculate_name(interface) %}
{% let dhcp = ipv4.dhcp || { ignore: 1 } %}
{% let dhcpv6 = ipv6.dhcpv6 || {} %}
set dhcp.{{ name }}=dhcp
set dhcp.{{ name }}.interface={{ s(ethernet.calculate_ipv4_name(interface)) }}
set dhcp.{{ name }}.start={{ dhcp.lease_first }}
set dhcp.{{ name }}.limit={{ dhcp.lease_count }}
set dhcp.{{ name }}.leasetime={{ dhcp.lease_time }}
set dhcp.{{ name }}.ignore={{ b(dhcp.ignore) }}
{% if (interface.role != 'upstream'): %}
{% if (dhcpv6.mode == 'hybrid'): %}
set dhcp.{{ name }}.ra=server
set dhcp.{{ name }}.dhcpv6=server
set dhcp.{{ name }}.ndp=disabled
set dhcp.{{ name }}.ra_slaac=1
add_list dhcp.{{ name }}.ra_flags=other-config
add_list dhcp.{{ name }}.ra_flags=managed-config
{% elif (dhcpv6.mode == 'stateful'): %}
set dhcp.{{ name }}.ra=server
set dhcp.{{ name }}.dhcpv6=server
set dhcp.{{ name }}.ndp=disabled
set dhcp.{{ name }}.ra_slaac=0
add_list dhcp.{{ name }}.ra_flags=other-config
add_list dhcp.{{ name }}.ra_flags=managed-config
{% elif (dhcpv6.mode == 'stateless'): %}
set dhcp.{{ name }}.ra=server
set dhcp.{{ name }}.dhcpv6=server
set dhcp.{{ name }}.ndp=disabled
set dhcp.{{ name }}.ra_slaac=1
add_list dhcp.{{ name }}.ra_flags=other-config
{% elif (dhcpv6.mode == 'relay'): %}
set dhcp.{{ name }}.ra=relay
set dhcp.{{ name }}.dhcpv6=relay
set dhcp.{{ name }}.ndp=relay
{% else %}
set dhcp.{{ name }}.ra=disabled
set dhcp.{{ name }}.dhcpv6=disabled
set dhcp.{{ name }}.ndp=disabled
{% endif %}
set dhcp.{{ name }}.prefix_filter={{ s(dhcpv6.filter_prefix) }}
set dhcp.{{ name }}.dns_service={{ b(!length(dhcpv6.announce_dns)) }}
{% for (let i, addr in dhcpv6.announce_dns): %}
add_list dhcp.{{ name }}.dns={{ s(addr) }}
{% endfor %}
{% else %}
set dhcp.{{ name }}.master={{ b(has_downstream_relays) }}
set dhcp.{{ name }}.ra={{ has_downstream_relays ? 'relay' : 'disabled' }}
set dhcp.{{ name }}.dhcpv6={{ has_downstream_relays ? 'relay' : 'disabled' }}
set dhcp.{{ name }}.ndp={{ has_downstream_relays ? 'relay' : 'disabled' }}
{% endif %}
{% for (let lease in interface.ipv4.dhcp_leases): %}
add dhcp host
set dhcp.@host[-1].hostname={{ lease.hostname }}
set dhcp.@host[-1].mac={{ lease.macaddr }}
set dhcp.@host[-1].ip={{ lease.static_lease_offset }}
set dhcp.@host[-1].leasetime={{ lease.lease_time }}
set dhcp.@host[-1].instance={{ s(name) }}
{% endfor %}

View File

@@ -1,171 +0,0 @@
add firewall zone
set firewall.@zone[-1].name={{ s(name) }}
{% if (interface.role == 'upstream'): %}
set firewall.@zone[-1].input='REJECT'
set firewall.@zone[-1].output='ACCEPT'
set firewall.@zone[-1].forward='REJECT'
set firewall.@zone[-1].masq=1
set firewall.@zone[-1].mtu_fix=1
{% else %}
set firewall.@zone[-1].input='REJECT'
set firewall.@zone[-1].output='ACCEPT'
set firewall.@zone[-1].forward='ACCEPT'
add firewall forwarding
set firewall.@forwarding[-1].src={{ s(name) }}
set firewall.@forwarding[-1].dest='{{ s(dest || ethernet.find_interface("upstream", interface.vlan.id)) }}'
{% endif %}
{% for (let network in networks || ethernet.calculate_names(interface)): %}
add_list firewall.@zone[-1].network={{ s(network) }}
{% endfor %}
add firewall rule
set firewall.@rule[-1].name='Allow-Ping'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].proto='icmp'
set firewall.@rule[-1].icmp_type='echo-request'
set firewall.@rule[-1].family='ipv4'
set firewall.@rule[-1].target='ACCEPT'
add firewall rule
set firewall.@rule[-1].name='Allow-IGMP'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].proto='igmp'
set firewall.@rule[-1].family='ipv4'
set firewall.@rule[-1].target='ACCEPT'
{% if (ipv4_mode || !ipv6_mode): %}
add firewall rule
set firewall.@rule[-1].name='Support-UDP-Traceroute'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].dest_port='33434:33689'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].family='ipv4'
set firewall.@rule[-1].target='REJECT'
set firewall.@rule[-1].enabled='false'
add firewall rule
set firewall.@rule[-1].name='Allow-DHCP-Renew'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].dest_port='68'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].family='ipv4'
{% endif %}
{% if (ipv6_mode || !ipv4_mode): %}
add firewall rule
set firewall.@rule[-1].name='Allow-DHCPv6'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].src_ip='fc00::/6'
set firewall.@rule[-1].dest_ip='fc00::/6'
set firewall.@rule[-1].dest_port='546'
set firewall.@rule[-1].family='ipv6'
set firewall.@rule[-1].target='ACCEPT'
add firewall rule
set firewall.@rule[-1].name='Allow-MLD'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].proto='icmp'
set firewall.@rule[-1].src_ip='fe80::/10'
set firewall.@rule[-1].icmp_type='130/0'
set firewall.@rule[-1].icmp_type='131/0'
set firewall.@rule[-1].icmp_type='132/0'
set firewall.@rule[-1].icmp_type='143/0'
set firewall.@rule[-1].family='ipv6'
set firewall.@rule[-1].target='ACCEPT'
add firewall rule
set firewall.@rule[-1].name='Allow-ICMPv6-Input'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].proto='icmp'
add_list firewall.@rule[-1].icmp_type='echo-request'
add_list firewall.@rule[-1].icmp_type='echo-reply'
add_list firewall.@rule[-1].icmp_type='destination-unreachable'
add_list firewall.@rule[-1].icmp_type='packet-too-big'
add_list firewall.@rule[-1].icmp_type='time-exceeded'
add_list firewall.@rule[-1].icmp_type='bad-header'
add_list firewall.@rule[-1].icmp_type='unknown-header-type'
add_list firewall.@rule[-1].icmp_type='router-solicitation'
add_list firewall.@rule[-1].icmp_type='neighbour-solicitation'
add_list firewall.@rule[-1].icmp_type='router-advertisement'
add_list firewall.@rule[-1].icmp_type='neighbour-advertisement'
set firewall.@rule[-1].limit='1000/sec'
set firewall.@rule[-1].family='ipv6'
set firewall.@rule[-1].target='ACCEPT'
add firewall rule
set firewall.@rule[-1].name='Allow-ICMPv6-Forward'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].dest='*'
set firewall.@rule[-1].proto='icmp'
add_list firewall.@rule[-1].icmp_type='echo-request'
add_list firewall.@rule[-1].icmp_type='echo-reply'
add_list firewall.@rule[-1].icmp_type='destination-unreachable'
add_list firewall.@rule[-1].icmp_type='packet-too-big'
add_list firewall.@rule[-1].icmp_type='time-exceeded'
add_list firewall.@rule[-1].icmp_type='bad-header'
add_list firewall.@rule[-1].icmp_type='unknown-header-type'
set firewall.@rule[-1].limit='1000/sec'
set firewall.@rule[-1].family='ipv6'
set firewall.@rule[-1].target='ACCEPT'
{% endif %}
{% if (interface.role == "downstream"): %}
add firewall rule
set firewall.@rule[-1].name='Allow-DNS-{{ name }}'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].dest_port='53'
set firewall.@rule[-1].family='ipv4'
add_list firewall.@rule[-1].proto='tcp'
add_list firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
add firewall rule
set firewall.@rule[-1].name='Allow-DHCP-{{ name }}'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].dest_port=67
set firewall.@rule[-1].family='ipv4'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
add firewall rule
set firewall.@rule[-1].name='Allow-DHCPv6-{{ name }}'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].dest_port=547
set firewall.@rule[-1].family='ipv6'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
{% endif %}
{%
for (let forward in interface.ipv4?.port_forward)
include('firewall/forward.uc', {
forward,
family: 'ipv4',
source_zone: ethernet.find_interface('upstream', interface.vlan?.id),
destination_zone: name,
destination_subnet: interface.ipv4.subnet
});
for (let forward in interface.ipv6?.port_forward)
include('firewall/forward.uc', {
forward,
family: 'ipv6',
source_zone: ethernet.find_interface('upstream', interface.vlan?.id),
destination_zone: name,
destination_subnet: interface.ipv6.subnet
});
for (let allow in interface.ipv6?.traffic_allow)
include('firewall/allow.uc', {
allow,
family: 'ipv6',
source_zone: ethernet.find_interface('upstream', interface.vlan?.id),
destination_zone: name,
destination_subnet: interface.ipv6.subnet
});
%}

View File

@@ -1,20 +0,0 @@
add firewall rule
set firewall.@rule[-1].name='Allow traffic to {{ allow.destination_address }}'
set firewall.@rule[-1].family={{ s(family) }}
set firewall.@rule[-1].src={{ s(source_zone || '*') }}
set firewall.@rule[-1].dest={{ s(destination_zone) }}
{% for (let proto in ((allow.protocol in ['any', 'all', '*'] && (allow.source_ports || allow.destination_ports)) ? ['tcp', 'udp'] : [ allow.protocol ])): %}
add_list firewall.@rule[-1].proto={{ s(proto) }}
{% endfor %}
{% if (allow.source_address): %}
set firewall.@rule[-1].src_ip={{ s(allow.source_address) }}
{% endif %}
{% for (let sport in allow.source_ports): %}
add_list firewall.@rule[-1].src_port={{ s(sport) }}
{% endfor %}
set firewall.@rule[-1].dest_ip={{ ipcalc.expand_wildcard_address(allow.destination_address, destination_subnet) }}
{% for (let dport in allow.destination_ports): %}
add_list firewall.@rule[-1].dest_port={{ s(dport) }}
{% endfor %}
set firewall.@rule[-1].target=ACCEPT

View File

@@ -1,15 +0,0 @@
{% if (true || source_zone): %}
add firewall redirect
set firewall.@redirect[-1].name='Forward port {{ forward.external_port }} to {{ forward.internal_address }}'
set firewall.@redirect[-1].family={{ s(family) }}
set firewall.@redirect[-1].src={{ s(source_zone || '*') }}
set firewall.@redirect[-1].dest={{ s(destination_zone) }}
{% for (let proto in ((forward.protocol in ['any', 'all', '*']) ? ['tcp', 'udp'] : [ forward.protocol ])): %}
add_list firewall.@redirect[-1].proto={{ s(proto) }}
{% endfor %}
set firewall.@redirect[-1].src_dport={{ s(forward.external_port) }}
set firewall.@redirect[-1].dest_ip={{ ipcalc.expand_wildcard_address(forward.internal_address, destination_subnet) }}
set firewall.@redirect[-1].dest_port={{ s(forward.internal_port) }}
set firewall.@redirect[-1].target=DNAT
{% endif %}

View File

@@ -1,45 +0,0 @@
{%
if (!interface.tunnel.peer_address) {
warn("A GRE tunnel requires a valid peer-address");
return;
}
%}
# GRE Configuration
set network.gre=interface
set network.gre.proto='gretap'
set network.gre.peeraddr='{{ interface.tunnel.peer_address }}'
set network.gre.nohostroute='1'
set network.gre.df='{{ b(interface.tunnel.dont_fragment) }}'
{%
let suffix = '';
let cfg = {
name: 'gretun',
netdev: 'gre4t-gre',
ipv4_mode, ipv4: interface.ipv4 || {},
ipv6_mode, ipv6: interface.ipv6 || {}
};
if (ethernet.has_vlan(interface)) {
cfg.name = 'gretun_' + interface.vlan.id;
cfg.netdev = 'gre4t-gre.' + interface.vlan.id;
cfg.this_vid = interface.vlan.id;
suffix = '.' + interface.vlan.id;
}
include("common.uc", cfg);
%}
add firewall rule
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].family='ipv4'
set firewall.@rule[-1].proto='47'
set firewall.@rule[-1].name='Allow-GRE-{{ name }}'
add network device
set network.@device[-1].name={{ s(name) }}
set network.@device[-1].type='bridge'
set network.@device[-1].ports='gre4t-gre{{ suffix }}'
set network.@device[-1].dhcp_healthcheck='{{ b(interface.tunnel.dhcp_healthcheck) }}'

View File

@@ -1,44 +0,0 @@
{%
if (!interface.tunnel.peer_address) {
warn("A GRE tunnel requires a valid peer-address");
return;
}
%}
# GRE Configuration
set network.greip6=interface
set network.greip6.proto='grev6tap'
set network.greip6.peer6addr='{{ interface.tunnel.peer_address }}'
set network.greip6.nohostroute='1'
{%
let suffix = '';
let cfg = {
name: 'gretun6',
netdev: 'gre6t-greip6',
ipv4_mode, ipv4: interface.ipv4 || {},
ipv6_mode, ipv6: interface.ipv6 || {}
};
if (ethernet.has_vlan(interface)) {
cfg.name = 'gretun6_' + interface.vlan.id;
cfg.netdev = 'gre6t-greip6.' + interface.vlan.id;
cfg.this_vid = interface.vlan.id;
suffix = '.' + interface.vlan.id;
}
include("common.uc", cfg);
%}
add firewall rule
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].src={{ s(name) }}
set firewall.@rule[-1].family='ipv6'
set firewall.@rule[-1].proto='47'
set firewall.@rule[-1].name='Allow-GREv6-{{ name }}'
add network device
set network.@device[-1].name={{ s(name) }}
set network.@device[-1].type='bridge'
set network.@device[-1].ports='gre6t-greip6{{ suffix }}'
set network.@device[-1].dhcp_healthcheck='{{ b(interface.tunnel.dhcp_healthcheck) }}'

View File

@@ -1,12 +0,0 @@
{% if (interface.role == 'upstream' && ethernet.has_vlan(interface)): %}
set network.{{ name }}.ip4table={{ routing_table.get(this_vid) }}
{% endif %}
{% if (ipv4_mode == 'static'): %}
set network.{{ name }}.ipaddr={{ ipv4.subnet }}
set network.{{ name }}.gateway={{ ipv4.gateway }}
{% else %}
set network.{{ name }}.peerdns={{ b(!length(ipv4.use_dns)) }}
{% endif %}
{% for (let dns in ipv4.use_dns): %}
add_list network.{{ name }}.dns={{ dns }}
{% endfor %}

View File

@@ -1,10 +0,0 @@
{% if (interface.role == 'upstream' && ethernet.has_vlan(interface)): %}
set network.{{ name }}.ip6table={{ routing_table.get(this_vid) }}
{% endif %}
{% if (ipv6_mode == 'static'): %}
set network.{{ name }}.ip6addr={{ ipv6.subnet }}
set network.{{ name }}.ip6gw={{ ipv6.gateway }}
set network.{{ name }}.ip6assign={{ ipv6.prefix_size || '64' }}
{% else %}
set network.{{ name }}.reqprefix={{ ipv6.prefix_size || 'auto' }}
{% endif %}

View File

@@ -1,31 +0,0 @@
{%
if (!interface.tunnel.server || !interface.tunnel.user_name || !interface.tunnel.password ) {
warn("A L2TP tunnel can only be created with a server, username and password");
return;
}
%}
# L2TP Configuration
set network.l2tp="interface"
set network.l2tp.proto="l2tp"
set network.l2tp.server={{ s(interface.tunnel.server) }}
set network.l2tp.username={{ s(interface.tunnel.user_name) }}
set network.l2tp.password={{ s(interface.tunnel.password) }}
set network.l2tp.ip4table="{{ routing_table.get(this_vid) }}"
add firewall zone
set firewall.@zone[-1].name="l2tp"
set firewall.@zone[-1].network="l2tp"
set firewall.@zone[-1].input="REJECT"
set firewall.@zone[-1].forward="REJECT"
set firewall.@zone[-1].output="REJECT"
set firewall.@zone[-1].masq="1"
set firewall.@zone[-1].mtu_fix="1"
add firewall forwarding
set firewall.@forwarding[-1].src="{{ name }}"
set firewall.@forwarding[-1].dest="l2tp"
add network rule
set network.@rule[-1].in="{{ name }}"
set network.@rule[-1].lookup="{{ routing_table.get(this_vid) }}"

View File

@@ -1,17 +0,0 @@
set network.batman=interface
set network.batman.proto=batadv
set network.batman.multicast_mode=0
set network.batman.distributed_arp_table=0
set network.batman.orig_interval=5000
{% if (ethernet.has_vlan(interface)): %}
set network.batman_v{{ this_vid }}=interface
set network.batman_v{{ this_vid }}.proto=batadv_vlan
set network.batman_v{{ this_vid }}.ifname='batman.{{ this_vid }}'
{% else %}
set network.batman_mesh=interface
set network.batman_mesh.proto=batadv_hardif
set network.batman_mesh.master=batman
set network.batman_mesh.mtu=1532
{% endif %}

View File

@@ -1,616 +0,0 @@
{%
let purpose = {
"onboarding-ap": {
"name": "OpenWifi-onboarding",
"isolate_clients": true,
"hidden": true,
"wifi_bands": [
"2G"
],
"bss_mode": "ap",
"encryption": {
"proto": "wpa2",
"ieee80211w": "required"
},
"certificates": {
"use_local_certificates": true
},
"radius": {
"local": {
"server-identity": "uCentral-EAP"
}
}
},
"onboarding-sta": {
"name": "OpenWifi-onboarding",
"wifi_bands": [
"2G"
],
"bss_mode": "sta",
"encryption": {
"proto": "wpa2",
"ieee80211w": "required"
},
"certificates": {
"use_local_certificates": true
}
}
};
if (purpose[ssid.purpose])
ssid = purpose[ssid.purpose];
let phys = [];
for (let band in ssid.wifi_bands)
for (let phy in wiphy.lookup_by_band(band))
if (phy.section)
push(phys, phy);
if (!length(phys)) {
warn("Can't find any suitable radio phy for SSID '%s' settings", ssid.name);
return;
}
if (type(ssid.roaming) == 'bool')
ssid.roaming = {
message_exchange: true
};
if (ssid.roaming && ssid.encryption.proto in [ "wpa", "psk", "none" ]) {
delete ssid.roaming;
warn("Roaming requires wpa2 or later");
}
let certificates = ssid.certificates || {};
if (certificates.use_local_certificates) {
cursor.load("system");
let certs = cursor.get_all("system", "@certificates[-1]");
certificates.ca_certificate = certs.ca;
certificates.certificate = certs.cert;
certificates.private_key = certs.key;
}
if (ssid.radius?.dynamic_authorization && 'radius-gw-proxy' in ssid.services) {
ssid.radius.dynamic_authorization.host = '127.0.0.1';
ssid.radius.dynamic_authorization.port = 3799;
}
function validate_encryption_ap() {
if (ssid.encryption.proto in [ "wpa", "wpa2", "wpa-mixed", "wpa3", "wpa3-mixed", "wpa3-192", "psk2-radius" ] &&
ssid.radius && ssid.radius.local &&
length(certificates))
return {
proto: ssid.encryption.proto,
eap_local: ssid.radius.local,
eap_user: "/tmp/ucentral/" + replace(location, "/", "_") + ".eap_user"
};
if (ssid.encryption.proto in [ "wpa", "wpa2", "wpa-mixed", "wpa3", "wpa3-mixed", "wpa3-192", "psk2-radius" ] &&
ssid.radius && ssid.radius.authentication &&
ssid.radius.authentication.host &&
ssid.radius.authentication.port &&
ssid.radius.authentication.secret)
return {
proto: ssid.encryption.proto,
auth: ssid.radius.authentication,
acct: ssid.radius.accounting,
health: ssid.radius.health || {},
dyn_auth: ssid.radius?.dynamic_authorization,
radius: ssid.radius
};
warn("Can't find any valid encryption settings");
return false;
}
function validate_encryption_sta() {
if (ssid.encryption.proto in [ "wpa", "wpa2", "wpa-mixed", "wpa3", "wpa3-mixed", "wpa3-192" ] &&
length(certificates))
return {
proto: ssid.encryption.proto,
client_tls: certificates
};
warn("Can't find any valid encryption settings");
return false;
}
function validate_encryption(phy) {
if ('6G' in phy.band && !(ssid?.encryption.proto in [ "wpa3", "wpa3-mixed", "wpa3-192", "sae", "sae-mixed", "owe" ])) {
warn("Invalid encryption settings for 6G band");
return null;
}
if (!ssid.encryption || ssid.encryption.proto in [ "none" ]) {
if (ssid.radius?.authentication?.mac_filter &&
ssid.radius.authentication?.host &&
ssid.radius.authentication?.port &&
ssid.radius.authentication?.secret)
return {
proto: 'none',
auth: ssid.radius.authentication,
acct: ssid.radius.accounting,
dyn_auth: ssid.radius?.dynamic_authorization,
health: ssid.radius.health || {},
radius: ssid.radius
};
return {
proto: 'none',
dyn_auth: ssid.radius?.dynamic_authorization,
};
}
if (ssid?.encryption?.proto in [ "owe", "owe-transition" ])
return {
proto: 'owe'
};
if (ssid.encryption.proto in [ "psk", "psk2", "psk-mixed", "sae", "sae-mixed" ] &&
ssid.encryption.key) {
if (ssid.radius?.authentication?.mac_filter &&
ssid.radius.authentication?.host &&
ssid.radius.authentication?.port &&
ssid.radius.authentication?.secret)
return {
proto: ssid.encryption.proto,
key: ssid.encryption.key,
auth: ssid.radius.authentication,
acct: ssid.radius.accounting,
dyn_auth: ssid.radius?.dynamic_authorization,
health: ssid.radius.health || {},
radius: ssid.radius
};
return {
proto: ssid.encryption.proto,
key: ssid.encryption.key,
dyn_auth: ssid.radius?.dynamic_authorization,
};
};
switch(ssid.bss_mode) {
case 'ap':
case 'wds-ap':
return validate_encryption_ap();
case 'sta':
case 'wds-sta':
return validate_encryption_sta();
}
warn("Can't find any valid encryption settings");
}
function match_ieee80211w(phy) {
if ('6G' in phy.band)
return 2;
if (!ssid.encryption)
return 0;
if (ssid.encryption.proto in [ "sae-mixed", "wpa3-mixed" ])
return 1;
if (ssid.encryption.proto in [ "sae", "wpa3", "wpa3-192" ])
return 2;
return index([ "disabled", "optional", "required" ], ssid.encryption.ieee80211w);
}
function match_sae_pwe(phy) {
if ('6G' in phy.band)
return 1;
return '';
}
function match_wds() {
return index([ "wds-ap", "wds-sta", "wds-repeater" ], ssid.bss_mode) >= 0;
}
function match_hs20_auth_type(auth_type) {
let types = {
"terms-and-conditions": "00",
"online-enrollment": "01",
"http-redirection": "02",
"dns-redirection": "03"
};
return (auth_type && auth_type.type) ? types[auth_type.type] : '';
}
function get_hs20_wan_metrics() {
if (!ssid.pass_point.wan_metrics ||
!ssid.pass_point.wan_metrics.info ||
!ssid.pass_point.wan_metrics.downlink ||
! ssid.pass_point.wan_metrics.uplink)
return '';
let map = {"up": 1, "down": 2, "testing": 3};
let info = map[ssid.pass_point.wan_metrics.info] ? map[ssid.pass_point.wan_metrics.info] : 1;
return sprintf("%02d:%d:%d:0:0:0", info, ssid.pass_point.wan_metrics.downlink, ssid.pass_point.wan_metrics.uplink);
}
let bss_mode = ssid.bss_mode;
if (ssid.bss_mode == "wds-ap")
bss_mode = "ap";
if (ssid.bss_mode == "wds-sta")
bss_mode = "sta";
function radius_vendor_tlv(server, port) {
let radius_serial = replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1-$2-$3-$4-$5-$6");
let radius_serial_len = length(radius_serial) + 2;
let radius_vendor = "26:x:0000e608" + // vendor element
"0113" + replace(radius_serial, /./g, (m) => sprintf("%02x", ord(m)));
let radius_ip = sprintf("%s:%s", server, port);
let radius_ip_len = length(radius_ip) + 2;
radius_vendor += "02" + sprintf("%02x", radius_ip_len) + replace(radius_ip, /./g, (m) => sprintf("%02x", ord(m)));
return radius_vendor;
}
function radius_proxy_tlv(server, port, name) {
let tlv = "33:x:" +
replace(replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1$2$3$4$5$6") + sprintf(":%s:%s:%s", server, port, name),
/./g, (m) => sprintf("%02x", ord(m)));
return tlv;
}
function radius_request_attribute(request) {
if (request.id && request.hex_value)
return sprintf('%d:x:%s', request.id, request.hex_value);
if (request.id && type(request.value) == 'string')
return sprintf('%d:s:%s', request.id, request.value);
if (request.id && type(request.value) == 'int')
return sprintf('%d:d:%d', request.id, request.value);
if (request.vendor_id && request.vendor_attributes) {
let tlv = sprintf('26:x:%04x', request.vendor_id);
for (let vsa in request.vendor_attributes)
tlv += sprintf('%02x%02x', vsa.type, length(vsa.value)) + vsa.id;
return tlv;
}
return '';
}
function calculate_ifname(name) {
if ('captive' in ssid.services)
return 'wlanc' + captive.get(name);
return '';
}
let radius_gw_proxy = ssid.services && (index(ssid.services, "radius-gw-proxy") >= 0);
if ('captive' in ssid.services && !ssid.captive)
ssid.captive = state?.services?.captive || {};
if (ssid.captive)
include("captive.uc", {
section: name + '_' + count,
config: ssid.captive
});
if (ssid.strict_forwarding)
services.set_enabled("bridger", 'early');
ssid.vendor_elements ??= '';
if (ssid.tip_information_element) {
if (state.unit?.beacon_advertisement) {
if (state.unit.beacon_advertisement.device_serial)
ssid.vendor_elements += 'dd1048d01701' + replace(serial, /./g, (m) => sprintf("%02x", ord(m)));
if (state.unit.beacon_advertisement.device_name && state.unit.name)
ssid.vendor_elements += 'dd' + sprintf('%02x', 4 + length(state.unit.name)) + '48d01702' + replace(state.unit.name, /./g, (m) => sprintf("%02x", ord(m)));
if (state.unit.beacon_advertisement.network_id) {
let id = sprintf('%d', state.unit.beacon_advertisement.network_id);
ssid.vendor_elements += 'dd' + sprintf('%02x', 4 + length(id)) + '48d01703' + replace(id, /./g, (m) => sprintf("%02x", ord(m)));
}
} else {
ssid.vendor_elements += 'dd0448d01700';
}
}
%}
# Wireless configuration
{% for (let n, phy in phys): %}
{% let basename = name + '_' + count; %}
{% let ssidname = basename + '_' + n + '_' + count; %}
{% let section = (owe ? 'o' : '' ) + ssidname; %}
{% let id = wiphy.allocate_ssid_section_id(phy) %}
{% let crypto = validate_encryption(phy); %}
{% let ifname = calculate_ifname(basename) %}
{% if (!crypto) continue; %}
set wireless.{{ section }}=wifi-iface
set wireless.{{ section }}.ucentral_path={{ s(location) }}
set wireless.{{ section }}.uci_section={{ s(section) }}
set wireless.{{ section }}.device={{ phy.section }}
{% if ('captive' in ssid.services): %}
set wireless.{{ section }}.ifname={{ s(ifname) }}
add_list uspot.{{ basename}}.ifname={{ ifname }}
add_list bridger.@defaults[0].blacklist={{ ifname }}
{% endif %}
{% if (ssid?.encryption?.proto == 'owe-transition'): %}
{% ssid.hidden_ssid = 1 %}
{% ssid.name += '-OWE' %}
set wireless.{{ section }}.ifname={{ s(section) }}
set wireless.{{ section }}.owe_transition_ifname={{ s('o' + section) }}
{% endif %}
{% if (owe): %}
set wireless.{{ section }}.ifname={{ s(section) }}
set wireless.{{ section }}.owe_transition_ifname={{ s(ssidname) }}
{% endif %}
{% if (bss_mode == 'mesh'): %}
set wireless.{{ section }}.mode={{ bss_mode }}
set wireless.{{ section }}.mesh_id={{ s(ssid.name) }}
set wireless.{{ section }}.mesh_fwding=0
set wireless.{{ section }}.network=batman_mesh
set wireless.{{ section }}.mcast_rate=24000
{% endif %}
{% if (index([ 'ap', 'sta' ], bss_mode) >= 0): %}
set wireless.{{ section }}.network={{ network }}
set wireless.{{ section }}.ssid={{ s(ssid.name) }}
set wireless.{{ section }}.mode={{ s(bss_mode) }}
set wireless.{{ section }}.bssid={{ ssid.bssid }}
set wireless.{{ section }}.wds='{{ b(match_wds()) }}'
set wireless.{{ section }}.wpa_disable_eapol_key_retries='{{ b(ssid.wpa_disable_eapol_key_retries) }}'
set wireless.{{ section }}.vendor_elements='{{ ssid.vendor_elements }}'
set wireless.{{ section }}.disassoc_low_ack='{{ b(ssid.disassoc_low_ack) }}'
set wireless.{{ section }}.auth_cache='{{ b(ssid.encryption?.key_caching) }}'
{% endif %}
{% if ('6G' in phy.band): %}
set wireless.{{ section }}.fils_discovery_max_interval={{ ssid.fils_discovery_interval }}
{% endif %}
# Crypto settings
set wireless.{{ section }}.ieee80211w={{ match_ieee80211w(phy) }}
set wireless.{{ section }}.sae_pwe={{ match_sae_pwe(phy) }}
set wireless.{{ section }}.encryption={{ crypto.proto }}
set wireless.{{ section }}.key={{ s(crypto.key) }}
{% if (crypto.eap_local): %}
set wireless.{{ section }}.eap_server=1
set wireless.{{ section }}.ca_cert={{ s(certificates.ca_certificate) }}
set wireless.{{ section }}.server_cert={{ s(certificates.certificate) }}
set wireless.{{ section }}.private_key={{ s(certificates.private_key) }}
set wireless.{{ section }}.private_key_passwd={{ s(certificates.private_key_password) }}
set wireless.{{ section }}.server_id={{ s(crypto.eap_local.server_identity) }}
set wireless.{{ section }}.eap_user_file={{ s(crypto.eap_user) }}
{% files.add_named(crypto.eap_user, render("../eap_users.uc", { users: crypto.eap_local.users })) %}
{% endif %}
{% if (crypto.auth): %}
{% if (radius_gw_proxy): %}
set wireless.{{ section }}.radius_gw_proxy=1
{% endif %}
set wireless.{{ section }}.auth_server={{ radius_gw_proxy ? '127.0.0.1' : crypto.auth.host }}
set wireless.{{ section }}.auth_port={{ radius_gw_proxy ? 1812 : crypto.auth.port }}
set wireless.{{ section }}.auth_secret={{ crypto.auth.secret }}
{% for (let request in crypto.auth.request_attribute): %}
add_list wireless.{{ section }}.radius_auth_req_attr={{ s(radius_request_attribute(request)) }}
{% endfor %}
{% if (radius_gw_proxy): %}
add_list wireless.{{ section }}.radius_auth_req_attr={{ s(radius_proxy_tlv(crypto.auth.host, crypto.auth.port, name + '_' + n + '_' + count)) }}
{% else %}
add_list wireless.{{ section }}.radius_auth_req_attr={{ s(radius_vendor_tlv(crypto.auth.host, crypto.auth.port)) }}
{% endif %}
{% if (crypto.auth.secondary): %}
set wireless.{{ section }}.auth_server_secondary={{ crypto.auth.secondary.host }}
set wireless.{{ section }}.auth_port_secondary={{ crypto.auth.secondary.port }}
set wireless.{{ section }}.auth_secret_secondary={{ crypto.auth.secondary.secret }}
{% endif %}
{% endif %}
{% if (crypto.acct): %}
set wireless.{{ section }}.acct_server={{ radius_gw_proxy ? '127.0.0.1' : crypto.acct.host }}
set wireless.{{ section }}.acct_port={{ radius_gw_proxy ? 1813 : crypto.acct.port }}
set wireless.{{ section }}.acct_secret={{ crypto.acct.secret }}
set wireless.{{ section }}.acct_interval={{ crypto.acct.interval }}
{% for (let request in crypto.acct.request_attribute): %}
add_list wireless.{{ section }}.radius_acct_req_attr={{ s(radius_request_attribute(request)) }}
{% endfor %}
{% if (radius_gw_proxy): %}
add_list wireless.{{ section }}.radius_acct_req_attr={{ s(radius_proxy_tlv(crypto.acct.host, crypto.acct.port, name + '_' + n + '_' + count)) }}
{% else %}
add_list wireless.{{ section }}.radius_acct_req_attr={{ s(radius_vendor_tlv(crypto.acct.host, crypto.acct.port)) }}
{% endif %}
{% if (crypto.acct.secondary): %}
set wireless.{{ section }}.acct_server_secondary={{ crypto.acct.secondary.host }}
set wireless.{{ section }}.acct_port_secondary={{ crypto.acct.secondary.port }}
set wireless.{{ section }}.acct_secret_secondary={{ crypto.acct.secondary.secret }}
{% endif %}
{% endif %}
{% if (crypto.health): %}
set wireless.{{ section }}.health_username={{ s(crypto.health.username) }}
set wireless.{{ section }}.health_password={{ s(crypto.health.password) }}
{% endif %}
{% if (crypto.dyn_auth): %}
set wireless.{{ section }}.dae_client={{ crypto.dyn_auth.host }}
set wireless.{{ section }}.dae_port={{ crypto.dyn_auth.port }}
set wireless.{{ section }}.dae_secret={{ crypto.dyn_auth.secret }}
set firewall.dyn_auth=rule
set firewall.dyn_auth.name='Allow-CoA'
set firewall.dyn_auth.src='{{ s(ethernet.find_interface("upstream", 0)) }}'
set firewall.dyn_auth.dest_port='{{ crypto.dyn_auth.port }}'
set firewall.dyn_auth.proto='udp'
set firewall.dyn_auth.target='ACCEPT'
{% endif %}
{% if (crypto.radius): %}
set wireless.{{ section }}.request_cui={{ b(crypto.radius.chargeable_user_id) }}
set wireless.{{ section }}.nasid={{ s(crypto.radius.nas_identifier) }}
set wireless.{{ section }}.dynamic_vlan=1
{% if (crypto.radius?.authentication?.mac_filter): %}
set wireless.{{ section }}.macfilter=radius
{% endif %}
{% endif %}
{% if (crypto.client_tls): %}
set wireless.{{ section }}.eap_type='tls'
set wireless.{{ section }}.ca_cert={{ s(certificates.ca_certificate) }}
set wireless.{{ section }}.client_cert={{ s(certificates.certificate)}}
set wireless.{{ section }}.priv_key={{ s(certificates.private_key) }}
set wireless.{{ section }}.priv_key_pwd={{ s(certificates.private_key_password) }}
set wireless.{{ section }}.identity='uCentral'
{% endif %}
{% if (interface.vlan_awareness?.first): %}
{% let vlan = interface.vlan_awareness.first;
if (interface.vlan_awareness.last)
vlan += '-' + interface.vlan_awareness.last; %}
set wireless.{{ section }}.network_vlan={{ vlan }}
{% endif %}
# AP specific setings
{% if (bss_mode == 'ap'): %}
set wireless.{{ section }}.proxy_arp={{ b(length(network) ? ssid.proxy_arp : false) }}
set wireless.{{ section }}.hidden={{ b(ssid.hidden_ssid) }}
set wireless.{{ section }}.time_advertisement={{ ssid.broadcast_time ? 2 : 0 }}
set wireless.{{ section }}.isolate={{ b(ssid.isolate_clients) }}
set wireless.{{ section }}.uapsd={{ b(ssid.power_save) }}
set wireless.{{ section }}.rts_threshold={{ ssid.rts_threshold }}
set wireless.{{ section }}.multicast_to_unicast={{ b(ssid.unicast_conversion) }}
set wireless.{{ section }}.maxassoc={{ ssid.maximum_clients }}
set wireless.{{ section }}.dtim_period={{ ssid.dtim_period }}
set wireless.{{ section }}.strict_forwarding={{ b(ssid.strict_forwarding) }}
{% if (interface?.vlan.id): %}
set wireless.{{ section }}.vlan_id={{ interface.vlan.id }}
{% endif %}
{% if (ssid.rate_limit): %}
set wireless.{{ section }}.ratelimit=1
{% endif %}
{% if (ssid.access_control_list?.mode): %}
set wireless.{{ section }}.macfilter={{ s(ssid.access_control_list.mode) }}
{% for (let mac in ssid.access_control_list.mac_address): %}
add_list wireless.{{ section }}.maclist={{ s(mac) }}
{% endfor %}
{% endif %}
{% if (ssid.rrm): %}
set wireless.{{ section }}.ieee80211k={{ b(ssid.rrm.neighbor_reporting) }}
set wireless.{{ section }}.rnr={{ b(ssid.rrm.reduced_neighbor_reporting) }}
set wireless.{{ section }}.ftm_responder={{ b(ssid.rrm.ftm_responder) }}
set wireless.{{ section }}.stationary_ap={{ b(ssid.rrm.stationary_ap) }}
set wireless.{{ section }}.lci={{ b(ssid.rrm.lci) }}
set wireless.{{ section }}.civic={{ ssid.rrm.civic }}
{% endif %}
{% if (ssid.roaming): %}
set wireless.{{ section }}.ieee80211r=1
set wireless.{{ section }}.ft_over_ds={{ b(ssid.roaming.message_exchange == "ds") }}
set wireless.{{ section }}.ft_psk_generate_local={{ b(ssid.roaming.generate_psk) }}
set wireless.{{ section }}.mobility_domain={{ ssid.roaming.domain_identifier }}
set wireless.{{ section }}.r0kh={{ s(ssid.roaming.pmk_r0_key_holder) }}
set wireless.{{ section }}.r1kh={{ s(ssid.roaming.pmk_r1_key_holder) }}
{% endif %}
{% if (ssid.quality_thresholds): %}
set wireless.{{ phy.section }}.rssi_reject_assoc_rssi={{ ssid.quality_thresholds.association_request_rssi }}
set wireless.{{ phy.section }}.rssi_ignore_probe_request={{ ssid.quality_thresholds.probe_request_rssi }}
{% if (ssid.quality_thresholds.probe_request_rssi): %}
set wireless.{{ section }}.hidden=1
set wireless.{{ section }}.dynamic_probe_resp=1
{% endif %}
set usteer2.{{ section }}=ssid
set usteer2.{{ section }}.client_kick_rssi={{ ssid.quality_thresholds.client_kick_rssi }}
set usteer2.{{ section }}.client_kick_ban_time={{ ssid.quality_thresholds.client_kick_ban_time }}
{% endif %}
{% for (let raw in ssid.hostapd_bss_raw): %}
add_list wireless.{{ section }}.hostapd_bss_options={{ s(raw) }}
{% endfor %}
{% if (ssid.pass_point): %}
set wireless.{{ section }}.iw_enabled=1
set wireless.{{ section }}.hs20=1
{% for (let name in ssid.pass_point.venue_name): %}
add_list wireless.{{ section }}.iw_venue_name={{ s(name) }}
{% endfor %}
set wireless.{{ section }}.iw_venue_group='{{ ssid.pass_point.venue_group }}'
set wireless.{{ section }}.iw_venue_type='{{ ssid.pass_point.venue_type }}'
{% for (let n, url in ssid.pass_point.venue_url): %}
add_list wireless.{{ section }}.iw_venue_url={{ s((n + 1) + ":" +url) }}
{% endfor %}
set wireless.{{ section }}.iw_network_auth_type='{{ match_hs20_auth_type(ssid.pass_point.auth_type) }}'
set wireless.{{ section }}.iw_domain_name={{ s(join(",", ssid.pass_point.domain_name)) }}
{% for (let realm in ssid.pass_point.nai_realm): %}
add_list wireless.{{ section }}.iw_nai_realm='{{ realm }}'
{% endfor %}
set wireless.{{ section }}.osen={{ b(ssid.pass_point.osen) }}
set wireless.{{ section }}.anqp_domain_id='{{ ssid.pass_point.anqp_domain }}'
{% for (let cell_net in ssid.pass_point.anqp_3gpp_cell_net): %}
add_list wireless.{{ section }}.iw_anqp_3gpp_cell_net='{{ s(cell_net) }}'
{% endfor %}
{% for (let name in ssid.pass_point.friendly_name): %}
add_list wireless.{{ section }}.hs20_oper_friendly_name={{ s(name) }}
{% endfor %}
set wireless.{{ section }}.iw_access_network_type='{{ ssid.pass_point.access_network_type }}'
set wireless.{{ section }}.iw_internet={{ b(ssid.pass_point.internet) }}
set wireless.{{ section }}.iw_asra={{ b(ssid.pass_point.asra) }}
set wireless.{{ section }}.iw_esr={{ b(ssid.pass_point.esr) }}
set wireless.{{ section }}.iw_uesa={{ b(ssid.pass_point.uesa) }}
set wireless.{{ section }}.iw_hessid={{ s(ssid.pass_point.hessid) }}
{% for (let name in ssid.pass_point.roaming_consortium): %}
add_list wireless.{{ section }}.iw_roaming_consortium={{ s(name) }}
{% endfor %}
set wireless.{{ section }}.disable_dgaf={{ b(ssid.pass_point.disable_dgaf) }}
set wireless.{{ section }}.hs20_release='3'
set wireless.{{ section }}.iw_ipaddr_type_availability={{ s(sprintf("%02x", ssid.pass_point.ipaddr_type_availability)) }}
{% for (let name in ssid.pass_point.connection_capability): %}
add_list wireless.{{ section }}.hs20_conn_capab={{ s(name) }}
{% endfor %}
set wireless.{{ section }}.hs20_wan_metrics={{ s(get_hs20_wan_metrics()) }}
{% endif %}
{% include("wmm.uc", { section }); %}
{% if (length(ssid.multi_psk)): %}
set wireless.{{ section }}.reassociation_deadline=3000
{% endif %}
{% if (ssid.pass_point): %}
{% for (let id, icon in ssid.pass_point.icons): %}
add wireless hs20-icon
set wireless.@hs20-icon[-1].width={{ s(icon.width) }}
set wireless.@hs20-icon[-1].height={{ s(icon.height) }}
set wireless.@hs20-icon[-1].type={{ s(icon.type) }}
set wireless.@hs20-icon[-1].lang={{ s(icon.language) }}
set wireless.@hs20-icon[-1].path={{ s(files.add_anonymous(location, 'hs20_icon_' + id, b64dec(icon.icon))) }}
{% endfor %}
{% endif %}
add wireless wifi-vlan
set wireless.@wifi-vlan[-1].iface={{ section }}
set wireless.@wifi-vlan[-1].name='v#'
set wireless.@wifi-vlan[-1].vid='*'
{% if (ssid.rate_limit && (ssid.rate_limit.ingress_rate || ssid.rate_limit.egress_rate)): %}
add ratelimit rate
set ratelimit.@rate[-1].ssid={{ s(ssid.name) }}
set ratelimit.@rate[-1].ingress={{ ssid.rate_limit.ingress_rate }}
set ratelimit.@rate[-1].egress={{ ssid.rate_limit.egress_rate }}
{% endif %}
{% for (let i = length(ssid.multi_psk); i > 0; i--): %}
{% let psk = ssid.multi_psk[i - 1]; %}
{% if (!psk.key) continue %}
add wireless wifi-station
set wireless.@wifi-station[-1].iface={{ s(section) }}
set wireless.@wifi-station[-1].mac={{ psk.mac }}
set wireless.@wifi-station[-1].key={{ psk.key }}
set wireless.@wifi-station[-1].vid={{ psk.vlan_id }}
{% endfor %}
{% else %}
# STA specific settings
{% endif %}
{% endfor %}

View File

@@ -1,35 +0,0 @@
{%
if (!interface.ipv4 || !interface.ipv4.subnet || interface.ipv4.addressing != 'static' ) {
warn("A VXLAN tunnel can only be created with a valid and static ivp4 address");
return;
}
if (!ethernet.has_vlan(interface)) {
warn("A VXLAN tunnel can only be created with a valid and static ivp4 address");
return;
}
if (!interface.tunnel.peer_address) {
warn("A VXLAN tunnel requires a valid peer-address");
return;
}
%}
# VXLAN Configuration
set network.{{ name }}_vx=interface
set network.{{ name }}_vx.proto=vxlan
set network.{{ name }}_vx.peeraddr={{ s(interface.tunnel.peer_address) }}
set network.{{ name }}_vx.port={{ interface.tunnel.peer_port }}
set network.{{ name }}_vx.vid={{ interface.vlan.id }}
set network.{{ name }}=interface
set network.{{ name }}.proto='static'
set network.{{ name }}.ifname='@{{ name }}_vx'
set network.{{ name }}.ipaddr={{ ipcalc.generate_prefix(state, interface.ipv4.subnet) }}
set network.{{ name }}.layer=2
set network.{{ name }}.type='bridge'
add firewall rule
set firewall.@rule[-1].name='Allow-VXLAN'
set firewall.@rule[-1].src='{{ s(ethernet.find_interface("upstream", 0)) }}'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].dest_port={{ interface.tunnel.peer_port }}

View File

@@ -1,125 +0,0 @@
{%
let wmm = state?.globals?.wireless_multimedia;
if (!length(wmm))
return;
let class = {
"CS0": 0,
"CS1": 8,
"CS2": 16,
"CS3": 24,
"CS4": 32,
"CS5": 40,
"CS6": 48,
"CS7": 56,
"AF11": 10,
"AF12": 12,
"AF13": 14,
"AF21": 18,
"AF22": 20,
"AF23": 22,
"AF31": 26,
"AF32": 28,
"AF33": 30,
"AF41": 34,
"AF42": 36,
"AF43": 38,
"EF": 46,
"VA": 44,
"LE": 1,
"DF": 0,
/* fake entry used by rfc8325 */
"MIN": 2
};
let profiles = {
"rfc8325": {
"defaults": {
"UP0": [ "MIN", "CS2" ],
"UP1": [ "LE" ],
"UP3": [ "AF21", "AF23" ],
"UP4": [ "CS3", "AF43" ],
"UP5": [ "CS5" ],
"UP6": [ "VA", "EF" ],
"UP7": [ "CS6", "CS7" ]
}
},
"3gpp": {
"defaults": {
"UP0": [ "DF" ],
"UP1": [ "CS1" ],
"UP2": [ "AF11", "AF13" ],
"UP3": [ "AF21", "AF23" ],
"UP4": [ "CS3", "AF33" ],
"UP5": [ "CS5", "AF43" ],
"UP6": [ "CS4" ],
"UP7": [ "CS6" ]
},
"exceptions": {
"UP6": [ "EF" ]
}
},
"enterprise": {
"defaults": {
"UP0": [ "DF" ],
"UP1": [ "CS1" ],
"UP2": [ "AF11", "AF13" ],
"UP3": [ "CS2", "AF23" ],
"UP4": [ "CS3", "AF33" ],
"UP5": [ "CS5" ],
"UP6": [ "CS4" ],
"UP7": [ "CS6" ]
},
"exceptions": {
"UP5": [ "AF41", "AF42", "AF43" ],
"UP6": [ "EF" ],
"UP7": [ "CS6" ]
}
}
};
function qos_map() {
let up_map = [];
if (wmm.profile)
wmm = profiles[wmm.profile];
if (!length(wmm.defaults))
wmm.defaults = { };
if (!length(wmm.exceptions))
wmm.exceptions = { };
for (let prio = 0; prio < 8; prio++) {
let up = wmm.exceptions["UP" + prio] || [];
let len = length(up);
if (!length(up))
continue;
for (let idx = 0; idx < len; idx++) {
push(up_map, class[up[idx]]);
push(up_map, prio);
}
}
for (let prio = 0; prio < 8; prio++) {
let up = wmm.defaults["UP" + prio];
if (length(up)) {
push(up_map, class[up[0]]);
push(up_map, class[up[1] || up[0]]);
} else {
push(up_map, 255);
push(up_map, 255);
}
}
let qos_map = join(",", up_map);
return qos_map;
}
%}
set wireless.{{ section }}.iw_qos_map_set={{ s(qos_map()) }}

View File

@@ -1,21 +0,0 @@
{% let interfaces = services.lookup_interfaces("dhcp-snooping") %}
{% let enable = length(interfaces) %}
{% if (!enable) return %}
# DHCP Snooping configuration
set event.dhcp=event
set event.dhcp.type=dhcp
set event.dhcp.filter='*'
{% for (let n, filter in dhcp_snooping.filters): %}
{{ n ? 'add_list' : 'set' }} event.dhcp.filter={{ filter }}
{% endfor %}
{% for (let interface in interfaces): %}
{% if (interface.role != "downstream") continue %}
{% let name = ethernet.calculate_name(interface) %}
add dhcpsnoop device
set dhcpsnoop.@device[-1].name={{ s(name) }}
set dhcpsnoop.@device[-1].ingress=1
set dhcpsnoop.@device[-1].egress=1
{% endfor %}

View File

@@ -1,11 +0,0 @@
{%
if (!health)
return;
%}
# Health configuration
set state.health.interval={{ health.interval }}
set state.health.dhcp_local={{ b(health.dhcp_local) }}
set state.health.dhcp_remote={{ b(health.dhcp_remote) }}
set state.health.dns_local={{ b(health.dns_local) }}
set state.health.dns_remote={{ b(health.dns_remote) }}

View File

@@ -1,7 +0,0 @@
{% if (!realtime) return %}
# Realtime event configuration
{% for (let real in realtime.types): %}
{% if (!(real in events)) continue; %}
add_list event.realtime.filter={{ real }}
{% endfor %}

View File

@@ -1,7 +0,0 @@
{% if (!statistics) return %}
# Statistics configuration
set state.stats.interval={{ statistics.interval }}
{% for (let statistic in statistics.types): %}
add_list state.stats.types={{ statistic }}
{% endfor %}

View File

@@ -1,8 +0,0 @@
{% if (!telemetry) return %}
# Telemetry streaming configuration
set event.bulk.interval={{ telemetry.interval }}
{% for (let type in telemetry.types): %}
{% if (!(type in events)) continue; %}
add_list event.bulk.filter={{ type }}
{% endfor %}

View File

@@ -1,9 +0,0 @@
{% if (!wifi_frames) return %}
# Wifi-frame reporting configuration
set event.wifi=event
set event.wifi.type=wifi
set event.wifi.filter='*'
{% for (let n, filter in wifi_frames.filters): %}
{{ n ? 'add_list' : 'set' }} event.wifi.filter={{ filter }}
{% endfor %}

View File

@@ -1,3 +0,0 @@
set event.wifiscan.interval={{ wifi_scan.interval }}
set event.wifiscan.verbose={{ b(wifi_scan.verbose) }}
set event.wifiscan.information_elements={{ b(wifi_scan.information_elements) }}

View File

@@ -1,206 +0,0 @@
{%
let phys = wiphy.lookup_by_band(radio.band);
if (!length(phys)) {
warn("Can't find any suitable radio phy for band %s radio settings", radio.band);
return;
}
function match_htmode(phy, radio) {
let channel_mode = radio.channel_mode;
let channel_width = radio.channel_width;
let fallback_modes = { EHT: /^(EHT|HE|VHT|HT)/, HE: /^(HE|VHT|HT)/, VHT: /^(VHT|HT)/, HT: /^HT/ };
let mode_weight = { HT: 1, VHT: 10, HE: 100, EHT: 1000 };
let wanted_mode = channel_mode + (channel_width == 8080 ? "80+80" : channel_width);
let supported_phy_modes = map(sort(map(phy.htmode, (mode) => {
let m = match(mode, /^([A-Z]+)(.+)$/);
return [ mode, mode_weight[m[1]] * (m[2] == "80+80" ? 159 : +m[2]) ];
}), (a, b) => (b[1] - a[1])), i => i[0]);
supported_phy_modes = filter(supported_phy_modes, mode =>
!(index(phy.band, "2G") >= 0 && mode == "VHT80"));
if (wanted_mode in supported_phy_modes)
return wanted_mode;
for (let supported_mode in supported_phy_modes) {
if (match(supported_mode, fallback_modes[channel_mode])) {
warn("Selected radio does not support requested HT mode %s, falling back to %s",
wanted_mode, supported_mode);
delete radio.channel;
return supported_mode;
}
}
warn("Selected radio does not support any HT modes");
die("Selected radio does not support any HT modes");
}
let channel_list = {
"320": [ 0 ],
"160": [ 36, 100 ],
"80": [ 36, 52, 100, 116, 132, 149 ],
"40": [ 36, 44, 52, 60, 100, 108,
116, 124, 132, 140, 149, 157, 165, 173,
184, 192 ]
};
if (!length(radio.valid_channels) && radio.band == "5G")
radio.valid_channels = [ 36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157, 165, 173, 184, 192 ];
if (!length(radio.valid_channels) && radio.band == "6G")
radio.valid_channels = [ 1, 2, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73,
77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141,
145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, 189, 193, 197, 201, 205,
209, 213, 217, 221, 225, 229, 233 ];
if (capab.country_code && !(radio.country in capab.country_code)) {
warn("Overriding country code to %s", capab.country_code[0]);
radio.country = capab.country_code[0];
}
if (length(restrict.country) && !(radio.country in restrict.country)) {
warn("Country code is restricted");
die("Country code is restricted");
}
function allowed_channel(phy, radio) {
if (restrict.dfs && radio.channel in phy.dfs_channels)
return false;
if (radio.channel_width == 20)
return true;
if (!channel_list[radio.channel_width])
return false;
if (!(radio.channel in channel_list[radio.channel_width]))
return false;
if (radio.valid_channels && !(radio.channel in radio.valid_channels))
return false;
return true;
}
function match_channel(phy, radio) {
let wanted_channel = radio.channel;
if (!wanted_channel || wanted_channel == "auto")
return 0;
if (index(phy.band, "5G") >= 0 && !allowed_channel(phy, radio)) {
warn("Selected radio does not support requested channel %d, falling back to ACS",
wanted_channel);
return 0;
}
if (wanted_channel in phy.channels)
return wanted_channel;
let min = (wanted_channel <= 14) ? 1 : 32;
let max = (wanted_channel <= 14) ? 14 : 196;
let eligible_channels = filter(phy.channels, (ch) => (ch >= min && ch <= max));
// try to find a channel next to the wanted one
for (let i = length(eligible_channels); i > 0; i--) {
let candidate = eligible_channels[i - 1];
if (candidate < wanted_channel || i == 1) {
warn("Selected radio does not support requested channel %d, falling back to %d",
wanted_channel, candidate);
return candidate;
}
}
warn("Selected radio does not support any channel in the target frequency range, falling back to %d",
phy.channels[0]);
return phy.channels[0];
}
function match_mimo(available_ant, wanted_mimo) {
if (!radio.mimo)
return available_ant;
let shift = ((available_ant & 0xf0) == available_ant) ? 4 : 0;
let m = match(wanted_mimo, /^([0-9]+)x([0-9]+$)/);
if (!m) {
warn("Failed to parse MIMO mode, falling back to %d", available_ant);
return available_ant;
}
let use_ant = 0;
for (let i = 0; i < m[1]; i++)
use_ant += 1 << i;
if (shift == 4)
switch(use_ant) {
case 0x1:
use_ant = 0x8;
break;
case 0x3:
use_ant = 0xc;
break;
case 0x7:
use_ant = 0xe;
break;
}
if (!use_ant || (use_ant << shift) > available_ant) {
warn("Invalid or unsupported MIMO mode %s specified, falling back to %d",
wanted_mimo || 'none', available_ant);
return available_ant;
}
return use_ant << shift;
}
function match_require_mode(require_mode) {
let modes = { HT: "n", VHT: "ac", HE: "ax" };
return modes[require_mode] || '';
}
if (restrict.dfs && radio.allow_dfs && radio.band == "5G") {
warn('DFS is restricted.');
radio.allow_dfs = false;
}
%}
# Wireless Configuration
{% for (let phy in phys): %}
{% let htmode = match_htmode(phy, radio) %}
set wireless.{{ phy.section }}.disabled=0
set wireless.{{ phy.section }}.ucentral_path={{ s(location) }}
set wireless.{{ phy.section }}.htmode={{ htmode }}
set wireless.{{ phy.section }}.channel={{ match_channel(phy, radio) }}
set wireless.{{ phy.section }}.txantenna={{ match_mimo(phy.tx_ant_avail, radio.mimo) }}
set wireless.{{ phy.section }}.rxantenna={{ match_mimo(phy.rx_ant_avail, radio.mimo) }}
set wireless.{{ phy.section }}.beacon_int={{ radio.beacon_interval }}
set wireless.{{ phy.section }}.country={{ s(radio.country) }}
set wireless.{{ phy.section }}.require_mode={{ s(match_require_mode(radio.require_mode)) }}
set wireless.{{ phy.section }}.txpower={{ radio.tx_power }}
set wireless.{{ phy.section }}.legacy_rates={{ b(radio.legacy_rates) }}
set wireless.{{ phy.section }}.chan_bw={{ radio.bandwidth }}
set wireless.{{ phy.section }}.maxassoc={{ radio.maximum_clients }}
set wireless.{{ phy.section }}.maxassoc_ignore_probe={{ b(radio.maximum_clients_ignore_probe) }}
set wireless.{{ phy.section }}.noscan=1
set wireless.{{ phy.section }}.reconf=1
set wireless.{{ phy.section }}.acs_exclude_dfs={{ b(!radio.allow_dfs) }}
{% for (let channel in radio.valid_channels): %}
{% if (!radio.allow_dfs && channel in phy.dfs_channels) continue %}
add_list wireless.{{ phy.section }}.channels={{ channel }}
{% endfor %}
{% if (radio.he_settings && phy.he_mac_capa && match(htmode, /HE.*/)): %}
set wireless.{{ phy.section }}.he_bss_color={{ radio.he_settings.bss_color }}
set wireless.{{ phy.section }}.multiple_bssid={{ b(radio.he_settings.multiple_bssid) }}
set wireless.{{ phy.section }}.ema={{ b(radio.he_settings.ema) }}
{% endif %}
{% if (radio.rates): %}
set wireless.{{ phy.section }}.basic_rate={{ radio.rates.beacon }}
set wireless.{{ phy.section }}.mcast_rate={{ radio.rates.multicast }}
{% endif %}
{% for (let raw in radio.hostapd_iface_raw): %}
add_list wireless.{{ phy.section }}.hostapd_options={{ s(raw) }}
{% endfor %}
{% if (radio.band == "6G"): %}
set wireless.{{ phy.section }}.he_co_locate={{ b(1) }}
{% endif %}
{% endfor %}

View File

@@ -1,15 +0,0 @@
{%
let enable = length(airtime_fairness);
services.set_enabled("atfpolicy", enable);
if (!enable)
return;
%}
set atfpolicy.@defaults[0].vo_queue_weight={{ airtime_fairness.voice_weight }}
set atfpolicy.@defaults[0].update_pkt_threshold={{ airtime_fairness.packet_threshold }}
set atfpolicy.@defaults[0].bulk_percent_thresh={{ airtime_fairness.bulk_threshold }}
set atfpolicy.@defaults[0].prio_percent_thresh={{ airtime_fairness.priority_threshold }}
set atfpolicy.@defaults[0].weight_normal={{ airtime_fairness.weight_normal }}
set atfpolicy.@defaults[0].weight_prio={{ airtime_fairness.weight_priority }}
set atfpolicy.@defaults[0].weight_bulk={{ airtime_fairness.weight_bulk }}

View File

@@ -1,84 +0,0 @@
{%
if (!services.is_present("spotfilter"))
return;
let interfaces = services.lookup_interfaces_by_ssids("captive");
let enable = length(interfaces);
if (enable && enable > 1) {
warn('captive portal can only run on a single interface');
enable = false;
}
services.set_enabled("spotfilter", enable);
services.set_enabled("uspot", enable);
if (!enable)
return;
%}
{% for (let interface in uniq(interfaces)): %}
{% let name = ethernet.calculate_name(interface) %}
add firewall redirect
set firewall.@redirect[-1].name='Redirect-captive-{{ name }}'
set firewall.@redirect[-1].src='{{ name }}'
set firewall.@redirect[-1].src_dport='80'
set firewall.@redirect[-1].proto='tcp'
set firewall.@redirect[-1].target='DNAT'
set firewall.@redirect[-1].mark='1/127'
add firewall rule
set firewall.@rule[-1].name='Allow-pre-captive-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='80'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].mark='1/127'
add firewall rule
set firewall.@rule[-1].name='Allow-captive-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='80'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].mark='2/127'
{% if (interface.role == 'downstream'): %}
add firewall rule
set firewall.@rule[-1].name='Allow-pre-captive-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest='{{ ethernet.find_interface("upstream", interface.vlan.id) }}'
set firewall.@rule[-1].proto='any'
set firewall.@rule[-1].target='DROP'
set firewall.@rule[-1].mark='1/127'
add firewall include
set firewall.@include[-1].type=restore
set firewall.@include[-1].family=ipv4
set firewall.@include[-1].path='/usr/share/uspot/firewall.ipt'
set firewall.@include[-1].reload=1
add firewall include
set firewall.@include[-1].type=restore
set firewall.@include[-1].family=ipv6
set firewall.@include[-1].path='/usr/share/uspot/firewall.ipt'
set firewall.@include[-1].reload=1
{% endif %}
{% endfor %}
add uhttpd uhttpd
set uhttpd.@uhttpd[-1].redirect_https='0'
set uhttpd.@uhttpd[-1].rfc1918_filter='1'
set uhttpd.@uhttpd[-1].max_requests='5'
set uhttpd.@uhttpd[-1].max_connections='100'
set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt'
set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key'
set uhttpd.@uhttpd[-1].script_timeout='60'
set uhttpd.@uhttpd[-1].network_timeout='30'
set uhttpd.@uhttpd[-1].http_keepalive='20'
set uhttpd.@uhttpd[-1].tcp_keepalive='1'
set uhttpd.@uhttpd[-1].no_dirlists='1'
add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:80'
add_list uhttpd.@uhttpd[-1].listen_http='[::]:80'
set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot
add_list uhttpd.@uhttpd[-1].ucode_prefix='/hotspot=/usr/share/uspot/handler.uc'
add_list uhttpd.@uhttpd[-1].ucode_prefix='/cpd=/usr/share/uspot/handler-cpd.uc'
add_list uhttpd.@uhttpd[-1].ucode_prefix='/env=/usr/share/uspot/handler-env.uc'
set uhttpd.@uhttpd[-1].error_page='/cpd'

View File

@@ -1,29 +0,0 @@
# Data Plane service configuration
{%
let iface = {};
function render(dict, type) {
for (let idx, filter in dict) {
%}
set dataplane.{{ filter.name }}=program
set dataplane.{{ filter.name }}.type={{ type }}
set dataplane.{{ filter.name }}.program={{ s(files.add_anonymous(location, 'ingress_' + idx, b64dec(filter.program))) }}
{%
for (let i in services.lookup_interfaces("data-plane:" + filter.name)) {
let name = ethernet.calculate_name(i);
if (!length(iface[name]))
iface[name] = [];
push(iface[name], filter.name);
}
}
}
render(data_plane.ingress_filters, "ingress");
%}
{% for (let k, v in iface): %}
set dataplane.{{ k }}=interface
{% for (let i, p in v): %}
add_list dataplane.{{ k }}.program={{ s(p) }}
{% endfor %}
{% endfor %}

View File

@@ -1,36 +0,0 @@
{%
if (!services.is_present("dhcprelay") || !dhcp_relay)
return;
let interfaces = services.lookup_interfaces("dhcp-relay");
let ports = ethernet.lookup_by_select_ports(dhcp_relay.select_ports);
let enable = length(interfaces) && length(ports);
services.set_enabled("dhcprelay", enable);
if (!enable)
return;
%}
# DHCP-relay service configuration
set firewall.dhcp_relay=rule
set firewall.dhcp_relay.name='Allow-DHCP-Relay'
set firewall.dhcp_relay.src='{{ s(ethernet.find_interface("upstream", 0)) }}'
set firewall.dhcp_relay.dest_port='67'
set firewall.dhcp_relay.family='ipv4'
set firewall.dhcp_relay.proto='udp'
set firewall.dhcp_relay.target='ACCEPT'
set dhcprelay.relay=bridge
set dhcprelay.relay.name=up
{% for (let vlan in dhcp_relay.vlans||[]): %}
add_list dhcprelay.relay.vlans={{ vlan.vlan }}
{% endfor %}
{% for (let port in ports): %}
add_list dhcprelay.relay.upstream={{ port }}
{% endfor %}
{% for (let vlan in dhcp_relay.vlans||[]): %}
set dhcprelay.vlan{{vlan.vlan}}=config
set dhcprelay.vlan{{vlan.vlan}}.server={{ s(vlan.relay_server) }}
set dhcprelay.vlan{{vlan.vlan}}.circuit_id={{ s(vlan?.circuit_id_format) }}
set dhcprelay.vlan{{vlan.vlan}}.remote_id={{ s(vlan?.remote_id_format) }}
{% endfor %}

View File

@@ -1,13 +0,0 @@
{% services.set_enabled("dhcpsnoop", true) %}
# DHCP Snooping configuration
{% for (let interface in state.interfaces): %}
{% if (interface.role != 'upstream') continue %}
{% for (let name in ethernet.lookup_by_interface_vlan(interface)): %}
add dhcpsnoop device
set dhcpsnoop.@device[-1].name={{ s(name) }}
set dhcpsnoop.@device[-1].ingress=1
set dhcpsnoop.@device[-1].egress=1
{% endfor %}
{% endfor %}

View File

@@ -1,48 +0,0 @@
{% if (!services.is_present("fbwifi")) return %}
{% let interfaces = services.lookup_interfaces("facebook-wifi") %}
{% let enable = length(interfaces) %}
{% services.set_enabled("fbwifi", enable) %}
{% if (!enable) return %}
# Facebook-wifi service configuration
set fbwifi.main.enabled=1
set fbwifi.main.zone={{ s(ethernet.calculate_name(interfaces[0])) }}
set fbwifi.main.gateway_token='FBWIFI:GATEWAY|{{ facebook_wifi.vendor_id }}|{{ facebook_wifi.gateway_id }}|{{ facebook_wifi.secret }}'
set uhttpd.main=uhttpd
add_list uhttpd.main.listen_http='0.0.0.0:80'
add_list uhttpd.main.listen_http='[::]:80'
add_list uhttpd.main.listen_https='0.0.0.0:443'
add_list uhttpd.main.listen_https='[::]:443'
set uhttpd.main.redirect_https='0'
set uhttpd.main.home='/www'
set uhttpd.main.max_requests='3'
set uhttpd.main.max_connections='100'
set uhttpd.main.cgi_prefix='/cgi-bin'
add_list uhttpd.main.lua_prefix='/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua'
set uhttpd.main.script_timeout='60'
set uhttpd.main.network_timeout='30'
set uhttpd.main.http_keepalive='20'
set uhttpd.main.tcp_keepalive='1'
set uhttpd.main.cert='/tmp/fbwifi/https_server_cert'
set uhttpd.main.key='/tmp/fbwifi/https_server_key'
set uhttpd.main.json_script='/usr/share/fbwifi/uhttpd.json'
set uhttpd.main.rfc1918_filter='0'
set firewall.fbwifi=include
set firewall.fbwifi.enabled=1
set firewall.fbwifi.family=ipv4
set firewall.fbwifi.path=/usr/share/fbwifi/firewall.include
set firewall.fbwifi.reload=1
set firewall.fbwifi.type=script
set uhttpd.fbwifi_redirect=uhttpd
set uhttpd.fbwifi_redirect.enabled=1
set uhttpd.fbwifi_redirect.listen_http='0.0.0.0:2060'
set uhttpd.fbwifi_redirect.listen_https='0.0.0.0:2061'
set uhttpd.fbwifi_redirect.cert='/tmp/fbwifi/https_server_cert'
set uhttpd.fbwifi_redirect.key='/tmp/fbwifi/https_server_key'
set uhttpd.fbwifi_redirect.json_script='/tmp/fbwifi/uhttpd-redirect.json'
add_list dhcp.@dnsmasq[0].rebind_domain=fbwifigateway.net

View File

@@ -1,11 +0,0 @@
{%-
let enable = length(gps);
services.set_enabled("umdns", enable);
if (!enable)
return;
%}
# Configure GPS
set gps.@gps[-1].disabled=0
set gps.@gps[-1].adjust_time={{ b(gps.adjust_time) }}
set gps.@gps[-1].baudrate={{ s(gps.baud_rate) }}

View File

@@ -1,36 +0,0 @@
{% if (!services.is_present("uhttpd")) return %}
{% let interfaces = services.lookup_interfaces("http") %}
{% let enable = length(interfaces) %}
{% services.set_enabled("uhttpd", enable) %}
{% services.set_enabled("rpcd", enable) %}
{% if (!enable) return %}
# HTTP service configuration
add uhttpd uhttpd
set uhttpd.@uhttpd[-1].redirect_https='0'
set uhttpd.@uhttpd[-1].home='/www'
set uhttpd.@uhttpd[-1].rfc1918_filter='1'
set uhttpd.@uhttpd[-1].max_requests='3'
set uhttpd.@uhttpd[-1].max_connections='100'
set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt'
set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key'
set uhttpd.@uhttpd[-1].cgi_prefix='/cgi-bin'
set uhttpd.@uhttpd[-1].lua_prefix='/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua'
set uhttpd.@uhttpd[-1].script_timeout='60'
set uhttpd.@uhttpd[-1].network_timeout='30'
set uhttpd.@uhttpd[-1].http_keepalive='20'
set uhttpd.@uhttpd[-1].tcp_keepalive='1'
set uhttpd.@uhttpd[-1].ubus_prefix='/ubus'
add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:{{ http.http_port || 80 }}'
{% let interfaces = services.lookup_interfaces("http") %}
{% for (let interface in interfaces): %}
{% let name = ethernet.calculate_name(interface) %}
add firewall rule
set firewall.@rule[-1].name='Allow-http-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].port='{{ http.http_port || 80 }}'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].target='ACCEPT'
{% endfor %}

View File

@@ -1,53 +0,0 @@
{%
if (!services.is_present("ieee8021x"))
return;
let interfaces = services.lookup_interfaces("ieee8021x");
let enable = length(interfaces);
if (ieee8021x.mode == "radius") {
if (!ieee8021x.radius.auth_server_addr ||
!ieee8021x.radius.auth_server_port ||
!ieee8021x.radius.auth_server_secret) {
warn(invalid radius configuration);
enable = false;
}
}
services.set_enabled("ieee8021x", enable);
if (!enable)
return;
let ports = [];
for (let p in ieee8021x.port_filter)
if (ethernet.ports[p])
push(ports, ethernet.ports[p].netdev);
cursor.load("system")
let certs = cursor.get_all("system", "@certificates[-1]")
%}
# IEEE8021x service configuration
add ieee8021x config
{% if (ieee8021x.mode == "radius"): %}
add ieee8021x config
set ieee8021x.@config[-1].nas_identifier={{ s(ieee8021x.radius.nas_identifier) }}
set ieee8021x.@config[-1].auth_server_addr={{ s(ieee8021x.radius.auth_server_addr) }}
set ieee8021x.@config[-1].auth_server_port={{ s(ieee8021x.radius.auth_server_port) }}
set ieee8021x.@config[-1].auth_server_secret={{ s(ieee8021x.radius.auth_server_secret) }}
set ieee8021x.@config[-1].acct_server_addr={{ s(ieee8021x.radius.acct_server_addr) }}
set ieee8021x.@config[-1].acct_server_port={{ s(ieee8021x.radius.acct_server_port) }}
set ieee8021x.@config[-1].acct_server_secret={{ s(ieee8021x.radius.acct_server_secret) }}
set ieee8021x.@config[-1].coa_server_addr={{ s(ieee8021x.radius.coa_server_addr) }}
set ieee8021x.@config[-1].coa_server_port={{ s(ieee8021x.radius.coa_server_port) }}
set ieee8021x.@config[-1].coa_server_secret={{ s(ieee8021x.radius.coa_server_secret) }}
{% else
files.add_named("/var/run/hostapd-ieee8021x.eap_user", render("../eap_users.uc", { users: ieee8021x.users })) %}
endif
%}
set ieee8021x.@config[-1].ca={{ s(certs.ca) }}
set ieee8021x.@config[-1].cert={{ s(certs.cert) }}
set ieee8021x.@config[-1].key={{ s(certs.key) }}
{% for (let port in ports): %}
add_list ieee8021x.@config[-1].ports={{ s(port) }}
set network.{{ replace(port, '.', '_') }}=device
set network.@device[-1].name={{ s(port) }}
set network.@device[-1].auth='1'
{% endfor %}

View File

@@ -1,20 +0,0 @@
{% if (!services.is_present("igmpproxy")) return %}
{% let interfaces = services.lookup_interfaces("igmp") %}
{% let enable = length(interfaces) %}
{% services.set_enabled("igmpproxy", enable) %}
{% if (!enable) return %}
# IGMP service configuration
{% let interfaces = services.lookup_interfaces("igmp") %}
{% for (let interface in interfaces): %}
{% if (!interface.ipv4) continue; %}
{% let name = ethernet.calculate_name(interface) %}
add igmpproxy phyint
set igmpproxy.@phyint[-1].network={{ name }}
set igmpproxy.@phyint[-1].zone={{ s((interface.role == "usptream") ? "wan" : name) }}
set igmpproxy.@phyint[-1].direction={{ s(interface.role) }}
{% if (interface.role == "upstream"): %}
set igmpproxy.@phyint[-1].altnet='0.0.0.0/0'
{% endif %}
{% endfor %}

View File

@@ -1,16 +0,0 @@
{% if (!services.is_present("lldpd")) return %}
{% let interfaces = services.lookup_interfaces("lldp") %}
{% let enable = length(interfaces) %}
{% services.set_enabled("lldpd", enable) %}
{% if (!enable) return %}
# LLDP service configuration
set lldpd.config.enable=1
set lldpd.config.lldp_description={{ s(lldp.describe) }}
set lldpd.config.lldp_location={{ s(lldp.location) }}
{% for (let interface in interfaces): %}
{% for (let port in ethernet.lookup_by_interface_spec(interface)): %}
add_list lldpd.config.interface={{ s(port) }}
{% endfor %}
{% endfor %}

View File

@@ -1,18 +0,0 @@
{%
if (!length(log)) return;
let hostname = state.unit?.hostname;
if (!hostname) {
cursor.load("system");
let system = cursor.get_all("system", "@system[-1]");
hostname = system?.hostname || OpenWifi;
}
%}
# Syslog service configuration
set system.@system[-1].log_ip={{ s(log.host) }}
set system.@system[-1].log_port={{ s(log.port) }}
set system.@system[-1].log_proto={{ s(log.proto) }}
set system.@system[-1].log_size={{ s(log.size) }}
set system.@system[-1].log_priority={{ s(log.priority) }}
set system.@system[-1].log_hostname={{ s(hostname) }}

View File

@@ -1,25 +0,0 @@
{% if (!services.is_present("umdns")) return %}
{% let interfaces = services.lookup_interfaces("mdns") %}
{% let enable = length(interfaces) %}
{% services.set_enabled("umdns", enable) %}
{% if (!enable) return %}
# MDNS service configuration
add umdns umdns
set umdns.@umdns[-1].enable=1
{% for (let interface in interfaces): %}
add_list umdns.@umdns[-1].network={{ s(ethernet.calculate_name(interface)) }}
{% endfor %}
{% for (let interface in interfaces): %}
{% let name = ethernet.calculate_name(interface) %}
add firewall rule
set firewall.@rule[-1].name='Allow-mdns-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='5353'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
{% endfor %}

View File

@@ -1,23 +0,0 @@
{%
if (!length(ntp))
return;
let interfaces = services.lookup_interfaces("ntp");
%}
delete system.ntp.server
set system.ntp.enabled={{ b(ntp.local_server) }}
set system.ntp.enable_server={{ b(ntp.servers) }}
{% for (let server in ntp.servers): %}
add_list system.ntp.server={{ s(server) }}
{% endfor
/* open the port on all interfaces that select ssh */
for (let interface in interfaces):
let name = ethernet.calculate_name(interface);
%}
add firewall rule
set firewall.@rule[-1].name='Allow-ntp-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='123'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
{% endfor %}

View File

@@ -1,27 +0,0 @@
{%
let enable = true;
if (!services.is_present("onlinecheck") ||
!length(online_check) ||
(!length(online_check.ping_hosts) &&
!length(online_check.download_hosts)))
enable = false;
services.set_enabled("onlinecheck", enable);
if (!enable)
return;
%}
# Online check service configuration
add onlinecheck config
set onlinecheck.@config[-1].check_interval={{ s(online_check.check_interval) }}
set onlinecheck.@config[-1].check_threshold={{ s(online_check.check_threshold) }}
{% for (let action in online_check.action): %}
add_list onlinecheck.@config[-1].action={{ s(action) }}
{% endfor %}
{% for (let host in online_check.ping_hosts): %}
add_list onlinecheck.@config[-1].ping_hosts={{ s(host) }}
{% endfor %}
{% for (let host in online_check.download_hosts): %}
add_list onlinecheck.@config[-1].download_hosts={{ s(host) }}
{% endfor %}

View File

@@ -1,87 +0,0 @@
{%
if (!quality_of_service)
quality_of_service = {};
let egress = ethernet.lookup_by_select_ports(quality_of_service.select_ports);
let enable = length(egress);
services.set_enabled("qosify", enable);
if (!enable)
return;
function get_speed(dev, speed) {
if (!speed)
speed = ethernet.get_speed(dev);
return speed;
}
function get_proto(proto) {
if (proto == "any")
return [ "udp", "tcp" ];
return [ proto ];
}
function get_range(port) {
if (port.range_end)
return sprintf("-%d", port.range_end)
}
let fs = require("fs");
let file = fs.open("/tmp/qosify.conf", "w");
for (let class in quality_of_service.classifier) {
for (let port in class.ports)
for (let proto in get_proto(port.protocol))
file.write(sprintf("%s:%d%s %s%s\n", proto, port.port,
port.range_end ? sprintf("-%d", port.range_end) : "",
port.reclassify ? "" : "+", class.dscp));
for (let fqdn in class.dns)
file.write(sprintf("dns:%s%s %s%s\n",
fqdn.suffix_matching ? "*." : "", fqdn.fqdn,
fqdn.reclassify ? "" : "+", class.dscp));
}
if (quality_of_service.services) {
let inputfile = fs.open('/usr/share/ucentral/qos.json', "r");
let db = json(inputfile.read("all"));
for (let k, v in db.classes) {
%}
set qosify.{{ k }}=class
set qosify.{{ k }}.ingress={{ s(v.ingress) }}
set qosify.{{ k }}.egress={{ s(v.egress) }}
set qosify.{{ k }}.bulk_trigger_pps={{ s(v.bulk_pps) }}
set qosify.{{ k }}.bulk_trigger_timeout={{ s(v.bulk_timeout) }}
set qosify.{{ k }}.dscp_bulk={{ s(v.bulk_dscp) }}
{%
}
let rules = [];
let all = 'all' in quality_of_service.services;
for (let k, v in db.services)
if (all || (k in quality_of_service.services))
for (let uses in v.uses)
push(quality_of_service.services, uses);
for (let k, v in db.services)
if (all || (k in quality_of_service.services)) {
for (let port in v.tcp)
push(rules, 'tcp:' + port + ' ' + v.classifier);
for (let port in v.udp)
push(rules, 'udp:' + port + ' ' + v.classifier);
for (let dns in v.fqdn)
push(rules, 'dns:' + dns + ' ' + v.classifier);
}
for (let rule in uniq(rules))
file.write(rule + '\n');
}
file.close();
%}
set qosify.@defaults[0].bulk_trigger_pps={{ quality_of_service?.bulk_detection?.packets_per_second || 0}}
set qosify.@defaults[0].dscp_bulk={{ quality_of_service?.bulk_detection?.dscp }}
{% for (let dev in egress): %}
set qosify.{{ dev }}=device
set qosify.{{ dev }}.name={{ s(dev) }}
set qosify.{{ dev }}.bandwidth_up='{{ get_speed(dev, quality_of_service.bandwidth_up) }}mbit'
set qosify.{{ dev }}.bandwidth_down='{{ get_speed(dev, quality_of_service.bandwidth_down) }}mbit'
{% endfor %}

View File

@@ -1,7 +0,0 @@
{%
if (!services.is_present("radius-gw-proxy"))
return;
let ssids = services.lookup_ssids("radius-gw-proxy");
let enable = length(ssids);
services.set_enabled("radius-gw-proxy", enable);
%}

View File

@@ -1,93 +0,0 @@
{% if (!services.is_present("radsecproxy")) return %}
{% let enable = (length(radius_proxy) && length(radius_proxy.realms)) %}
{% services.set_enabled("radsecproxy", enable) %}
{% if (!enable) return %}
add radsecproxy options
add_list radsecproxy.@options[-1].ListenUDP='localhost:1812'
add_list radsecproxy.@options[-1].ListenUDP='localhost:1813'
add radsecproxy client
set radsecproxy.@client[-1].name='client'
set radsecproxy.@client[-1].host='localhost'
set radsecproxy.@client[-1].type='udp'
set radsecproxy.@client[-1].secret={{ s(radius_proxy.proxy_secret) }}
{% for (idx, realm in radius_proxy.realms): %}
{% let certs = {};
if (realm.use_local_certificates) {
cursor.load("system");
certs = cursor.get_all("system", "@certificates[-1]");
} else if (realm.ca_certificate && realm.certificate && realm.private_key) {
certs.ca = files.add_anonymous(location, 'ca' + idx, b64dec(realm.ca_certificate));
certs.cert = files.add_anonymous(location, 'cert' + idx, b64dec(realm.certificate));
certs.key = files.add_anonymous(location, 'key' + idx, b64dec(realm.private_key));
} else if (realm.protocol == "radsec") {
warn("invalid certificate settings");
continue;
}
%}
{% if (realm.protocol == "radsec"): %}
set radsecproxy.tls{{ idx }}=tls
set radsecproxy.@tls[-1].name='tls{{ idx }}'
set radsecproxy.@tls[-1].CACertificateFile={{ s(certs.ca) }}
set radsecproxy.@tls[-1].certificateFile={{ s(certs.cert) }}
set radsecproxy.@tls[-1].certificateKeyFile={{ s(certs.key) }}
set radsecproxy.@tls[-1].certificateKeyPassword=''
set radsecproxy.server{{ idx }}=server
set radsecproxy.@server[-1].name='server{{ idx }}'
{% if (realm.auto_discover): %}
set radsecproxy.@server[-1].dynamicLookupCommand='/usr/libexec/naptr_lookup.sh'
{% else %}
set radsecproxy.@server[-1].host={{ s(realm.host) }}
set radsecproxy.@server[-1].port={{ s(realm.port) }}
set radsecproxy.@server[-1].secret={{ s(realm.secret) }}
{% endif %}
set radsecproxy.@server[-1].type='tls'
set radsecproxy.@server[-1].tls='tls{{ idx }}'
set radsecproxy.@server[-1].statusServer='0'
set radsecproxy.@server[-1].certificateNameCheck='0'
{% for (name in realm.realm): %}
add radsecproxy realm
set radsecproxy.@realm[-1].name='{{ name }}'
set radsecproxy.@realm[-1].server='server{{ idx }}'
set radsecproxy.@realm[-1].accountingServer='server{{ idx }}'
{% endfor %}
{% endif %}
{% if (realm.protocol == "radius"): %}
set radsecproxy.server{{ idx + "auth" }}=server
set radsecproxy.@server[-1].name='server{{ idx }}auth'
set radsecproxy.@server[-1].host={{ s(realm.auth_server) }}
set radsecproxy.@server[-1].port={{ s(realm.auth_port) }}
set radsecproxy.@server[-1].secret={{ s(realm.auth_secret) }}
set radsecproxy.@server[-1].type='udp'
{% if (realm.acct_server): %}
set radsecproxy.server{{ idx + "acct" }}=server
set radsecproxy.@server[-1].name='server{{ idx }}acct'
set radsecproxy.@server[-1].host={{ s(realm.acct_server) }}
set radsecproxy.@server[-1].port={{ s(realm.acct_port) }}
set radsecproxy.@server[-1].secret={{ s(realm.acct_secret) }}
set radsecproxy.@server[-1].type='udp'
{% endif %}
{% for (name in realm.realm): %}
add radsecproxy realm
set radsecproxy.@realm[-1].name='{{ name }}'
set radsecproxy.@realm[-1].server='server{{ idx }}auth'
{% if (realm.acct_server): %}
set radsecproxy.@realm[-1].accountingServer='server{{ idx }}acct'
{% endif %}
{% endfor %}
{% endif %}
{% if (realm.protocol == "block"): %}
{% for (name in realm.realm): %}
add radsecproxy realm
set radsecproxy.@realm[-1].name='{{ name }}'
set radsecproxy.@realm[-1].replyMessage={{ s(realm.message) }}
{% endfor %}
{% endif %}
{% endfor %}

View File

@@ -1,12 +0,0 @@
{%
if (!services.is_present("rrmd"))
return;
let interfaces = services.lookup_interfaces("mdns");
let enable = length(rrm);
services.set_enabled("rrmd", enable);
if (!enable)
return ;
%}
set rrmd.@base[0].beacon_request_assoc={{ rrm.beacon_request_assoc || 0 }}
set rrmd.@base[0].station_stats_interval={{ rrm.station_stats_interval || 0 }}

View File

@@ -1,12 +0,0 @@
{% if (!services.is_present("rtty")) return %}
{% let enable = length(rtty) %}
{% services.set_enabled("rtty", enable) %}
{% if (!enable) return %}
# RTTY service configuration
set rtty.@rtty[-1].enable={{ b((rtty.token && rtty.host && rtty.port)) }}
set rtty.@rtty[-1].token={{ s(rtty.token) }}
set rtty.@rtty[-1].host={{ s(rtty.host) }}
set rtty.@rtty[-1].port={{ s(rtty.port) }}
set rtty.@rtty[-1].ssl={{ b(rtty.mutual_tls) }}

View File

@@ -1,31 +0,0 @@
{%
let interfaces = services.lookup_interfaces("ssh");
let enable = length(interfaces);
if (restrict.ssh && enable) {
warn('SSH is restricted');
enable = false;
}
services.set_enabled("dropbear", enable);
if (!enable)
return;
files.add_named("/etc/dropbear/authorized_keys", join("\n", ssh.authorized_keys || []) + "\n");
%}
# SSH service configuration
set dropbear.@dropbear[-1].enable={{ b(enable) }}
set dropbear.@dropbear[-1].Port={{ s(ssh.port) }}
set dropbear.@dropbear[-1].PasswordAuth={{ b(ssh.password_authentication) }}
{% for (let interface in interfaces): %}
{% let name = ethernet.calculate_name(interface) %}
add firewall rule
set firewall.@rule[-1].name='Allow-ssh-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='{{ ssh.port }}'
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].target='ACCEPT'
{% endfor %}

View File

@@ -1,29 +0,0 @@
{% if (!services.is_present("usteer")) return %}
{% let ssids = services.lookup_ssids("wifi-steering") %}
{% let enable = length(ssids) %}
{% services.set_enabled("usteer", enable) %}
{% if (!enable) return %}
{% let name = wifi_steering.mode == 'local' ? ethernet.find_interface("upstream", 0) : '' %}
# Wifi-Steering service configuration
add usteer usteer
set usteer.@usteer[-1].network={{ s(name) }}
set usteer.@usteer[-1].ipv6={{ b(wifi_steering.ipv6) }}
set usteer.@usteer[-1].key={{ s(wifi_steering.key) }}
set usteer.@usteer[-1].assoc_steering={{ b(wifi_steering.assoc_steering) }}
set usteer.@usteer[-1].min_snr={{ wifi_steering.required_snr }}
set usteer.@usteer[-1].min_connect_snr={{ wifi_steering.required_probe_snr }}
set usteer.@usteer[-1].roam_scan_snr={{ wifi_steering.required_roam_snr }}
set usteer.@usteer[-1].load_kick_enabled={{ b(wifi_steering.load_kick_threshold) }}
set usteer.@usteer[-1].load_kick_threshold={{ wifi_steering.load_kick_threshold }}
set usteer.@usteer[-1].autochannel={{ b(wifi_steering.auto_channel) }}
{% for (let ssid in ssids): %}
add_list usteer.@usteer[-1].ssid_list={{ s(ssid.name) }}
{% endfor %}
add firewall rule
set firewall.@rule[-1].name='Allow-usteer-{{ name }}'
set firewall.@rule[-1].src='{{ name }}'
set firewall.@rule[-1].dest_port='16720'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'

View File

@@ -1,116 +0,0 @@
{%
let wireguard = length(services.lookup_interfaces("wireguard-overlay"));
let vxlan = length(services.lookup_interfaces("vxlan-overlay"));
if (!wireguard && !vxlan) {
services.set_enabled("unetd", false);
return;
}
if (wireguard + vlxan > 1) {
warn('only a single wireguard/vxlan-overlay is allowed\n');
services.set_enabled("unetd", false);
return;
}
if (!wireguard_overlay.root_node.key ||
!wireguard_overlay.root_node.endpoint ||
!wireguard_overlay.root_node.ipaddr) {
warn('root node is not configured correctly\n');
services.set_enabled("unetd", false);
return;
}
services.set_enabled("unetd", true);
let ips = [];
wireguard_overlay.root_node.name = "gateway";
wireguard_overlay.root_node.groups = [ "gateway" ];
for (let ip in wireguard_overlay.root_node.ipaddr)
push(ips, ip);
if (wireguard)
wireguard_overlay.root_node.subnet = [ '0.0.0.0/0' ];
latency.add(wireguard_overlay.root_node.endpoint, 4);
let cfg = {
'config': {
'port': wireguard_overlay.peer_port,
'peer-exchange-port': wireguard_overlay.peer_exchange_port,
'keepalive': 10
},
'hosts': {
gateway: wireguard_overlay.root_node,
}
};
let pipe = require('fs').popen(sprintf('echo "%s" | wg pubkey', wireguard_overlay.private_key));
let pubkey = replace(pipe.read("all"), '\n', '');
pipe.close();
for (let host in wireguard_overlay.hosts)
if (host.name) {
if (!host.name || !host.key) {
warn('host is not configured correctly\n');
return;
}
cfg.hosts[host.name] = host;
cfg.hosts[host.name].groups = [ 'ap' ];
if (host.key == pubkey)
continue;
for (let ip in host.ipaddr)
push(ips, ip);
}
if (vxlan) {
cfg.services = {
"l2-tunnel": {
"type": "vxlan",
"config": {
port: wireguard_overlay?.vxlan?.port || 4789,
},
"members": [ "gateway", "@ap" ]
}
};
if (wireguard_overlay?.vxlan?.isolate ?? true)
cfg.services['l2-tunnel'].config.forward_ports = [ "gateway" ];
}
system('rm /tmp/unet.*.json');
let filename = '/tmp/unet.' + time() + '.json';
files.add_named(filename, cfg);
include('../interface/firewall.uc', { name: 'unet', ipv4_mode: true, ipv6_mode: true, interface: { role: 'upstream' }, networks: [ 'unet' ] });
%}
# Wireguard Overlay Configuration
set network.unet=interface
set network.unet.proto=unet
set network.unet.device=unet
set network.unet.file={{ s(filename) }}
set network.unet.key={{ s(wireguard_overlay.private_key) }}
set network.unet.domain=unet
set network.unet.ip4table='{{ routing_table.get('wireguard_overlay') }}'
{% if (vxlan): %}
set network.unet.tunnels='vx-unet=l2-tunnel'
add firewall rule
set firewall.@rule[-1].name='Allow-VXLAN-unet'
set firewall.@rule[-1].src='unet'
set firewall.@rule[-1].proto='udp'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].dest_port={{ wireguard_overlay?.vxlan?.port || 3457 }}
{% endif %}
{% for (let ip in ips): %}
add network route
set network.@route[-1].interface='unet'
set network.@route[-1].target={{ s(ip) }}
set network.@route[-1].table='local'
{% latency.add(ip, 4) %}
{% endfor %}

View File

@@ -1,52 +0,0 @@
{%
let interfaces = services.lookup_interfaces_by_ssids("captive");
let enable = length(interfaces);
if (enable != 1)
return;
for (let name, data in captive.interfaces) {
let config = {
name,
devices: [],
config: {
default_class: 0,
default_dns_class: 1,
client_autoremove: false,
class: [
{
index: 0,
device_macaddr: split(name, '_')[0],
fwmark: 1,
fwmark_mask: 127
}, {
index: 1,
fwmark: 2,
fwmark_mask: 127
}
],
whitelist: [
{
"class": 1,
"hosts": [ ],
"address": [],
}
]
}
};
for (let iface in data.iface)
push(config.devices, 'wlanc' + iface);
for (let fqdn in data.walled_garden_fqdn)
push(config.config.whitelist[0].hosts, fqdn);
for (let ipaddr in data.walled_garden_ipaddr)
push(config.config.whitelist[0].address, ipaddr);
let fs = require('fs');
let file = fs.open('/tmp/spotfilter-' + name + '.json', 'w');
file.write(config);
file.close();
services.set_enabled("uhttpd", true)
}
%}

View File

@@ -1,17 +0,0 @@
{% if (state.switch.port_mirror && state.switch.port_mirror.monitor_ports && state.switch.port_mirror.analysis_port): %}
{%
let analysis = ethernet.lookup_by_select_ports([state.switch.port_mirror.analysis_port]);
ethernet.reserve_port(state.switch.port_mirror.analysis_port);
let mirrors = ethernet.lookup_by_select_ports(state.switch.port_mirror.monitor_ports);
%}
# Switch port-mirror configuration
set switch.mirror=port-mirror
{% for (let mirror in mirrors): %}
add_list switch.mirror.monitor={{ s(mirror) }}
{% endfor %}
set switch.mirror.analysis={{ s(analysis[0]) }}
{% endif %}

View File

@@ -1,149 +0,0 @@
{%
let fs = require('fs');
// reject the config if there is no valid upstream configuration
if (!state.uuid) {
warn('Configuration must contain a valid UUID. Rejecting whole file');
die('Configuration must contain a valid UUID. Rejecting whole file');
}
include('admin_ui.uc');
// reject the config if there is no valid upstream configuration
let upstream;
for (let i, interface in state.interfaces) {
if (interface.role != 'upstream')
continue;
upstream = interface;
}
if (!upstream) {
warn('Configuration must contain at least one valid upstream interface. Rejecting whole file');
die('Configuration must contain at least one valid upstream interface. Rejecting whole file');
}
for (let i, interface in state.interfaces)
interface.index = i;
/* find out which vlans are used and which should be assigned dynamically */
let vlans = [];
for (let i, interface in state.interfaces)
if (ethernet.has_vlan(interface))
push(vlans, interface.vlan.id);
else
interface.vlan = { id: 0};
// populate the broad-band profile if present. This needs to happen after the default vlans
// and before the dynamic vlan are assigned
let profile = local_profile.get();
if (profile && profile.broadband)
include('broadband.uc', { broadband: profile.broadband });
let vid = 4090;
function next_free_vid() {
while (index(vlans, vid) >= 0)
vid--;
return vid--;
}
/* dynamically assign vlan ids to all interfaces that have none yet */
for (let i, interface in state.interfaces)
if (!interface.vlan.id)
interface.vlan.dyn_id = next_free_vid();
/* dynamically assign vlans to all swconfig ports */
let swconfig = false;
for (let k, port in ethernet.ports) {
if (port.swconfig == null)
continue;
port.vlan = next_free_vid();
port.switch = capab.switch_ports[port.netdev];
port.netdev += '.' + port.vlan;
swconfig = true;
}
if (swconfig) {
ethernet.swconfig = {};
for (let k, port in ethernet.ports) {
if (!port.switch)
continue;
ethernet.swconfig[port.netdev] = port;
}
}
include('base.uc');
if (state.unit)
include('unit.uc', { location: '/unit', unit: state.unit });
if (!state.services)
state.services = {};
for (let service in services.lookup_services())
tryinclude('services/' + service + '.uc', {
location: '/services/' + service,
[service]: state.services[service] || {}
});
if (!state.metrics)
state.metrics = {};
let file = fs.open('/etc/events.json', 'r');
let events = [];
if (file) {
try {
events = json(file.read('all'));
} catch(e) {
}
file.close();
}
for (let metric in services.lookup_metrics())
tryinclude('metric/' + metric + '.uc', {
location: '/metric/' + metric,
[metric]: state.metrics[metric] || {},
events
});
if (state.switch)
tryinclude('switch.uc', {
location: '/switch/'
});
for (let i, ports in state.ethernet)
include('ethernet.uc', { location: '/ethernet/' + i, ports });
for (let i, radio in state.radios)
include('radio.uc', { location: '/radios/' + i, radio });
function iterate_interfaces(role) {
for (let i, interface in state.interfaces) {
if (interface.role != role)
continue;
include('interface.uc', { location: '/interfaces/' + i, interface, vlans });
}
}
iterate_interfaces("upstream");
iterate_interfaces("downstream");
let fs = require('fs');
for (let name in fs.glob('/usr/share/ucentral/templates/third-party/*.uc')) {
name = split(fs.basename(name), '.')[0];
let config = state.third_party ? state.third_party[name] : {};
tryinclude('third-party/' + name + '.uc', {
location: '/third-party/' + name,
[replace(name, '-', '_')]: config
});
}
services.set_enabled("usteer2", true);
include('spotfilter.uc');
if (state.config_raw)
include("config_raw.uc", { location: '/config_raw', config_raw: state.config_raw });
latency.write();
services.set_enabled("bridger", false);
%}

View File

@@ -1,19 +0,0 @@
# Basic unit configuration
{% if (unit.name): %}
set system.@system[-1].description={{ s(unit.name) }}
{% endif %}
{% if (unit.hostname): %}
set system.@system[-1].hostname={{ s(unit.hostname) }}
{% endif %}
{% if (unit.location): %}
set system.@system[-1].notes={{ s(unit.location) }}
{% endif %}
{% if (unit.timezone): %}
set system.@system[-1].timezone={{ s(unit.timezone) }}
{% endif %}
set system.@system[-1].leds_off={{ b(!unit.leds_active) }}
{%
shell.password(unit.random_password);
services.set_enabled("led", true);
%}

View File

@@ -1,118 +0,0 @@
#!/usr/bin/ucode
push(REQUIRE_SEARCH_PATH,
"/usr/lib/ucode/*.so",
"/usr/share/ucentral/*.uc");
let schemareader = require("schemareader");
let renderer = require("renderer");
let fs = require("fs");
let ubus = require("ubus").connect();
let inputfile = fs.open(ARGV[0], "r");
let inputjson = json(inputfile.read("all"));
let custom_config = (split(ARGV[0], ".")[0] != "/etc/ucentral/ucentral");
let error = 0;
inputfile.close();
let logs = [];
function set_service_state(state) {
for (let service, enable in renderer.services_state()) {
if (enable != state)
continue;
printf("%s %s\n", service, enable ? "starting" : "stopping");
system(sprintf("/etc/init.d/%s %s", service, (enable || enable == 'early') ? "restart" : "stop"));
}
system("/etc/init.d/dnsmasq restart");
}
try {
for (let cmd in [ 'rm -rf /tmp/ucentral',
'mkdir /tmp/ucentral',
'rm /tmp/dnsmasq.conf',
'/etc/init.d/spotfilter stop',
'touch /tmp/dnsmasq.conf' ])
system(cmd);
let state = schemareader.validate(inputjson, logs);
let batch = state ? renderer.render(state, logs) : "";
if (state.strict && length(logs)) {
push(logs, 'Rejecting config due to strict-mode validation');
state = null;
}
fs.stdout.write("Log messages:\n" + join("\n", logs) + "\n\n");
if (state) {
fs.stdout.write("UCI batch output:\n" + batch + "\n");
let outputjson = fs.open("/tmp/ucentral.uci", "w");
outputjson.write(batch);
outputjson.close();
for (let cmd in [ 'rm -rf /tmp/config-shadow',
'cp -r /etc/config-shadow /tmp' ])
system(cmd);
let apply = fs.popen("/sbin/uci -c /tmp/config-shadow batch", "w");
apply.write(batch);
apply.close();
renderer.write_files(logs);
set_service_state(false);
for (let cmd in [ 'uci -c /tmp/config-shadow commit',
'cp /tmp/config-shadow/* /etc/config/',
'rm -rf /tmp/config-shadow'])
system(cmd);
set_service_state('early');
ubus.call('state', 'reload');
for (let cmd in [ 'reload_config',
'/etc/init.d/ratelimit restart',
'/etc/init.d/dnsmasq restart',
'ubus call state reload'])
system(cmd);
if (!custom_config) {
fs.unlink('/etc/ucentral/ucentral.active');
fs.symlink(ARGV[0], '/etc/ucentral/ucentral.active');
}
set_service_state(true);
} else {
error = 1;
}
if (!length(batch) || !state)
error = 2;
else if (length(logs))
error = 1;
}
catch (e) {
error = 2;
warn("Fatal error while generating UCI: ", e, "\n", e.stacktrace[0].context, "\n");
}
if (inputjson.uuid && inputjson.uuid > 1 && !custom_config) {
let text = [ 'Success', 'Rejects', 'Failed' ];
let status = {
error,
text: text[error] || "Failed",
};
if (length(logs))
status.rejected = logs;
ubus.call("ucentral", "result", {
uuid: inputjson.uuid || 0,
id: +ARGV[1] || 0,
status,
});
if (error > 1)
exit(1);
}

View File

@@ -1,79 +0,0 @@
let nl = require("nl80211");
let def = nl.const;
const NL80211_IFTYPE_STATION = 2;
const NL80211_IFTYPE_AP = 3;
const NL80211_IFTYPE_MESH_POINT = 7;
let iftypes = {
[NL80211_IFTYPE_STATION]: "station",
[NL80211_IFTYPE_AP]: "ap",
[NL80211_IFTYPE_MESH_POINT]: "mesh",
};
const NL80211_CHAN_WIDTH_20 = 1;
const NL80211_CHAN_WIDTH_40 = 2;
const NL80211_CHAN_WIDTH_80 = 3;
const NL80211_CHAN_WIDTH_80P80 = 4;
const NL80211_CHAN_WIDTH_160 = 5;
const NL80211_CHAN_WIDTH_5 = 6;
const NL80211_CHAN_WIDTH_10 = 7;
let chwidth = {
[NL80211_CHAN_WIDTH_20]: "20",
[NL80211_CHAN_WIDTH_40]: "40",
[NL80211_CHAN_WIDTH_80]: "80",
[NL80211_CHAN_WIDTH_80P80]: "80p80",
[NL80211_CHAN_WIDTH_160]: "160",
[NL80211_CHAN_WIDTH_5]: "5",
[NL80211_CHAN_WIDTH_10]: "10",
};
function freq2channel(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 if (freq >= 5955 && freq <= 7115)
return (freq - 5950) / 5;
else
return (freq - 5000) / 5;
}
function wif_get(wdev) {
let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP);
if (res === false)
warn("Unable to lookup interfaces: " + nl.error() + "\n");
return res;
}
function lookup_wifs() {
let wifs = wif_get();
let rv = {};
for (let wif in wifs) {
if (!wif.wiphy_freq || !iftypes[wif.iftype])
continue;
let w = {};
w.ssid = wif.ssid;
w.bssid = wif.mac;
w.mode = iftypes[wif.iftype];
w.channel = [];
w.frequency = [];
w.tx_power = (wif.wiphy_tx_power_level / 100) || 0;
for (let f in [ wif.wiphy_freq, wif.center_freq1, wif.center_freq2 ])
if (f) {
push(w.channel, freq2channel(f));
push(w.frequency, f);
}
if (chwidth[wif.channel_width])
w.ch_width = chwidth[wif.channel_width];
rv[wif.ifname] = w;
}
return rv;
}
return lookup_wifs();

View File

@@ -1,40 +0,0 @@
let nl = require("nl80211");
let def = nl.const;
const NL80211_IFTYPE_MESH_POINT = 7;
function wif_get(wdev) {
let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP);
if (res === false)
warn("Unable to lookup interfaces: " + nl.error() + "\n");
return res;
}
function lookup_mesh() {
let wifs = wif_get();
let rv = {};
for (let wif in wifs) {
if (!wif.wiphy_freq || wif.iftype != NL80211_IFTYPE_MESH_POINT)
continue;
let w = [];
let params = { dev: wif.ifname };
let mpath = nl.request(def.NL80211_CMD_GET_MPATH, def.NLM_F_DUMP, params);
for (let path in mpath) {
push(w, {
destinantion: path.mac,
next_hop: path.mpath_next_hop,
metric: path.mpath_info.metric,
expire: path.mpath_info.expire,
discovery_timeout: path.mpath_info.discovery_timeout,
discovery_retries: path.mpath_info.discovery_retries,
hop_count: path.mpath_info.hop_count,
});
}
rv[wif.ifname] = w;
}
return rv;
}
return lookup_mesh();

View File

@@ -1,170 +0,0 @@
let fs = require('fs');
let uci = require("uci");
let cursor = uci ? uci.cursor() : null;
let nl = require("nl80211");
let def = nl.const;
function freq2channel(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 if (freq >= 5955 && freq <= 7115)
return (freq - 5950) / 5;
else
return (freq - 5000) / 5;
}
function phy_get(wdev) {
let res = nl.request(def.NL80211_CMD_GET_WIPHY, def.NLM_F_DUMP, { split_wiphy_dump: true });
if (res === false)
warn("Unable to lookup phys: " + nl.error() + "\n");
return res;
}
let paths = {};
function add_path(path, phy, index) {
if (!phy)
return;
phy = fs.basename(phy);
paths[phy] = path;
if (index)
paths[phy] += '+' + index;
}
function lookup_paths() {
let wireless = cursor.get_all('wireless');
for (let k, section in wireless) {
if (section['.type'] != 'wifi-device' || !section.path)
continue;
let phys = fs.glob(sprintf('/sys/devices/%s/ieee80211/phy*', section.path));
if (!length(phys))
phys = fs.glob(sprintf('/sys/devices/platform/%s/ieee80211/phy*', section.path));
if (!length(phys))
continue;
sort(phys);
let index = 0;
for (let phy in phys)
add_path(section.path, phy, index++);
}
}
function get_hwmon(phy) {
let hwmon = fs.glob(sprintf('/sys/class/ieee80211/%s/hwmon*/temp*_input', phy));
if (!hwmon)
return 0;
let file = fs.open(hwmon[0], 'r');
if (!file)
return 0;
let temp = +file.read('all');
file.close();
return temp;
}
function lookup_phys() {
lookup_paths();
let phys = phy_get();
let ret = {};
for (let phy in phys) {
let phyname = 'phy' + phy.wiphy;
let path = paths[phyname];
if (!path)
continue;
let p = {};
let temp = get_hwmon('phy' + phy.wiphy);
if (temp)
p.temperature = temp / 1000;
p.tx_ant = phy.wiphy_antenna_tx;
p.rx_ant = phy.wiphy_antenna_rx;
p.tx_ant_avail = phy.wiphy_antenna_avail_tx;
p.rx_ant_avail = phy.wiphy_antenna_avail_rx;
p.frequencies = [];
p.channels = [];
p.dfs_channels = [];
p.htmode = [];
p.band = [];
for (let band in phy.wiphy_bands) {
for (let freq in band?.freqs) {
if (freq.disabled)
continue;
push(p.frequencies, freq.freq);
push(p.channels, freq2channel(freq.freq));
if (freq.radar)
push(p.dfs_channels, freq2channel(freq.freq));
if (freq.freq >= 6000)
push(p.band, '6G');
else if (freq.freq <= 2484)
push(p.band, '2G');
else if (freq.freq >= 5160 && freq.freq <= 5885)
push(p.band, '5G');
}
if (band?.ht_capa) {
p.ht_capa = band.ht_capa;
push(p.htmode, 'HT20');
if (band.ht_capa & 0x2)
push(p.htmode, 'HT40');
}
if (band?.vht_capa) {
p.vht_capa = band.vht_capa;
push(p.htmode, 'VHT20', 'VHT40', 'VHT80');
let chwidth = (band?.vht_capa >> 2) & 0x3;
switch(chwidth) {
case 2:
push(p.htmode, 'VHT80+80');
/* fall through */
case 1:
push(p.htmode, 'VHT160');
}
}
for (let iftype in band?.iftype_data) {
if (iftype.iftypes?.ap) {
p.he_phy_capa = iftype?.he_cap_phy;
p.he_mac_capa = iftype?.he_cap_mac;
push(p.htmode, 'HE20');
let chwidth = (iftype?.he_cap_phy[0] || 0) & 0xff;
if (chwidth & 0x2 || chwidth & 0x4)
push(p.htmode, 'HE40');
if (chwidth & 0x4)
push(p.htmode, 'HE80');
if (chwidth & 0x8 || chwidth & 0x10)
push(p.htmode, 'HE160');
if (chwidth & 0x10)
push(p.htmode, 'HE80+80');
if (iftype.eht_cap_phy) {
p.eht_phy_capa = iftype?.eht_cap_phy;
p.eht_mac_capa = iftype?.eht_cap_mac;
push(p.htmode, 'EHT20');
if (chwidth & 0x2 || chwidth & 0x4)
push(p.htmode, 'EHT40');
if (chwidth & 0x4)
push(p.htmode, 'EHT80');
if (chwidth & 0x8 || chwidth & 0x10)
push(p.htmode, 'EHT160');
if (chwidth & 0x10)
push(p.htmode, 'EHT80+80');
if ('6G' in p.band)
push(p.htmode, 'EHT320');
}
}
}
}
p.band = uniq(p.band);
if (!length(p.dfs_channels))
delete p.dfs_channels;
ret[path] = p;
}
return ret;
}
return lookup_phys();

View File

@@ -1,103 +0,0 @@
let nl = require("nl80211");
let def = nl.const;
function parse_bitrate(info) {
let rate = {
bitrate: (info.bitrate32 || info.bitrate) * 100
};
if (info.short_gi)
rate.sgi = true;
if (info.mcs) {
rate.ht = true;
rate.mcs = info.mcs;
}
else if (info.vht_mcs) {
rate.vht = true;
rate.mcs = info.vht_mcs;
rate.nss = info.vht_nss;
}
else if (info.he_mcs) {
rate.he = true;
rate.mcs = info.he_mcs;
rate.nss = info.he_nss;
rate.he_gi = info.he_gi;
rate.he_dcm = info.he_dcm;
}
if (info.width_40)
rate.chwidth = 40;
else if (info.width_80)
rate.chwidth = 80;
else if (info.width_80p80)
rate.chwidth = 8080;
else if (info.width_160)
rate.chwidth = 160;
else if (info.width_10)
rate.chwidth = 10;
else if (info.width_5)
rate.chwidth = 5;
else
rate.chwidth = 20;
return rate;
}
function iface_assoclist(wif) {
let params = { dev: wif.ifname };
let res = nl.request(def.NL80211_CMD_GET_STATION, def.NLM_F_DUMP, params);
if (res === false) {
warn("Unable to lookup associations: " + nl.error() + "\n");
return [];
}
let assocdev = [];
for (let sta in res) {
let assoc = {
bssid: wif.mac,
station: sta.mac,
connected: +sta.sta_info?.connected_time,
inactive: +sta.sta_info?.inactive_time / 1000,
tx_duration: +sta.sta_info?.tx_duration,
rx_duration: +sta.sta_info?.rx_duration,
rssi: +sta.sta_info?.signal,
ack_signal: +sta.sta_info?.ack_signal,
ack_signal_avg: +sta.sta_info?.ack_signal_avg,
rx_packets: +sta.sta_info?.rx_packets,
tx_packets: +sta.sta_info?.tx_packets,
rx_bytes: +sta.sta_info?.rx_bytes64,
tx_bytes: +sta.sta_info?.tx_bytes64,
tx_retries: +sta.sta_info?.tx_retries,
tx_failed: +sta.sta_info?.tx_failed,
rx_rate: parse_bitrate(sta.sta_info?.rx_bitrate || {}),
tx_rate: parse_bitrate(sta.sta_info?.tx_bitrate || {}),
};
if (global.tid_stats)
assoc.tid_stats = sta.sta_info?.tid_stats || [];
push(assocdev, assoc);
};
return assocdev;
}
function wif_get(wdev) {
let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP);
if (res === false)
warn("Unable to lookup interfaces: " + nl.error() + "\n");
return res;
}
function lookup_stations() {
let rv = {};
let wifs = wif_get();
for (let wif in wifs) {
let assoc = iface_assoclist(wif);
if (length(assoc))
rv[wif.ifname] = assoc;
}
return rv;
}
return lookup_stations();

View File

@@ -1,36 +0,0 @@
let nl = require("nl80211");
let def = nl.const;
let rv = { survey: [] };
//let frequency = 5660;
function survey_get(dev) {
let res = nl.request(def.NL80211_CMD_GET_SURVEY, def.NLM_F_DUMP, { dev });
if (res === false)
warn("Unable to lookup survey: " + nl.error() + "\n");
return res;
}
function wif_get(wdev) {
let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP);
if (res === false)
warn("Unable to lookup interfaces: " + nl.error() + "\n");
return res;
}
function lookup_survey() {
let wifs = wif_get();
for (let wif in wifs)
for (let survey in survey_get(wif.ifname))
if (!frequency || survey.survey_info.frequency == frequency)
if (survey.survey_info?.time)
push(rv.survey, survey.survey_info);
}
lookup_survey();
//printf('%.J\n', rv);
return rv;

View File

@@ -1,15 +0,0 @@
description:
This section is used to define templates that can be referenced by a
configuration. This avoids duplication of data. A RADIUS server can be
defined here for example and then referenced by several SSIDs.
type: object
properties:
wireless-encryption:
type: object
description:
A dictionary of wireless encryption templates which can be referenced
by the corresponding property name.
patternProperties:
".+":
$ref: "https://ucentral.io/schema/v1/interface/ssid/encryption/"
additionalProperties: false

View File

@@ -1,28 +0,0 @@
type: array
items:
type: string
enum:
- CS0
- CS1
- CS2
- CS3
- CS4
- CS5
- CS6
- CS7
- AF11
- AF12
- AF13
- AF21
- AF22
- AF23
- AF31
- AF32
- AF33
- AF41
- AF42
- AF43
- DF
- EF
- VA
- LE

View File

@@ -1,12 +0,0 @@
type: object
additionalProperties: false
properties:
profile:
description:
Define a default profile that shall be used for the WMM behaviour of all SSIDs on
the device.
type: string
enum:
- enterprise
- rfc8325
- 3gpp

View File

@@ -1,23 +0,0 @@
description:
Define the default WMM behaviour of all SSIDs on the device. Each access
category can be assigned a default class selector that gets used for packet
matching.
type: object
additionalProperties: false
properties:
UP0:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP1:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP2:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP3:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP4:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP5:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP6:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
UP7:
$ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'

View File

@@ -19,10 +19,6 @@ properties:
format: uc-cidr6
examples:
- fdca:1234:4567::/48
wireless-multimedia:
anyOf:
- $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/table/'
- $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/profile/'
ipv4-blackhole:
description:
Define a list of non-interface specific BLACKHOLE (to-nowhere) routes.

View File

@@ -1,18 +0,0 @@
description:
The MAC ACL that defines which clients are allowed or denied to associations.
type: object
properties:
mode:
description:
Defines if this is an allow or deny list.
type: string
enum:
- allow
- deny
mac-address:
description:
Association requests will be denied if the rssi is below this threshold.
type: array
items:
type: string
format: uc-mac

View File

@@ -1,27 +0,0 @@
description:
When running a local EAP server or using STA/MESH to connect to another BSS
a set of certificates is required.
type: object
properties:
use-local-certificates:
description:
The device will use its local certificate bundle for the TLS setup and
ignores all other certificate options in this section.
type: boolean
default: false
ca-certificate:
description:
The local servers CA bundle.
type: string
certificate:
description:
The local servers certificate.
type: string
private-key:
description:
The local servers private key/
type: string
private-key-password:
description:
The password required to read the private key.
type: string

View File

@@ -1,48 +0,0 @@
description:
A device has certain properties that describe its identity and location.
These properties are described inside this object.
type: object
properties:
proto:
description:
The wireless encryption protocol that shall be used for this BSS
type: string
enum:
- none
- owe
- owe-transition
- psk
- psk2
- psk-mixed
- psk2-radius
- wpa
- wpa2
- wpa-mixed
- sae
- sae-mixed
- wpa3
- wpa3-192
- wpa3-mixed
examples:
- psk2
key:
description:
The Pre Shared Key (PSK) that is used for encryption on the BSS when
using any of the WPA-PSK modes.
type: string
maxLength: 63
minLength: 8
ieee80211w:
description:
Enable 802.11w Management Frame Protection (MFP) for this BSS.
type: string
enum:
- disabled
- optional
- required
default: disabled
key-caching:
description:
PMKSA created through EAP authentication and RSN preauthentication can be cached.
type: boolean
default: true

View File

@@ -1,23 +0,0 @@
type: object
description:
A SSID can have multiple PSK/VID mappings. Each one of them can be bound to a
specific MAC or be a wildcard.
properties:
mac:
type: string
format: uc-mac
key:
description:
The Pre Shared Key (PSK) that is used for encryption on the BSS when
using any of the WPA-PSK modes.
type: string
maxLength: 63
minLength: 8
vlan-id:
type: integer
maximum: 4096
examples:
- 3
- 100
- 200
- 4094

View File

@@ -1,209 +0,0 @@
description:
Enable Hotspot 2.0 support.
type: object
properties:
venue-name:
description:
This parameter can be used to configure one or more Venue Name Duples
for Venue Name ANQP information.
type: array
items:
type: string
venue-group:
description:
The available values are defined in 802.11u.
type: integer
maximum: 32
venue-type:
description:
The available values are defined in IEEE Std 802.11u-2011, 7.3.1.34
type: integer
maximum: 32
venue-url:
description:
This parameter can be used to configure one or more Venue URL Duples to
provide additional information corresponding to Venue Name information.
type: array
items:
type: string
format: uri
auth-type:
description:
This parameter indicates what type of network authentication is used in
the network.
type: object
properties:
type:
description:
Specifies the specific network authentication type in use.
type: string
enum:
- "terms-and-conditions"
- "online-enrollment"
- "http-redirection"
- "dns-redirection"
uri:
description:
Specifies the redirect URL applicable to the indicated authentication type.
type: string
format: uri
examples:
- https://operator.example.org/wireless-access/terms-and-conditions.html
- http://www.example.com/redirect/me/here/
minLength: 2
maxLength: 2
domain-name:
description:
The IEEE 802.11u Domain Name.
type: array
items:
type: string
format: hostname
nai-realm:
description:
NAI Realm information
type: array
items:
type: string
osen:
description:
OSU Server-Only Authenticated L2 Encryption Network;
type: boolean
anqp-domain:
description:
ANQP Domain ID, An identifier for a set of APs in an ESS that share the
same common ANQP information.
type: integer
maximum: 65535
minimum: 0
anqp-3gpp-cell-net:
description:
The ANQP 3GPP Cellular Network information.
type: array
items:
type: string
friendly-name:
description:
This parameter can be used to configure one or more Operator Friendly
Name Duples.
type: array
items:
type: string
access-network-type:
description:
Indicate the type of network. This is part of the interworking IE.
type: integer
maximum: 15
default: 0
internet:
description:
Whether the network provides connectivity to the Internet
type: boolean
default: true
asra:
description:
Additional Step Required for Access.
type: boolean
default: false
esr:
description:
Emergency services reachable.
type: boolean
default: false
uesa:
description:
Unauthenticated emergency service accessible.
type: boolean
default: false
hessid:
description:
Homogeneous ESS identifier
type: string
example: 00:11:22:33:44:55
roaming-consortium:
description:
Roaming Consortium OIs can be configured here. Each OI is between 3 and 15
octets and is configured as a hexstring.
type: array
items:
type: string
disable-dgaf:
description:
Disable Downstream Group-Addressed Forwarding. This can be used to configure
a network where no group-addressed frames are allowed.
type: boolean
default: false
ipaddr-type-available:
description:
IP Address Type Availability.
type: integer
maximum: 255
connection-capability:
description:
This can be used to advertise what type of IP traffic can be sent through the
hotspot.
type: array
items:
type: string
icons:
description:
The operator icons.
type: array
items:
type: object
properties:
width:
type: integer
description: The width of the operator icon in pixel
examples:
- 64
height:
type: integer
description: The height of the operator icon in pixel
examples:
- 64
type:
type: string
description: The mimetype of the operator icon
examples:
- image/png
icon:
type: string
format: uc-base64
description: The base64 encoded image
language:
type: string
description: ISO 639-2 language code of the icon
pattern: "^[a-z][a-z][a-z]$"
examples:
- eng
- fre
- ger
- ita
examples:
- width: 32
height: 32
type: image/png
language: eng
icon: R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7
wan-metrics:
description:
A description of the wan metric offered by this device.
type: object
properties:
info:
description:
The state of the devices uplink
type: string
enum:
- up
- down
- testing
downlink:
description:
Estimate of WAN backhaul link current downlink speed in kbps.
type: integer
uplink:
description:
Estimate of WAN backhaul link current uplink speed in kbps.
type: integer

Some files were not shown because too many files have changed in this diff Show More