Merge pull request #31 from Telecominfraproject/OLS-545-Removal-of-AP-specific-schema-elements-from-ols-ucentral-schema

[OLS-545] Removal of AP specific schema elements from ols-ucentral-sc…
This commit is contained in:
Mike Hansen
2025-01-09 12:42:12 -05:00
committed by GitHub
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