diff --git a/command/cmd.uc b/command/cmd.uc old mode 100644 new mode 100755 index 7b4c955..272a29e --- a/command/cmd.uc +++ b/command/cmd.uc @@ -1,5 +1,13 @@ +#!/usr/bin/ucode {% - +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 cmdfile = fs.open(ARGV[2], "r"); +let cmd = json(cmdfile.read("all")); +let id = ARGV[3]; let ctx = ubus.connect(); if (!ctx) { diff --git a/command/cmd_wifiscan.uc b/command/cmd_wifiscan.uc index 92d61c7..52f9581 100644 --- a/command/cmd_wifiscan.uc +++ b/command/cmd_wifiscan.uc @@ -1,11 +1,6 @@ {% let verbose = args.verbose ? true : false; -if (args.bands) { - for (let band in args.bands) - ctx.call("wifi", "scan", { band }); -} else { - ctx.call("wifi", "scan"); -} +ctx.call("wifi", "scan"); system("sleep 5"); let scan = ctx.call("wifi", "scan_dump", {verbose}); let survey = ctx.call("wifi", "survey"); diff --git a/renderer/renderer.uc b/renderer/renderer.uc index 472165f..d797aa8 100644 --- a/renderer/renderer.uc +++ b/renderer/renderer.uc @@ -5,6 +5,7 @@ let uci = require("uci"); let ubus = require("ubus"); +let math = require("math"); let cursor = uci ? uci.cursor() : null; let conn = ubus ? ubus.connect() : null; @@ -176,12 +177,22 @@ let ethernet = { return sort(keys(matched)); }, - calculate_names: function(interface) { + calculate_name: function(interface) { let vid = interface.vlan ? interface.vlan.id : ''; - let name = interface.role + vid; - let ipv4_mode = interface.ipv4 ? interface.ipv4.addressing : 'none'; + + return (interface.role == 'upstream' ? 'wan' : 'lan') + vid; + }, + + calculate_names: function(interface) { + let name = this.calculate_name(interface); + + let ipv4_mode = interface.ipv4 ? interface.ipv4.addressing : 'none'; let ipv6_mode = interface.ipv6 ? interface.ipv6.addressing : 'none'; - return name; + + return ( + (ipv4_mode == 'none') || (ipv6_mode == 'none') || + (ipv4_mode == 'static' && ipv6_mode == 'static') + ) ? [ name ] : [ name + '_4', name + '_6' ]; } }; diff --git a/renderer/templates/base.uc b/renderer/templates/base.uc new file mode 100644 index 0000000..de12e51 --- /dev/null +++ b/renderer/templates/base.uc @@ -0,0 +1,14 @@ +# 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' + +add network device +set network.@device[-1].name=up +set network.@device[-1].type=bridge + +add network device +set network.@device[-1].name=down +set network.@device[-1].type=bridge diff --git a/renderer/templates/interface.uc b/renderer/templates/interface.uc index b3d1410..06e6be2 100644 --- a/renderer/templates/interface.uc +++ b/renderer/templates/interface.uc @@ -1,4 +1,6 @@ {% + let math = require("math"); + // Skip interfaces previously marked as conflicting. if (interface.conflicting) { warn("Skipping conflicting interface declaration"); @@ -7,14 +9,7 @@ } // Check this interface for role/vlan uniqueness... - let this_vid = interface.vlan ? interface.vlan.id : ''; - - if (!this_vid) { - if (interface.role == 'upstream') - this_vid = 2; - else - this_vid = 1; - } + let this_vid = interface.vlan ? interface.vlan.id : 1; for (let other_interface in state.interfaces) { if (other_interface == interface) @@ -28,6 +23,12 @@ } } + // check if a downstream interface with a vlan has a matching upstream interface + if (interface.vlan && interface.role == "downstream" && index(vlans, this_vid) < 0) { + warn("Trying to create a downstream interface with a VLAN ID, without matching upstream interface."); + 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_spec(interface); @@ -41,27 +42,15 @@ return; } - // Compute unique logical name and netdev name to use - let name = ethernet.calculate_names(interface); - let bridgedev = 'bridge'; - let netdev = ''; + // store the VLAN id assigned to this interface + push(vlans, this_vid); - // If this interface enables host isolation, we need to turn it into a - // dedicated isolated netdev... - if (interface.isolate_hosts) { - // If there are any ethernet ports or multiple SSIDs participating, - // we need to spawn a new bridge interface. - if (length(eth_ports) > 0 || length(bss_modes) > 1) - netdev = 'br-' + name; - } - // ... upstream interfaces with a vlan require the @upstream syntax - else if (interface.role == 'upstream' && interface.vlan) { - netdev = "@upstream." + this_vid; - } - // ... otherwise we program a VLAN on top of the global bridge. - else { - netdev = bridgedev + '.' + this_vid; - } + // Compute unique logical name and netdev name to use + let name = ethernet.calculate_name(interface); + let bridgedev = 'up'; + if (interface.role == "downstream") + bridgedev = 'down'; + let netdev = bridgedev + '.' + this_vid; // Determine the IPv4 and IPv6 configuration modes and figure out if we // can set them both in a single interface (dualstack) or whether we need @@ -72,38 +61,23 @@ (ipv4_mode == 'none') || (ipv6_mode == 'none') || (ipv4_mode == 'static' && ipv6_mode == 'static') ); + + // upstream interfaces always have a routing metric of 0 + if (interface.role == "upstream") + interface.metric = 0; %} -# Network configuration -add network interface -set networ.@interface[-1].ifname='lo' -set networ.@interface[-1].proto='static' -set networ.@interface[-1].ipaddr='127.0.0.1' -set networ.@interface[-1].netmask='255.0.0.0' - -add network device -set network.@device[-1].name=bridge -set network.@device[-1].type=bridge - -{% if (interface.isolate_hosts && netdev): %} -set network.{{ name }}_dev=device -set network.{{ name }}_dev.type=bridge -set network.{{ name }}_dev.name={{ netdev }} +add network bridge-vlan +set network.@bridge-vlan[-1].device={{ bridgedev }} +set network.@bridge-vlan[-1].vlan={{ this_vid }} {% for (let i, port in eth_ports): %} -{{ i ? 'add_list' : 'set' }} network.{{ name }}_dev.ifname={{ port }} +{{ i ? 'add_list' : 'set' }} network.@bridge-vlan[-1].ports={{ port }}{{ ((interface.role == 'upstream') && interface.vlan) ? ':t' : '' }} {% endfor %} -{% elif (!interface.isolate_hosts): %} -set network.{{ name }}_vlan=bridge-vlan -set network.{{ name }}_vlan.device={{ bridgedev }} -set network.{{ name }}_vlan.vlan={{ this_vid }} -{% for (let i, port in eth_ports): %} -{{ i ? 'add_list' : 'set' }} network.{{ name }}_vlan.ports={{ port }}{{ ((interface.role == 'upstream') && interface.vlan) ? ':t' : '' }} -{% endfor %} -{% endif %} {% if (use_dualstack): %} -set network.{{ name }}=interface +set network.{{name}}=interface set network.{{ name }}.ucentral_name={{ s(interface.name) }} +set network.{{ name }}.ucentral_path={{ s(location) }} set network.{{ name }}.ifname={{ netdev }} set network.{{ name }}.metric={{ interface.metric }} {% if (ipv4_mode == 'static'): %} @@ -121,17 +95,17 @@ set network.{{ name }}.peerdns={{ b(!length(interface.ipv4.use_dns)) }} {% else %} set network.{{ name }}.proto=dhcpv6 {% endif %} -{% if (interface.role == 'upstream'): %} +{% if (interface.role == 'upstream' && interface.vlan): %} set network.{{ name }}.ip4table={{ this_vid }} set network.{{ name }}.ip6table={{ this_vid }} {% endif %} {% else %} {% if (ipv4_mode != 'none'): %} -set network.{{ name }}_4=interface +set network.{{name}}=interface_4 set network.{{ name }}_4.ucentral_name={{ s(interface.name) }} set network.{{ name }}_4.ifname={{ netdev }} set network.{{ name }}_4.metric={{ interface.metric }} -{% if (interface.role == 'upstream'): %} +{% if (interface.role == 'upstream' && interface.vlan): %} set network.{{ name }}_4.ip4table={{ this_vid }} {% endif %} {% if (ipv4_mode == 'static'): %} @@ -142,11 +116,11 @@ set network.{{ name }}_4.proto=dhcp {% endif %} {% endif %} {% if (ipv6_mode != 'none'): %} -set network.{{ name }}_6=interface +set network.{{name}}=interface_6 set network.{{ name }}_6.ucentral_name={{ s(interface.name) }} set network.{{ name }}_6.ifname={{ netdev }} set network.{{ name }}_6.metric={{ interface.metric }} -{% if (interface.role == 'upstream'): %} +{% if (interface.role == 'upstream' && interface.vlan): %} set network.{{ name }}_6.ip6table={{ this_vid }} {% endif %} {% if (ipv6_mode == 'static'): %} @@ -161,27 +135,19 @@ set network.{{ name }}_6.proto=dhcp {% if (use_dualstack && interface.role == "downstream" && interface.vlan): %} add network rule set network.@rule[-1].in={{ name }} -set network.@rule[-1].lookup={{ this_vid }} +set network.@rule[-1].lookup={{ interface.vlan.id }} {% endif %} {% - include('interface/firewall.uc', { - interface, - networks: use_dualstack ? [ name ] : [ name + '_4', name + '_6' ] - }); + include('interface/firewall.uc'); if (interface.ipv4) - include('interface/dhcp.uc', { - interface, - name: use_dualstack ? name : name + '_4' - }); + include('interface/dhcp.uc'); for (let i, ssid in interface.ssids) { include('ssid.uc', { location: location + '/ssids/' + i, - ssid, - interface, - networks: use_dualstack ? [ name ] : [ name + '_4', name + '_6' ] + ssid }); } %} diff --git a/renderer/templates/interface/dhcp.uc b/renderer/templates/interface/dhcp.uc index 073abe4..e94fe36 100644 --- a/renderer/templates/interface/dhcp.uc +++ b/renderer/templates/interface/dhcp.uc @@ -1,4 +1,4 @@ - +{% let name = ethernet.calculate_name(interface) %} {% let dhcp = interface.ipv4.dhcp || { ignore: 1 } %} add dhcp dhcp set dhcp.@dhcp[-1].interface={{ s(name) }} diff --git a/renderer/templates/interface/firewall.uc b/renderer/templates/interface/firewall.uc index ebff930..d16ea4d 100644 --- a/renderer/templates/interface/firewall.uc +++ b/renderer/templates/interface/firewall.uc @@ -1,8 +1,8 @@ -{% for (let n, network in networks): %} -{% if (interface.role == upstream): %} +{% for (let name in ethernet.calculate_names(interface)): %} +{% if (interface.role == "upstream"): %} add firewall zone -set firewall.@zone[-1].name={{ s(network) }} -set firewall.@zone[-1].network={{ s(network) }} +set firewall.@zone[-1].name={{ s(name) }} +set firewall.@zone[-1].network={{ s(name) }} set firewall.@zone[-1].input='REJECT' set firewall.@zone[-1].output='ACCEPT' set firewall.@zone[-1].forward='REJECT' @@ -10,14 +10,14 @@ set firewall.@zone[-1].masq=1 set firewall.@zone[-1].mtu_fix=1 {% else %} add firewall zone -set firewall.@zone[-1].name={{ s(network) }} -set firewall.@zone[-1].network={{ s(network) }} +set firewall.@zone[-1].name={{ s(name) }} +set firewall.@zone[-1].network={{ s(name) }} set firewall.@zone[-1].input='ACCEPT' set firewall.@zone[-1].output='ACCEPT' set firewall.@zone[-1].forward='ACCEPT' add firewall forwarding -set firewall.@forwarding[-1].src={{ network }} -set firewall.@forwarding[-1].dest=upstream{{ interface.vlan ? interface.vlan.id : '' }} +set firewall.@forwarding[-1].src={{ name }} +set firewall.@forwarding[-1].dest='wan{{ interface.vlan ? interface.vlan.id : '' }}' {% endif %} {% endfor %} diff --git a/renderer/templates/radio.uc b/renderer/templates/radio.uc index 214fdc9..dec906f 100644 --- a/renderer/templates/radio.uc +++ b/renderer/templates/radio.uc @@ -96,7 +96,7 @@ set wireless.{{ phy.section }}.require_mode={{ s(match_require_mode(radio.requir 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 }} -{% if (phy.he_mac_capa && match(htmode, /HE.*/)): %} +{% 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) }} diff --git a/renderer/templates/ssid.uc b/renderer/templates/ssid.uc index e47d176..3e1b24d 100644 --- a/renderer/templates/ssid.uc +++ b/renderer/templates/ssid.uc @@ -44,51 +44,54 @@ {% let id = wiphy.allocate_ssid_section_id(phy) %} {% let crypto = validate_encryption(); %} {% if (!crypto) continue; %} -set wireless.{{ id }}=wifi-iface -set wireless.{{ id }}.device={{ phy.section }} -{% for (let i, network in networks): %} -{{ i ? 'add_list' : 'set' }} wireless.{{ id }}.network={{ network }} +add wireless wifi-iface +set wireless.@wifi-iface[-1].ucentral_path={{ s(location) }} +set wireless.@wifi-iface[-1].device={{ phy.section }} +{% for (let i, name in ethernet.calculate_names(interface)): %} +{{ i ? 'add_list' : 'set' }} wireless.@wifi-iface[-1].network={{ name }} {% endfor %} -set wireless.{{ id }}.ssid={{ s(ssid.name) }} -set wireless.{{ id }}.mode={{ ssid.bss_mode }} -set wireless.{{ id }}.bssid={{ ssid.bssid }} -set wireless.{{ id }}.hidden={{ b(ssid.hidden_ssid) }} -set wireless.{{ id }}.time_advertisement={{ ssid.broadcast_time }} -set wireless.{{ id }}.isolate={{ b(ssid.isolate_clients) }} -set wireless.{{ id }}.uapsd={{ b(ssid.power_save) }} -set wireless.{{ id }}.rts_threshold={{ ssid.rts_threshold }} -set wireless.{{ id }}.multicast_to_unicast={{ b(ssid.unicast_conversion) }} -set wireless.{{ id }}.beacon_rate={{ ssid.rates.beacon }} -set wireless.{{ id }}.mcast_rate={{ ssid.rates.multicast }} +set wireless.@wifi-iface[-1].ssid={{ s(ssid.name) }} +set wireless.@wifi-iface[-1].mode={{ ssid.bss_mode }} +set wireless.@wifi-iface[-1].bssid={{ ssid.bssid }} +set wireless.@wifi-iface[-1].hidden={{ b(ssid.hidden_ssid) }} +set wireless.@wifi-iface[-1].time_advertisement={{ ssid.broadcast_time }} +set wireless.@wifi-iface[-1].isolate={{ b(ssid.isolate_clients) }} +set wireless.@wifi-iface[-1].uapsd={{ b(ssid.power_save) }} +set wireless.@wifi-iface[-1].rts_threshold={{ ssid.rts_threshold }} +set wireless.@wifi-iface[-1].multicast_to_unicast={{ b(ssid.unicast_conversion) }} +{% if (ssid.rates): %} +set wireless.@wifi-iface[-1].beacon_rate={{ ssid.rates.beacon }} +set wireless.@wifi-iface[-1].mcast_rate={{ ssid.rates.multicast }} +{% endif %} {% if (ssid.rrm): %} -set wireless.{{ id }}.ieee80211k={{ b(ssid.rrm.neighbor_reporting) }} -set wireless.{{ id }}.ftm_responder={{ b(ssid.rrm.ftm_responder) }} -set wireless.{{ id }}.stationary_ap={{ b(ssid.rrm.stationary_ap) }} -set wireless.{{ id }}.lci={{ b(ssid.rrm.lci) }} -set wireless.{{ id }}.civic={{ ssid.rrm.civic }} +set wireless.@wifi-iface[-1].ieee80211k={{ b(ssid.rrm.neighbor_reporting) }} +set wireless.@wifi-iface[-1].ftm_responder={{ b(ssid.rrm.ftm_responder) }} +set wireless.@wifi-iface[-1].stationary_ap={{ b(ssid.rrm.stationary_ap) }} +set wireless.@wifi-iface[-1].lci={{ b(ssid.rrm.lci) }} +set wireless.@wifi-iface[-1].civic={{ ssid.rrm.civic }} {% endif %} {% if (ssid.roaming): %} -set wireless.{{ id }}.ieee80211r=1 -set wireless.{{ id }}.ft_over_ds={{ b(ssid.roaming.message_exchange == "ds") }} -set wireless.{{ id }}.ft_psk_generate_local={{ b(ssid.roaming.generate_psk) }} -set wireless.{{ id }}.mobility_domain={{ ssid.roaming.domain_identifier }} +set wireless.@wifi-iface[-1].ieee80211r=1 +set wireless.@wifi-iface[-1].ft_over_ds={{ b(ssid.roaming.message_exchange == "ds") }} +set wireless.@wifi-iface[-1].ft_psk_generate_local={{ b(ssid.roaming.generate_psk) }} +set wireless.@wifi-iface[-1].mobility_domain={{ ssid.roaming.domain_identifier }} {% endif %} -set wireless.{{ id }}.ieee80211w={{ match_ieee80211w() }} -set wireless.{{ id }}.encryption={{ crypto.proto }} -set wireless.{{ id }}.key={{ crypto.key }} +set wireless.@wifi-iface[-1].ieee80211w={{ match_ieee80211w() }} +set wireless.@wifi-iface[-1].encryption={{ crypto.proto }} +set wireless.@wifi-iface[-1].key={{ crypto.key }} {% if (crypto.auth): %} -set wireless.{{ id }}.auth_server={{ crypto.auth.host }} -set wireless.{{ id }}.auth_port={{ crypto.auth.port }} -set wireless.{{ id }}.auth_secret={{ crypto.auth.secret }} +set wireless.@wifi-iface[-1].auth_server={{ crypto.auth.host }} +set wireless.@wifi-iface[-1].auth_port={{ crypto.auth.port }} +set wireless.@wifi-iface[-1].auth_secret={{ crypto.auth.secret }} {% endif %} {% if (crypto.acct): %} -set wireless.{{ id }}.acct_server={{ crypto.acct.host }} -set wireless.{{ id }}.acct_port={{ crypto.acct.port }} -set wireless.{{ id }}.acct_secret={{ crypto.acct.secret }} -set wireless.{{ id }}.acct_interval={{ crypto.acct.interval }} +set wireless.@wifi-iface[-1].acct_server={{ crypto.acct.host }} +set wireless.@wifi-iface[-1].acct_port={{ crypto.acct.port }} +set wireless.@wifi-iface[-1].acct_secret={{ crypto.acct.secret }} +set wireless.@wifi-iface[-1].acct_interval={{ crypto.acct.interval }} {% endif %} -{% if (ssid.rate_limit.ingress_rate || ssid.rate_limit.egress_rate): %} +{% 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 }} diff --git a/renderer/templates/toplevel.uc b/renderer/templates/toplevel.uc index 3c87f02..80802d6 100644 --- a/renderer/templates/toplevel.uc +++ b/renderer/templates/toplevel.uc @@ -1,5 +1,22 @@ {% - include('unit.uc', { location: '/unit', unit: state.unit }); + // 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) { + location = '/'; + 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'); + } + + include('base.uc'); + + if (state.unit) + include('unit.uc', { location: '/unit', unit: state.unit }); for (let service in state.services) include('services/' + service + '.uc', { @@ -16,8 +33,17 @@ for (let i, radio in state.radios) include('radio.uc', { location: '/radios/' + i, radio }); - for (let i, interface in state.interfaces) - include('interface.uc', { location: '/interfaces/' + i, interface }); + let vlans = []; + 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"); if (state.config_raw) include("config_raw.uc", { location: '/config_raw', config_raw: state.config_raw }); diff --git a/renderer/ucentral.uc b/renderer/ucentral.uc index f42c097..dae0e8e 100755 --- a/renderer/ucentral.uc +++ b/renderer/ucentral.uc @@ -8,13 +8,15 @@ let schemareader = require("schemareader"); let renderer = require("renderer"); let fs = require("fs"); -let inputfile = fs.open("/tmp/test.json", "r"); +let inputfile = fs.open(ARGV[2], "r"); let inputjson = json(inputfile.read("all")); +let error = 0; + inputfile.close(); +let logs = []; try { - let logs = []; let batch = renderer.render(schemareader.validate(inputjson), logs); fs.stdout.write("Log messages:\n" + join("\n", logs) + "\n\n"); @@ -35,10 +37,28 @@ try { for (let cmd in [ 'uci -c /tmp/config-shadow commit', 'cp /tmp/config-shadow/* /etc/config/', - 'reload_config', 'rm -rf /tmp/config-shadow' ]) + 'rm -rf /tmp/config-shadow', + 'reload_config']) system(cmd); + + fs.unlink('/etc/ucentral/ucentral.active'); + fs.symlink(ARGV[2], '/etc/ucentral/ucentral.active'); + } catch (e) { + error = 1; warn("Fatal error while generating UCI: ", e, "\n", e.stacktrace[0].context, "\n"); } + +let ubus = require("ubus").connect(); + +ubus.call("ucentral", "result", { + uuid: inputjson.uuid || 0, + id: +ARGV[3] || 0, + status: { + error, + text: error ? "Failed" : "Success", + rejected: logs || [] + } +}); %} diff --git a/schema/interface.ssid.yml b/schema/interface.ssid.yml index 92b5e10..e415c2e 100644 --- a/schema/interface.ssid.yml +++ b/schema/interface.ssid.yml @@ -29,6 +29,8 @@ properties: - ap - sta - mesh + - wds + default: ap bssid: description: Override the BSSID of the network, only applicable in adhoc or sta mode. diff --git a/schema/interface.vlan.yml b/schema/interface.vlan.yml index 97a7c83..199473f 100644 --- a/schema/interface.vlan.yml +++ b/schema/interface.vlan.yml @@ -17,3 +17,4 @@ properties: enum: - 802.1ad - 802.1q + default: 802.1q diff --git a/schema/metrics.yml b/schema/metrics.yml index f44a540..2dfb849 100644 --- a/schema/metrics.yml +++ b/schema/metrics.yml @@ -12,6 +12,7 @@ properties: description: The reporting interval defined in seconds. type: integer + minimum: 60 types: description: A list of names of subsystems that shall be reported periodically. @@ -32,6 +33,7 @@ properties: description: The reporting interval defined in seconds. type: integer + minimum: 60 dhcp-snooping: description: DHCP snooping allows us to intercept DHCP packages on interface that are diff --git a/schema/ucentral.yml b/schema/ucentral.yml index 334af02..c715f49 100644 --- a/schema/ucentral.yml +++ b/schema/ucentral.yml @@ -3,6 +3,10 @@ $schema: http://json-schema.org/draft-07/schema# description: OpenWrt uCentral schema type: object properties: + uuid: + description: + The uniquie ID of the configuration. This is the unix timestamp of when the config was created. + type: integer unit: $ref: "https://ucentral.io/schema/v1/unit/" globals: diff --git a/schemareader.uc b/schemareader.uc index 5363ef7..ef55989 100644 --- a/schemareader.uc +++ b/schemareader.uc @@ -224,6 +224,9 @@ function instantiateInterfaceVlan(value) { assert(value["proto"] in [ "802.1ad", "802.1q" ], "Property interface.vlan.proto must be one of [ \"802.1ad\", \"802.1q\" ]"); obj.proto = value["proto"]; } + else { + obj.proto = "802.1q"; + } return obj; } @@ -954,9 +957,12 @@ function instantiateInterfaceSsid(value) { if (exists(value, "bss-mode")) { assert(type(value["bss-mode"]) == "string", "Property interface.ssid.bss-mode must be of type string"); - assert(value["bss-mode"] in [ "ap", "sta", "mesh" ], "Property interface.ssid.bss-mode must be one of [ \"ap\", \"sta\", \"mesh\" ]"); + assert(value["bss-mode"] in [ "ap", "sta", "mesh", "wds" ], "Property interface.ssid.bss-mode must be one of [ \"ap\", \"sta\", \"mesh\", \"wds\" ]"); obj.bss_mode = value["bss-mode"]; } + else { + obj.bss_mode = "ap"; + } if (exists(value, "bssid")) { assert(type(value["bssid"]) == "string", "Property interface.ssid.bssid must be of type string"); @@ -1376,6 +1382,7 @@ function instantiateMetrics(value) { if (exists(value, "interval")) { assert(type(value["interval"]) == "int", "Property metrics.statistics.interval must be of type integer"); + assert(value["interval"] >= 60, "Property metrics.statistics.interval must be >= 60"); obj.interval = value["interval"]; } @@ -1407,6 +1414,7 @@ function instantiateMetrics(value) { if (exists(value, "interval")) { assert(type(value["interval"]) == "int", "Property metrics.health.interval must be of type integer"); + assert(value["interval"] >= 60, "Property metrics.health.interval must be >= 60"); obj.interval = value["interval"]; } @@ -1468,6 +1476,11 @@ function newUCentralState(value) { let obj = {}; + if (exists(value, "uuid")) { + assert(type(value["uuid"]) == "int", "Property UCentralState.uuid must be of type integer"); + obj.uuid = value["uuid"]; + } + if (exists(value, "unit")) { obj.unit = instantiateUnit(value["unit"]); } diff --git a/system/capabilities.uc b/system/capabilities.uc old mode 100644 new mode 100755 index 0761584..4c81342 --- a/system/capabilities.uc +++ b/system/capabilities.uc @@ -1,15 +1,30 @@ +#!/usr/bin/ucode {% - capa = {}; - ctx = ubus.connect(); - capa.compatible = replace(board.model.id, ',', '_'); - capa.model = board.model.name; - capa.network = board.network; - if (board["bridge"]) - capa["bridge-vlan"] = true; - if (board["switch"]) - capa["switch"] = board["switch"]; - wifi = ctx.call("wifi", "phy"); - if (length(wifi)) - capa.wifi = wifi; - print(capa); +push(REQUIRE_SEARCH_PATH, + "/usr/lib/ucode/*.so", + "/usr/share/ucentral/*.uc"); + +let ubus = require("ubus"); +let fs = require("fs"); + +let boardfile = fs.open("/etc/board.json", "r"); +let board = json(boardfile.read("all")); +boardfile.close(); + +capa = {}; +ctx = ubus.connect(); +capa.compatible = replace(board.model.id, ',', '_'); +capa.model = board.model.name; +capa.network = board.network; +if (board["bridge"]) + capa["bridge-vlan"] = true; +if (board["switch"]) + capa["switch"] = board["switch"]; +wifi = ctx.call("wifi", "phy"); +if (length(wifi)) + capa.wifi = wifi; + +capafile = fs.open("/etc/ucentral/capabilities.json", "w"); +capafile.write(capa); +capafile.close(); %} diff --git a/system/crashlog.uc b/system/crashlog.uc new file mode 100755 index 0000000..fbea7ea --- /dev/null +++ b/system/crashlog.uc @@ -0,0 +1,13 @@ +{% +if (!fs.stat("/sys/fs/pstore/dmesg-ramoops-0")) + return 0; +let fd = fs.open("/sys/fs/pstore/dmesg-ramoops-0", "r"); +let line, lines = []; +while (line = fd.read("line")) + push(lines, trim(line)); +fd.close(); +let fd = fs.open("/tmp/crashlog", "w"); +fd.write({crashlog: lines}); +fd.close(); +print(lines); +%} diff --git a/system/health.uc b/system/health.uc old mode 100644 new mode 100755 index 14e1861..74dd683 --- a/system/health.uc +++ b/system/health.uc @@ -1,4 +1,9 @@ +#!/usr/bin/ucode {% +let fs = require("fs"); +let uci = require("uci"); +let ubus = require("ubus"); + state = { unit: {}, interfaces: {} @@ -103,7 +108,7 @@ catch(e) { let errors = length(state.interfaces); if (!errors) - delete(state.interfaces); + delete(state, "interfaces"); let sanity = 100 - (errors * 100 / count); diff --git a/system/probe_services.uc b/system/probe_services.uc old mode 100644 new mode 100755 diff --git a/system/state.uc b/system/state.uc old mode 100644 new mode 100755 index 0705b98..5a939a8 --- a/system/state.uc +++ b/system/state.uc @@ -1,4 +1,11 @@ +#!/usr/bin/ucode {% + let fs = require("fs"); + let uci = require("uci"); + let ubus = require("ubus"); + let cfgfile = fs.open("/etc/ucentral/ucentral.active", "r"); + let cfg = json(cfgfile.read("all")); + /* set up basic functionality */ if (!cursor) cursor = uci.cursor(); diff --git a/ucentral.schema.json b/ucentral.schema.json index 86ecbe7..a3ecbb4 100644 --- a/ucentral.schema.json +++ b/ucentral.schema.json @@ -3,6 +3,9 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { + "uuid": { + "type": "integer" + }, "unit": { "$ref": "#/$defs/unit" }, @@ -231,7 +234,8 @@ "enum": [ "802.1ad", "802.1q" - ] + ], + "default": "802.1q" } } }, @@ -858,8 +862,10 @@ "enum": [ "ap", "sta", - "mesh" - ] + "mesh", + "wds" + ], + "default": "ap" }, "bssid": { "type": "string", @@ -1174,7 +1180,8 @@ "type": "object", "properties": { "interval": { - "type": "integer" + "type": "integer", + "minimum": 60 }, "types": { "type": "array", @@ -1193,7 +1200,8 @@ "type": "object", "properties": { "interval": { - "type": "integer" + "type": "integer", + "minimum": 60 } } },