Compare commits

...

55 Commits

Author SHA1 Message Date
Oleksandr Mazur
c9b4970b29 Fix broken schema json file
After latest main merge, there's some overlapping which effectively
breaks the schema (it becomes invalid json file, due to some objects
not ending where they should).

Run generate.sh to provide a complete valid generated json file.

Signed-off-by: Oleksandr Mazur <oleksandr.mazur@plvision.eu>
2025-09-05 12:36:54 +03:00
Mike Hansen
fdf54a7e0e Merge pull request #44 from Telecominfraproject/ols-688-bpdu-guard
ols-688-sprint-12-bpdu-guard-draft
2025-09-03 13:39:44 -04:00
Binny
52e38ce792 Merge branch 'main' into ols-688-bpdu-guard 2025-09-03 23:05:31 +05:30
Mike Hansen
41e621b455 Merge pull request #45 from Telecominfraproject/ols-688-sprint-12-storm-control-draft
ols-688-sprint-12-stormcontrol-draft
2025-08-20 09:43:38 -04:00
Binny
c79f7f4517 ols-688-sprint12-bpduguard-final-p2 2025-06-17 15:12:40 +00:00
Binny
0e43b3cb3a ols-688-sprint12-stormcontrol-finalreview-p2 2025-06-17 15:06:46 +00:00
Binny
67f3f14fab ols-688-sprint12-stormcontrol-finalreview 2025-06-17 04:12:10 +00:00
Binny
8c82a276d8 ols-688-sprint12-bpdu-guard-finalreview 2025-06-17 04:05:31 +00:00
Binny
a098465268 ols-688-bpdu-guard-first-reviewchange 2025-06-11 03:33:54 +00:00
Binny
040650cb5c ols-688-sprint-12-stormcontrol-draft 2025-06-09 04:09:55 +00:00
Binny
adf3514ae9 ols-688-sprint-12-bpdu-guard-draft 2025-06-08 18:16:46 +00:00
Mike Hansen
d8d4380977 Merge pull request #43 from Telecominfraproject/OLS_UpdateSchemaVersion_410
Update OLS Schema version to 4.1.0, regenerate html
2025-05-26 13:21:31 -04:00
Mike Hansen
c63ac1f5d9 Update OLS Schema version to 4.1.0, regenerate html
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-05-26 08:48:43 -04:00
Binny
37b9152b6e Merge pull request #42 from Telecominfraproject/ols-659-simplify-vlan-config
OLS-659 proposal draft for bulk-vlan
2025-05-16 06:29:12 +05:30
Binny
5db059b95b Merge pull request #41 from Telecominfraproject/ols-655-state-message-l2-loopdetection-info
OLS-655 - STP and Loop Detection Protocol States in State Message
2025-05-16 06:28:45 +05:30
Binny
5a7f055793 ols-655 final change to merge 2025-05-06 02:37:02 +00:00
Binny
2b7ce76453 ols-655-review-comments-part1 2025-04-23 20:33:39 +00:00
Binny
d1ab8b453b ols-659 changes_after_review1 2025-04-23 20:19:25 +00:00
Binny
e80a6d2166 OLS-659 proposal draft for bulk-vlan 2025-04-22 16:28:58 +00:00
Binny
6469510af1 OLS-655 draft changes 2025-04-21 16:24:18 +00:00
Mike Hansen
d84e5ee624 Merge pull request #40 from Telecominfraproject/staging-OLS-644-global-dns
OLS-644: Global DNS configuration
2025-04-10 09:03:18 -04:00
Tanya Singh
029cdb4ed9 OLS-644: Add Global DNS to switch.yml and use generate.sh to create the json files
Signed-off-by: Tanya Singh <tanya_singh@accton.com>
2025-04-10 17:13:03 +08:00
Tanya Singh
cd7d50997c OLS-644: Global DNS configuration
Signed-off-by: Tanya Singh <tanya_singh@accton.com>
2025-03-27 11:37:06 +08:00
Mike Hansen
f394cb4019 Merge pull request #38 from Telecominfraproject/OLS-578-Tag-ols-ucentral-client-and-ols-ucentral-schema-4.0.0-pre-release
[OLS-578] Tag ols-ucentral-client and ols-ucentral-schema 4.0.0 pre-r…
2025-02-05 08:16:49 -05:00
Mike Hansen
5e345b22a3 [OLS-578] Tag ols-ucentral-client and ols-ucentral-schema 4.0.0 pre-release
Update to 4.0.0

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-02-04 12:21:40 -05:00
Mike Hansen
dcd935359c Merge pull request #36 from Telecominfraproject/Sprint-8-OLS-ARP-Inspect-Schema
ols-556-sprint8-arp-inspect-schema-draft
2025-02-04 12:09:05 -05:00
Mike Hansen
362e03a363 Resolving merge issues 2025-02-04 12:08:04 -05:00
Mike Hansen
51c5b1b9f4 Merge pull request #34 from Telecominfraproject/Sprint-8-OLS---Rate-Limiting-schema
Sprint-8 OLS Rate Limiting Schema change
2025-02-04 11:54:02 -05:00
Mike Hansen
5d50740f98 Resolving merge issues 2025-02-04 11:52:13 -05:00
Mike Hansen
01d4c80824 Merge pull request #33 from Telecominfraproject/OLS-556-Sprint-8-IPSourceGuard
Sprint-8 OLS - IP Source Guard schema
2025-02-04 11:42:40 -05:00
Mike Hansen
76cc0646e0 Resolving merge issues 2025-02-04 11:41:06 -05:00
Mike Hansen
ffe61ea929 Merge pull request #32 from Telecominfraproject/OLS-556-Sprint-8-SubcribeRTEvents
RT event subscription Sprint-8 OLS
2025-02-04 11:32:19 -05:00
Mike Hansen
078c2021eb Resolving merge issues 2025-02-04 11:31:29 -05:00
Mike Hansen
a1e044834b Merge pull request #35 from Telecominfraproject/OLS-550_Schema_Corrections_Pre4p0_Features
ols-550 changes for old ols 3.2 features to align with schema design …
2025-01-31 09:52:00 -05:00
Mike Hansen
18d5b2c475 Merge branch 'main' into OLS-550_Schema_Corrections_Pre4p0_Features 2025-01-31 09:49:56 -05:00
Mike Hansen
374fab81db ols-550 regenerate json files to pickup dhcp-snoop-port fix
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-31 09:41:51 -05:00
Binny
6bc313b440 corrected attribute names and moved ipsg config at switch level to separate file 2025-01-29 18:32:40 +00:00
Binny
1d052a18c2 created new files, and accomodated review comments 2025-01-29 15:36:13 +00:00
Binny
69bc3a60b7 changes to make rtevent a new file, and property names. 2025-01-29 14:46:01 +00:00
Binny
cb0069a356 correcting property names to align with review comments 2025-01-29 14:29:20 +00:00
Binny
e1a110bc7f ols-550 correcting the intendation for dhcp-snoop-port 2025-01-28 17:13:58 +00:00
Mike Hansen
63d1103ef3 Merge pull request #37 from Telecominfraproject/OLS-562-Add-schema-version-to-ols-ucentral-schema
[OLS-562] Add schema version to ols-ucentral-schema
2025-01-27 16:58:06 -05:00
Binny
77b79d1007 ols-550-Review feedback incorporated 2025-01-27 18:51:26 +00:00
Mike Hansen
b148155dea [OLS-562] Add schema version to ols-ucentral-schema
Add a schema.json version file similar to AP in wlan-ucentral-schema

Enhance connect.capabilties.yml to include both the switch and schema versions

  version:
    type: object
    description:
      Switch version info, OLS release and schema.
    properties:
      switch:
        type: object
        description: the ols release of this Switch
        properties:
          major:
            type: integer
          minor:
            type: integer
          patch:
            type: integer
        examples:
        - 'major': 3
          'minor': 2
          'patch': 0
      schema:
        type: object
        description: the schema version used for this ols release.
        properties:
          major:
            type: integer
          minor:
            type: integer
          patch:
            type: integer
        examples:
        - 'major': 3
          'minor': 2
          'patch': 0

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-22 19:17:23 -05:00
Binny
59ad89be0f ols-556-sprint8-arp-inspect-schema-draft 2025-01-22 14:48:04 +00:00
Binny
8e32d2775a ols-550 changes for old ols 3.2 features to align with schema design and acl index and rule id suggestion 2025-01-21 06:54:14 +00:00
Binny
548b76a948 Sprint-8 OLS Rate Limiting Schema change 2025-01-15 14:40:44 +00:00
Binny
4f2a23741b Sprint-8 OLS - IP Source Guard schema 2025-01-15 14:31:51 +00:00
Binny
44c07718e3 Changes for RT event subscription 2025-01-15 14:10:01 +00:00
Mike Hansen
adeeb0457b Merge pull request #31 from Telecominfraproject/OLS-545-Removal-of-AP-specific-schema-elements-from-ols-ucentral-schema
[OLS-545] Removal of AP specific schema elements from ols-ucentral-sc…
2025-01-09 12:42:12 -05:00
Mike Hansen
0ed83ba0a5 [OLS-545] Removal of AP specific schema elements from ols-ucentral-schema
Remove the AP specific elements of the schema (configuration), and state (reporting)
Remove the ucode files that are not used
Regenerate the json and documentation files to reflect schema and state changes

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-09 09:51:36 -05:00
Binny
a03b5620c5 Merge pull request #30 from Telecominfraproject/OLS-538_port_speed_enum_fix_40g
add 40g,50g, 200g port speeds
2025-01-07 06:24:15 +05:30
Mike Hansen
d37fc0b7eb Merge pull request #29 from Telecominfraproject/OLS-529-Schema-schema-interface.yml-incorrect-dhcp-snooop-port
[OLS-529] Schema: schema/interface.yml incorrect "dhcp-snooop-port"
2025-01-06 08:47:00 -05:00
Mike Hansen
b5845fbd89 [OLS-529] Schema: schema/interface.yml incorrect "dhcp-snooop-port"
- cleanup generate.sh

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2024-12-16 11:34:16 -05:00
Mike Hansen
b6ac5a6450 [OLS-529] Schema: schema/interface.yml incorrect "dhcp-snooop-port"
schema/interface.yml
Fix:
  dhcp-snooop-port:
    $ref: "https://ucentral.io/schema/v1/interface/dhcp-snoop-port/"

Change to:
  dhcp-snoop-port:
    $ref: "https://ucentral.io/schema/v1/interface/dhcp-snoop-port/"

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2024-12-12 14:26:36 -05:00
169 changed files with 5919 additions and 22703 deletions

1
.gitignore vendored
View File

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

View File

@@ -15,18 +15,36 @@ properties:
version:
type: object
description:
The ols schema version to be used with this Switch
Switch version info, OLS release and schema.
properties:
major:
type: integer
minor:
type: integer
patch:
type: integer
examples:
- 'major': 3
'minor': 2
'patch': 0
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:
@@ -225,6 +243,7 @@ properties:
- Spanning-Tree-Per-VLAN
- Spanning-Tree-Per-VLAN-Rapid
- Spanning-Tree-MSTP
- BPDU-Guard
# L3
- SVI-StaticIPv4
- SVI-StaticIPv6
@@ -262,6 +281,7 @@ properties:
- MAC-ACL
- IP-ACL
- Guest-VLAN
- Storm-Control
# Services
- Service-SSH
- Service-RSSH
@@ -282,6 +302,7 @@ properties:
- Service-Online-Check
- Service-CaptivePortal
- Service-PublicIpCheck
- Service-Global-DNS
# Tunneling
- Tunneling-VxLAN
- Tunneling-GRE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

181
docs/schema_doc.css Normal file
View File

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

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

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

117
docs/ucentral-schema.html Normal file

File diff suppressed because one or more lines are too long

30
docs/ucentral-state.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

5
schema.json Normal file
View File

@@ -0,0 +1,5 @@
{
"major": 4,
"minor": 1,
"patch": 0
}

View File

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

View File

@@ -367,4 +367,173 @@ properties:
lldp-notification:
type: boolean
description: Enables the transmission of SNMP trap notifications about LLDP changes.
default: false
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 networks 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
bpdu-guard:
description: BPDU Guard configuration block. Enables protection against unexpected BPDUs
on edge ports to prevent loops and rogue switch connections.
type: object
properties:
enabled:
description: When true, the port will be placed into an error-disabled state if any BPDU is received.
type: boolean
auto-recovery-secs:
description: Time in 'seconds' after which a port that was err-disabled due to BPDU Guard
violation will be automatically re-enabled.
type: integer
default: 300
edge-port:
description: When true, the port behaves as an STP Edge Port. When false, the port
participates fully in STP and is treated as a normal switch port.
type: boolean
default: false
storm-control:
description: Storm Control configuration per storm type. Allows enabling or disabling traffic storm control for broadcast, multicast, and unknown unicast packets,
with independent packet-per-second (pps) thresholds. A limit-pps value of 0 implies the control is disabled for that traffic type.
type: object
properties:
broadcast-pps:
type: integer
minimum: 0
default: 0
description: Maximum allowed broadcast packets per second. 0 disables broadcast storm control.
multicast-pps:
type: integer
minimum: 0
default: 0
description: Maximum allowed multicast packets per second. 0 disables multicast storm control.
unknown-unicast-pps:
type: integer
minimum: 0
default: 0
description: Maximum allowed unknown unicast packets per second. 0 disables unknown unicast storm control.

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
description: A collection of access control entries that define the rules for filtering traffic through a network interface.
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

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