mirror of
https://github.com/Telecominfraproject/ols-ucentral-schema.git
synced 2025-10-29 17:22:23 +00:00
Compare commits
63 Commits
OLS-229-ge
...
v4.0.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f394cb4019 | ||
|
|
5e345b22a3 | ||
|
|
dcd935359c | ||
|
|
362e03a363 | ||
|
|
51c5b1b9f4 | ||
|
|
5d50740f98 | ||
|
|
01d4c80824 | ||
|
|
76cc0646e0 | ||
|
|
ffe61ea929 | ||
|
|
078c2021eb | ||
|
|
a1e044834b | ||
|
|
18d5b2c475 | ||
|
|
374fab81db | ||
|
|
6bc313b440 | ||
|
|
1d052a18c2 | ||
|
|
69bc3a60b7 | ||
|
|
cb0069a356 | ||
|
|
e1a110bc7f | ||
|
|
63d1103ef3 | ||
|
|
77b79d1007 | ||
|
|
b148155dea | ||
|
|
59ad89be0f | ||
|
|
8e32d2775a | ||
|
|
548b76a948 | ||
|
|
4f2a23741b | ||
|
|
44c07718e3 | ||
|
|
adeeb0457b | ||
|
|
0ed83ba0a5 | ||
|
|
a03b5620c5 | ||
|
|
d37fc0b7eb | ||
|
|
00403aa20a | ||
|
|
b5845fbd89 | ||
|
|
b6ac5a6450 | ||
|
|
3faa3421d3 | ||
|
|
7bffbb1cc9 | ||
|
|
e96efa25ae | ||
|
|
5622b66bb8 | ||
|
|
5bc20c20b3 | ||
|
|
9a7f469e61 | ||
|
|
a8d8de9b4d | ||
|
|
53c239b60d | ||
|
|
9a994374b5 | ||
|
|
44ed03b3f7 | ||
|
|
a72be45c21 | ||
|
|
7c62326155 | ||
|
|
5291c3da99 | ||
|
|
1b12452eeb | ||
|
|
76504b1ad6 | ||
|
|
bcde6a7155 | ||
|
|
82f5eb7740 | ||
|
|
ceccdef561 | ||
|
|
80a598fadf | ||
|
|
81e8cd5706 | ||
|
|
8a4815187f | ||
|
|
e8da89616e | ||
|
|
5da5b090be | ||
|
|
f9e15067ff | ||
|
|
4235960ab8 | ||
|
|
7e839b0681 | ||
|
|
1de6cad7e8 | ||
|
|
5dc634f78e | ||
|
|
4d03a432c1 | ||
|
|
ee945311e1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
/jsdoc.conf.json
|
||||
docs/
|
||||
node_modules
|
||||
|
||||
@@ -12,6 +12,39 @@ properties:
|
||||
Platform revision
|
||||
examples:
|
||||
- Rel 1.6 build 5
|
||||
version:
|
||||
type: object
|
||||
description:
|
||||
Switch version info, OLS release and schema.
|
||||
properties:
|
||||
switch:
|
||||
type: object
|
||||
description: the ols client version for this Switch
|
||||
properties:
|
||||
major:
|
||||
type: integer
|
||||
minor:
|
||||
type: integer
|
||||
patch:
|
||||
type: integer
|
||||
examples:
|
||||
- 'major': 3
|
||||
'minor': 2
|
||||
'patch': 0
|
||||
schema:
|
||||
type: object
|
||||
description: the ols schema version used with the ols client.
|
||||
properties:
|
||||
major:
|
||||
type: integer
|
||||
minor:
|
||||
type: integer
|
||||
patch:
|
||||
type: integer
|
||||
examples:
|
||||
- 'major': 3
|
||||
'minor': 2
|
||||
'patch': 0
|
||||
platform:
|
||||
type: string
|
||||
enum:
|
||||
@@ -124,6 +157,72 @@ properties:
|
||||
type: string
|
||||
examples:
|
||||
- Ethernet1
|
||||
mclag-capabilities:
|
||||
description: Capabilities of the MC-LAG (Multi-Chassis Link Aggregation) feature in the switch
|
||||
type: object
|
||||
properties:
|
||||
max-mclag-groups:
|
||||
description: Defines the maximum number of MC-LAG groups that can be configured on the switch.
|
||||
type: integer
|
||||
max-ports-per-mclag-group:
|
||||
description: Specifies the maximum number of physical ports that can be part of a single MC-LAG group.
|
||||
type: integer
|
||||
max-vlans-per-mclag-group:
|
||||
description: Indicates the maximum number of VLANs that can be supported within a single MC-LAG group.
|
||||
type: integer
|
||||
dual-active-detection:
|
||||
description: Describes the dual-active detection mechanism to prevent both switches from becoming active simultaneously.
|
||||
type: string
|
||||
enum:
|
||||
- ICCP
|
||||
- Backup-Link
|
||||
- None
|
||||
failover-time-milliseconds:
|
||||
description: Specifies the time (in milliseconds) required for traffic to fail over to the secondary switch when there is a failure in the primary switch.
|
||||
type: integer
|
||||
vlan-synchronization:
|
||||
description: Indicates whether VLAN synchronization across MC-LAG peers is supported and the number of VLANs that can be synchronized.
|
||||
type: boolean
|
||||
max-mac-entries-per-mclag:
|
||||
description: Maximum number of MAC address entries that can be synchronized across MC-LAG peers.
|
||||
type: integer
|
||||
lldp-capabilities:
|
||||
type: object
|
||||
description:
|
||||
Description of LLDP capabilities across different switch models/vendors.
|
||||
properties:
|
||||
supported-tlvs:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- lldp-basic-tlv-mgmt-ip-v4
|
||||
- lldp-basic-tlv-mgmt-ip-v6
|
||||
- lldp-basic-tlv-port-descr
|
||||
- lldp-basic-tlv-sys-capab
|
||||
- lldp-basic-tlv-sys-descr
|
||||
- lldp-basic-tlv-sys-name
|
||||
- lldp-dot1-tlv-proto-ident
|
||||
- lldp-dot1-tlv-proto-vid
|
||||
- lldp-dot1-tlv-pvid
|
||||
- lldp-dot1-tlv-vlan-name
|
||||
- lldp-dot3-tlv-link-agg
|
||||
- lldp-dot3-tlv-mac-phy
|
||||
- lldp-dot3-tlv-max-frame
|
||||
- lldp-dot3-tlv-poe
|
||||
- lldp-med-location-civic-addr
|
||||
- lldp-med-tlv-ext-poe
|
||||
- lldp-med-tlv-inventory
|
||||
- lldp-med-tlv-location
|
||||
- lldp-med-tlv-med-cap
|
||||
- lldp-med-tlv-network-policy
|
||||
max-neighbors:
|
||||
type: integer
|
||||
description: Maximum number of LLDP neighbors a switch can discover and maintain.
|
||||
examples:
|
||||
- 64
|
||||
- 128
|
||||
- 256
|
||||
supported-features:
|
||||
type: array
|
||||
description:
|
||||
@@ -133,6 +232,11 @@ properties:
|
||||
enum:
|
||||
# L2
|
||||
- VLAN
|
||||
- VLAN-Voice
|
||||
- Jumbo-Frames
|
||||
- Link-Aggregation-LACP
|
||||
- Link-Aggregation-Static
|
||||
- Link-Aggregation-MCLAG
|
||||
- Port-Isolation
|
||||
- Spanning-Tree
|
||||
- Spanning-Tree-Rapid
|
||||
@@ -160,16 +264,22 @@ properties:
|
||||
- Routing-IPv6-DHCP-Stateful
|
||||
- Routing-IPv6-DHCP-Stateless
|
||||
- Routing-IPv6-Port-Forward
|
||||
- Multicast-VLAN-Registration
|
||||
# PoE
|
||||
- PoE-Reset
|
||||
# .1X
|
||||
- Port-Access-Control
|
||||
- PAC-Dynamic-Auth
|
||||
- mac-address-bypass
|
||||
# System
|
||||
- System-PasswordChange
|
||||
- System-SwUpdate
|
||||
- System-SwUpdate-Partial
|
||||
- Port-Mirroring
|
||||
# Security
|
||||
- MAC-ACL
|
||||
- IP-ACL
|
||||
- Guest-VLAN
|
||||
# Services
|
||||
- Service-SSH
|
||||
- Service-RSSH
|
||||
@@ -180,6 +290,7 @@ properties:
|
||||
- Service-GPS
|
||||
- Service-IGMP
|
||||
- Service-NTP
|
||||
- Service-NTP-Client
|
||||
- Service-MDNS
|
||||
- Service-QoS
|
||||
- Service-Syslog
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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");
|
||||
@@ -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"
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
});
|
||||
@@ -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'});
|
||||
@@ -1 +0,0 @@
|
||||
include("./state.uc", { stats: args.stats });
|
||||
@@ -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
|
||||
});
|
||||
@@ -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"
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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
181
docs/schema_doc.css
Normal 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
1
docs/schema_doc.min.js
vendored
Normal 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
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
28
docs/ucentral-state.html
Normal file
File diff suppressed because one or more lines are too long
25
docs/ucentral.capabilities.html
Normal file
25
docs/ucentral.capabilities.html
Normal file
File diff suppressed because one or more lines are too long
@@ -10,7 +10,8 @@ set -x
|
||||
./merge-schema.py capabilities capabilities connect.capabilities.yml ucentral.capabilities.pretty.json 0 1
|
||||
#./generate-reader.uc > schemareader.uc
|
||||
#./generate-example.uc > input.json
|
||||
mkdir -p docs
|
||||
which generate-schema-doc > /dev/null
|
||||
generate-schema-doc --config expand_buttons=true ucentral.schema.pretty.json docs/ucentral-schema.html
|
||||
generate-schema-doc --config expand_buttons=true ucentral.state.pretty.json docs/ucentral-state.html
|
||||
generate-schema-doc --config expand_buttons=true ucentral.capabilities.pretty.json docs/ucentral.capabilities.html
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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 ]
|
||||
}
|
||||
}
|
||||
}
|
||||
1172
renderer/renderer.uc
1172
renderer/renderer.uc
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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 %}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
%}
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
# Raw Configuration
|
||||
{% for (let config in config_raw): %}
|
||||
{{ config[0] }} {{ config[1] }}{{config[2] ? '=' + config[2] : ''}}
|
||||
{% endfor %}
|
||||
@@ -1,4 +0,0 @@
|
||||
{% for (let user in users): %}
|
||||
"{{ user.user_name }}" PWD "{{ user.password }}"
|
||||
{% endfor %}
|
||||
* TLS,TTLS
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 }); %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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
|
||||
});
|
||||
%}
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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) }}'
|
||||
@@ -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) }}'
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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) }}"
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 }}
|
||||
@@ -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()) }}
|
||||
@@ -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 %}
|
||||
@@ -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) }}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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) }}
|
||||
@@ -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 %}
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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'
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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
|
||||
@@ -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) }}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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) }}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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);
|
||||
%}
|
||||
@@ -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 %}
|
||||
@@ -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 }}
|
||||
@@ -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) }}
|
||||
@@ -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 %}
|
||||
@@ -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'
|
||||
@@ -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 %}
|
||||
@@ -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)
|
||||
}
|
||||
%}
|
||||
@@ -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 %}
|
||||
@@ -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);
|
||||
%}
|
||||
@@ -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);
|
||||
%}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
5
schema.json
Normal file
5
schema.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
}
|
||||
@@ -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
|
||||
@@ -18,6 +18,13 @@ properties:
|
||||
- LAN*
|
||||
- WAN*
|
||||
- "*"
|
||||
name:
|
||||
description:
|
||||
This is a free text field, stating the administrative name of the
|
||||
port. It may contain spaces and special characters, not exceeding 64 characters.
|
||||
type: string
|
||||
examples:
|
||||
- cloud_uplink_port
|
||||
speed:
|
||||
description:
|
||||
The link speed that shall be forced.
|
||||
@@ -30,7 +37,10 @@ properties:
|
||||
- 5000
|
||||
- 10000
|
||||
- 25000
|
||||
- 40000
|
||||
- 50000
|
||||
- 100000
|
||||
- 200000
|
||||
default: 1000
|
||||
duplex:
|
||||
description:
|
||||
@@ -161,3 +171,331 @@ properties:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 4094
|
||||
mac-address-bypass:
|
||||
description: Enables bypass when a device does not support 802.1X authentication (e.g., printers, IP phones)
|
||||
type: boolean
|
||||
mac-address-bypass-timeout-minutes:
|
||||
description: Defines the time period (in minutes) for which a MAC address is allowed access to the network without requiring reauthentication, after being authenticated or allowed via MAC Authentication Bypass (MAB).
|
||||
type: integer
|
||||
trunk-group:
|
||||
description: Associates this port to a trunk or a port-channel.
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 64
|
||||
lacp-config:
|
||||
description:
|
||||
This section describes the 802.3ad Link Aggregation Control Protocol (LACP) configuration for the current interface.
|
||||
type: object
|
||||
properties:
|
||||
lacp-enable:
|
||||
description:
|
||||
Enables 802.3ad Link Aggregation Control Protocol (LACP) for the current interface.
|
||||
type: boolean
|
||||
default: false
|
||||
lacp-role:
|
||||
description:
|
||||
Configures the port LACP role as actor or partner.
|
||||
type: string
|
||||
enum:
|
||||
- actor
|
||||
- partner
|
||||
default: actor
|
||||
lacp-mode:
|
||||
description:
|
||||
Configures the LACP negotiation activity mode as active or passive.
|
||||
type: string
|
||||
enum:
|
||||
- active
|
||||
- passive
|
||||
default: passive
|
||||
lacp-port-admin-key:
|
||||
description:
|
||||
Configures the port's LACP administration key.
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
default: 1
|
||||
lacp-port-priority:
|
||||
description:
|
||||
Configures the LACP port priority.
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
default: 32768
|
||||
lacp-system-priority:
|
||||
description:
|
||||
Configures the LACP System priority.
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
default: 32768
|
||||
lacp-pchan-admin-key:
|
||||
description:
|
||||
Configures the port channel's LACP administration key (optional).
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
lacp-timeout:
|
||||
description:
|
||||
Configures the timeout to wait for the next LACP data unit.
|
||||
type: string
|
||||
enum:
|
||||
- short
|
||||
- long
|
||||
default: long
|
||||
lldp-interface-config:
|
||||
type: object
|
||||
description: Configurations of LLDP on a specified interface.
|
||||
properties:
|
||||
lldp-admin-status:
|
||||
type: string
|
||||
description: Enables LLDP transmit, receive, or transmit and receive mode on the specified port.
|
||||
enum:
|
||||
- rx
|
||||
- tx
|
||||
- rx-tx
|
||||
lldp-basic-tlv-mgmt-ip-v4:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise the management address for this device.
|
||||
default: true
|
||||
lldp-basic-tlv-mgmt-ip-v6:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise the management IPv6 address for this device, if available.
|
||||
default: false
|
||||
lldp-basic-tlv-port-descr:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its port description.
|
||||
default: true
|
||||
lldp-basic-tlv-sys-capab:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its system capabilities.
|
||||
default: true
|
||||
lldp-basic-tlv-sys-descr:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise the system description.
|
||||
default: true
|
||||
lldp-basic-tlv-sys-name:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its system name.
|
||||
default: true
|
||||
lldp-dot1-tlv-proto-ident:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise the supported protocols.
|
||||
default: true
|
||||
lldp-dot1-tlv-proto-vid:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise port-based protocol-related VLAN information.
|
||||
default: true
|
||||
lldp-dot1-tlv-pvid:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its default Native VLAN ID (PVID).
|
||||
default: true
|
||||
lldp-dot1-tlv-vlan-name:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its VLAN name.
|
||||
default: true
|
||||
lldp-dot3-tlv-link-agg:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its link aggregation capabilities.
|
||||
default: true
|
||||
lldp-dot3-tlv-mac-phy:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its MAC and physical layer specifications.
|
||||
default: true
|
||||
lldp-dot3-tlv-max-frame:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its maximum frame size.
|
||||
default: true
|
||||
lldp-dot3-tlv-poe:
|
||||
type: boolean
|
||||
description: Configures an LLDP-enabled port to advertise its Power-over-Ethernet capabilities.
|
||||
default: true
|
||||
lldp-med-location-civic-addr:
|
||||
type: object
|
||||
description: Configures an LLDP-MED-enabled port to advertise its location identification details.
|
||||
properties:
|
||||
lldp-med-location-civic-addr-admin-status:
|
||||
type: boolean
|
||||
description: Enables or disables the advertisement of this TLV.
|
||||
default: false
|
||||
lldp-med-location-civic-country-code:
|
||||
type: string
|
||||
description: Configure the two-letter ISO 3166 country code in capital ASCII letters.
|
||||
lldp-med-location-civic-device-type:
|
||||
type: integer
|
||||
description: The type of device to which the location applies.
|
||||
lldp-med-location-civic-ca:
|
||||
description: The list of LLDP MED Location CA Types to advertise the physical location of the device, that is the city, street number, building and room information.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
lldp-med-location-civic-ca-type:
|
||||
type: integer
|
||||
description: A one-octet descriptor of the data civic address value.
|
||||
maximum: 255
|
||||
minimum: 0
|
||||
lldp-med-location-civic-ca-value:
|
||||
type: string
|
||||
description: Description of a location.
|
||||
maxLength: 32
|
||||
minLength: 1
|
||||
lldp-med-notification:
|
||||
type: boolean
|
||||
description: Enables the transmission of SNMP trap notifications about LLDP-MED changes.
|
||||
default: false
|
||||
lldp-med-tlv-ext-poe:
|
||||
type: boolean
|
||||
description: Configures an LLDP-MED-enabled port to advertise its extended Power over Ethernet configuration and usage information.
|
||||
default: true
|
||||
lldp-med-tlv-inventory:
|
||||
type: boolean
|
||||
description: Configures an LLDP-MED-enabled port to advertise its inventory identification details.
|
||||
default: true
|
||||
lldp-med-tlv-location:
|
||||
type: boolean
|
||||
description: Configures an LLDP-MED-enabled port to advertise its location identification details.
|
||||
default: true
|
||||
lldp-med-tlv-med-cap:
|
||||
type: boolean
|
||||
description: Configures an LLDP-MED-enabled port to advertise its Media Endpoint Device capabilities.
|
||||
default: true
|
||||
lldp-med-tlv-network-policy:
|
||||
type: boolean
|
||||
description: Configures an LLDP-MED-enabled port to advertise its network policy configuration.
|
||||
default: true
|
||||
lldp-notification:
|
||||
type: boolean
|
||||
description: Enables the transmission of SNMP trap notifications about LLDP changes.
|
||||
default: false
|
||||
ip-arp-inspect-port:
|
||||
type: object
|
||||
description: Configuration for ARP Inspection on specific interfaces or ports in the switch.
|
||||
properties:
|
||||
rate-limit-pps:
|
||||
type: integer
|
||||
description: Sets a rate limit (packets per second) for the ARP packets received on a port. Ensures that the port does not process ARP packets beyond the configured limit.
|
||||
minimum: 0
|
||||
maximum: 65535
|
||||
trusted:
|
||||
type: boolean
|
||||
description: Configures the port as trusted, exempting it from ARP Inspection. Trusted ports bypass ARP validation checks.
|
||||
rate-limit-port:
|
||||
type: object
|
||||
description: Configuration for ingress and egress rate limiting on a specific port (in kbps)
|
||||
properties:
|
||||
ingress-kbps:
|
||||
type: integer
|
||||
description: Sets the maximum allowed ingress (input) traffic rate for the port, in kilobits per second (kbps).
|
||||
minimum: 64
|
||||
maximum: 1000000000
|
||||
egress-kbps:
|
||||
type: integer
|
||||
description: Sets the maximum allowed egress (output) traffic rate for the port, in kilobits per second (kbps).
|
||||
minimum: 64
|
||||
maximum: 1000000000
|
||||
ip-source-guard-port:
|
||||
type: object
|
||||
description: Configuration of IP Source Guard (IPSG) on a physical interface in a Layer 2 switch.
|
||||
properties:
|
||||
rule:
|
||||
type: string
|
||||
description: Configures the switch to filter inbound traffic based on source IP address only,
|
||||
or source IP address and corresponding MAC address combined.
|
||||
enum:
|
||||
- sip
|
||||
- sip-mac
|
||||
mode:
|
||||
type: string
|
||||
description: Specifies the learning mode to use for validation, either MAC address table or ACL table.
|
||||
The system searches for source addresses in the specified table.
|
||||
enum:
|
||||
- mac
|
||||
- acl
|
||||
max-binding:
|
||||
type: integer
|
||||
description: Sets the maximum number of address entries that can be mapped to an interface
|
||||
in the binding table. Includes both static entries and dynamically learned entries
|
||||
via DHCP Snooping.
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
acl:
|
||||
description: A collection of access control entries that define the rules for filtering traffic through a network port.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
acl-inf-policy-preference:
|
||||
description: Determines the priority of multiple ACL policies when more than one is applied to an interface, if any.
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 64
|
||||
default: 1
|
||||
acl-inf-policy-ingress:
|
||||
description: Specifies the ACL policy that is applied to incoming traffic on an interface.
|
||||
type: string
|
||||
maxLength: 32
|
||||
minLength: 1
|
||||
examples:
|
||||
- blacklisted-macs
|
||||
acl-inf-counters-ingress:
|
||||
description: Tracks the number and type of packets that match the ingress ACL rules on an interface.
|
||||
type: boolean
|
||||
default: false
|
||||
acl-inf-policy-egress:
|
||||
description: Specifies the ACL policy that is applied to outgoing traffic from an interface.
|
||||
type: string
|
||||
maxLength: 32
|
||||
minLength: 1
|
||||
examples:
|
||||
- blacklisted-macs
|
||||
acl-inf-counters-egress:
|
||||
description: Tracks the number and type of packets that match the egress ACL rules on an interface.
|
||||
type: boolean
|
||||
default: false
|
||||
voice-vlan-intf-config:
|
||||
description: Configure the Voice VLAN feature at the interface level, allowing for VoIP traffic to be prioritized on this specific port.
|
||||
type: object
|
||||
properties:
|
||||
voice-vlan-intf-mode:
|
||||
description: Specify the mode of placing this port on the voice VLAN.
|
||||
type: string
|
||||
default: "auto"
|
||||
enum:
|
||||
- none
|
||||
- manual
|
||||
- auto
|
||||
voice-vlan-intf-priority:
|
||||
description: Define the Class of Service (CoS) priority for VoIP traffic passing through this port, ensuring higher priority over other traffic types.
|
||||
type: integer
|
||||
default: 6
|
||||
minimum: 0
|
||||
maximum: 6
|
||||
voice-vlan-intf-detect-voice:
|
||||
description: Select the detection method for identifying VoIP traffic on this port, such as OUI-based detection or traffic pattern recognition.
|
||||
type: string
|
||||
default: "oui"
|
||||
enum:
|
||||
- oui
|
||||
- lldp
|
||||
voice-vlan-intf-security:
|
||||
description: Enable or configure security filtering for VoIP traffic on the interface to protect against unauthorized devices.
|
||||
type: boolean
|
||||
default: false
|
||||
dhcp-snoop-port:
|
||||
description: Configuration for DHCP Snooping on a port level on a switch
|
||||
type: object
|
||||
properties:
|
||||
dhcp-snoop-port-trust:
|
||||
description: This parameter designates a switch port as ‘trusted’ for DHCP messages, meaning it can forward DHCP offers and acknowledgments, which is essential for connecting to legitimate DHCP servers
|
||||
type: boolean
|
||||
default: false
|
||||
dhcp-snoop-port-client-limit:
|
||||
description: It sets a limit on the number of DHCP clients that can be associated with a single port, helping to prevent a single port from exhausting the network’s IP address pool
|
||||
type: integer
|
||||
minimum: 1
|
||||
dhcp-snoop-port-circuit-id:
|
||||
description: Specifies DHCP Option 82 circuit ID suboption information. Often including information like the interface number and VLAN ID, this can be useful for network management and troubleshooting
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 32
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/'
|
||||
@@ -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.
|
||||
|
||||
14
schema/interface.ipv4.arp-inspect.yml
Normal file
14
schema/interface.ipv4.arp-inspect.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
type: object
|
||||
description: Configuration for ARP Inspection on specific VLANs in the switch.
|
||||
properties:
|
||||
vlan-enable:
|
||||
type: boolean
|
||||
description: Enable or disable ARP Inspection for a specified VLAN.
|
||||
vlan-acl-rule:
|
||||
type: string
|
||||
description: Specifies an ARP ACL to apply to one or more VLANs.
|
||||
maxLength: 32
|
||||
minLength: 1
|
||||
vlan-acl-nodhcp-bindings:
|
||||
type: boolean
|
||||
description: Validate ARP packets against only the specified ACL without checking address bindings in the DHCP snooping database.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user