From f6027eb7e71b59e4de40aa8ce1aade431e204807 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Fri, 21 May 2021 16:06:48 +0200 Subject: [PATCH] schema: add captive portal support Signed-off-by: John Crispin --- renderer/renderer.uc | 3 + renderer/templates/interface.uc | 16 ++- renderer/templates/interface/captive.uc | 22 ++++ schema/interface.captive.yml | 10 +- schema/interface.ssid.captive.yml | 40 ------- schema/interface.ssid.yml | 2 - schema/interface.yml | 2 + schemareader.uc | 139 ++++++++++++------------ ucentral.schema.json | 77 +++++++------ 9 files changed, 153 insertions(+), 158 deletions(-) create mode 100644 renderer/templates/interface/captive.uc delete mode 100644 schema/interface.ssid.captive.yml diff --git a/renderer/renderer.uc b/renderer/renderer.uc index c7c9e3d..9b431d6 100644 --- a/renderer/renderer.uc +++ b/renderer/renderer.uc @@ -181,6 +181,9 @@ let ethernet = { calculate_name: function(interface) { let vid = interface.vlan ? interface.vlan.id : ''; + if (interface.captive) + return 'captive'; + return (interface.role == 'upstream' ? 'wan' : 'lan') + vid; }, diff --git a/renderer/templates/interface.uc b/renderer/templates/interface.uc index 7539970..1be6038 100644 --- a/renderer/templates/interface.uc +++ b/renderer/templates/interface.uc @@ -59,6 +59,12 @@ } } + // Captive Portal is only supported on downstream interfaces + if (interface.captive && interface.role != 'downstream') { + warn("Trying to create a Cpative Portal on a none downstream 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); @@ -113,8 +119,11 @@ if (tunnel_proto == "mesh") include("interface/mesh.uc", { name }); - // All none L2/3 tunnel require a vlan inside their bridge - include("interface/bridge-vlan.uc", { interface, name, eth_ports, this_vid, bridgedev }); + // All none L2/3 tunnel require a vlan inside their bridge (unless we run a captive portal) + if (interface.captive) + netdev = ''; + else + include("interface/bridge-vlan.uc", { interface, name, eth_ports, this_vid, bridgedev }); include("interface/common.uc", { name, this_vid, netdev, @@ -147,4 +156,7 @@ count++; } } + + if (interface.captive) + include('interface/captive.uc', { netdev }); %} diff --git a/renderer/templates/interface/captive.uc b/renderer/templates/interface/captive.uc new file mode 100644 index 0000000..88e7673 --- /dev/null +++ b/renderer/templates/interface/captive.uc @@ -0,0 +1,22 @@ + +# Captive Portal Configuration +add opennds opennds +set opennds.@opennds[-1].enabled='1' +set opennds.@opennds[-1].fwhook_enabled='1' +set opennds.@opennds[-1].debuglevel='1' +add_list opennds.@opennds[-1].users_to_router='allow tcp port 53' +add_list opennds.@opennds[-1].users_to_router='allow udp port 53' +add_list opennds.@opennds[-1].users_to_router='allow udp port 67' +add_list opennds.@opennds[-1].users_to_router='allow tcp port 22' +add_list opennds.@opennds[-1].users_to_router='allow tcp port 80' +add_list opennds.@opennds[-1].users_to_router='allow tcp port 443' +add_list opennds.@opennds[-1].authenticated_users='allow all' +set opennds.@opennds[-1].login_option_enabled='1' +set opennds.@opennds[-1].gatewayinterface='br-captive' +set opennds.@opennds[-1].gatewayname={{ s(interface.captive.gateway_name) }} +set opennds.@opennds[-1].maxclients='{{ interface.captive.max_clients }}' +set opennds.@opennds[-1].gatewayfqdn={{ s(interface.captive.gateway_fqdn) }} +set opennds.@opennds[-1].uploadrate='{{ interface.captive.upload_rate }}' +set opennds.@opennds[-1].downloadrate='{{ interface.captive.download_rate }}' +set opennds.@opennds[-1].uploadquota='{{ interface.captive.upload_quota }}' +set opennds.@opennds[-1].downloadquota='{{ interface.captive.download_quota }}' diff --git a/schema/interface.captive.yml b/schema/interface.captive.yml index 1e05527..fd2f246 100644 --- a/schema/interface.captive.yml +++ b/schema/interface.captive.yml @@ -13,7 +13,7 @@ properties: type: string format: fqdn default: ucentral.splash - maxclients: + max-clients: description: The maximum number of clients that shall be accept. type: integer @@ -22,19 +22,19 @@ properties: description: The maximum upload rate for a specific client. type: integer - default: 10000 + default: 0 download-rate: description: The maximum download rate for a specific client. type: integer - default: 10000 + default: 0 upload-quota: description: The maximum upload quota for a specific client. type: integer - default : 10000 + default : 0 download-quota: description: The maximum download quota for a specific client. type: integer - default: 10000 + default: 0 diff --git a/schema/interface.ssid.captive.yml b/schema/interface.ssid.captive.yml deleted file mode 100644 index 1e05527..0000000 --- a/schema/interface.ssid.captive.yml +++ /dev/null @@ -1,40 +0,0 @@ -description: - This section can be used to setup a captive portal on the AP. -type: object -properties: - gateway-name: - description: - This name will be presented to connecting users in on the splash page. - type: string - default: uCentral - Captive Portal - gateway-fqdn: - description: - The fqdn used for the captive portal IP. - type: string - format: fqdn - default: ucentral.splash - maxclients: - description: - The maximum number of clients that shall be accept. - type: integer - default: 32 - upload-rate: - description: - The maximum upload rate for a specific client. - type: integer - default: 10000 - download-rate: - description: - The maximum download rate for a specific client. - type: integer - default: 10000 - upload-quota: - description: - The maximum upload quota for a specific client. - type: integer - default : 10000 - download-quota: - description: - The maximum download quota for a specific client. - type: integer - default: 10000 diff --git a/schema/interface.ssid.yml b/schema/interface.ssid.yml index 312ac02..73066c7 100644 --- a/schema/interface.ssid.yml +++ b/schema/interface.ssid.yml @@ -80,8 +80,6 @@ properties: type: array items: $ref: "https://ucentral.io/schema/v1/interface/ssid/multi-psk/" - captive: - $ref: "https://ucentral.io/schema/v1/interface/ssid/captive/" rrm: $ref: "https://ucentral.io/schema/v1/interface/ssid/rrm/" rates: diff --git a/schema/interface.yml b/schema/interface.yml index aacd07d..74e2891 100644 --- a/schema/interface.yml +++ b/schema/interface.yml @@ -53,6 +53,8 @@ properties: $ref: "https://ucentral.io/schema/v1/interface/ipv4/" ipv6: $ref: "https://ucentral.io/schema/v1/interface/ipv6/" + captive: + $ref: "https://ucentral.io/schema/v1/interface/captive/" ssids: type: array items: diff --git a/schemareader.uc b/schemareader.uc index dad5ea9..b7b19a0 100644 --- a/schemareader.uc +++ b/schemareader.uc @@ -607,6 +607,71 @@ function instantiateInterfaceIpv6(value) { return obj; } +function instantiateInterfaceCaptive(value) { + assert(type(value) == "object", "Property interface.captive must be of type object"); + + let obj = {}; + + if (exists(value, "gateway-name")) { + assert(type(value["gateway-name"]) == "string", "Property interface.captive.gateway-name must be of type string"); + obj.gateway_name = value["gateway-name"]; + } + else { + obj.gateway_name = "uCentral - Captive Portal"; + } + + if (exists(value, "gateway-fqdn")) { + assert(type(value["gateway-fqdn"]) == "string", "Property interface.captive.gateway-fqdn must be of type string"); + assert(matchFqdn(value["gateway-fqdn"]), "Property interface.captive.gateway-fqdn must match fqdn format"); + obj.gateway_fqdn = value["gateway-fqdn"]; + } + else { + obj.gateway_fqdn = "ucentral.splash"; + } + + if (exists(value, "max-clients")) { + assert(type(value["max-clients"]) == "int", "Property interface.captive.max-clients must be of type integer"); + obj.max_clients = value["max-clients"]; + } + else { + obj.max_clients = 32; + } + + if (exists(value, "upload-rate")) { + assert(type(value["upload-rate"]) == "int", "Property interface.captive.upload-rate must be of type integer"); + obj.upload_rate = value["upload-rate"]; + } + else { + obj.upload_rate = 0; + } + + if (exists(value, "download-rate")) { + assert(type(value["download-rate"]) == "int", "Property interface.captive.download-rate must be of type integer"); + obj.download_rate = value["download-rate"]; + } + else { + obj.download_rate = 0; + } + + if (exists(value, "upload-quota")) { + assert(type(value["upload-quota"]) == "int", "Property interface.captive.upload-quota must be of type integer"); + obj.upload_quota = value["upload-quota"]; + } + else { + obj.upload_quota = 0; + } + + if (exists(value, "download-quota")) { + assert(type(value["download-quota"]) == "int", "Property interface.captive.download-quota must be of type integer"); + obj.download_quota = value["download-quota"]; + } + else { + obj.download_quota = 0; + } + + return obj; +} + function instantiateInterfaceSsidEncryption(value) { assert(type(value) == "object", "Property interface.ssid.encryption must be of type object"); @@ -668,71 +733,6 @@ function instantiateInterfaceSsidMultiPsk(value) { return obj; } -function instantiateInterfaceSsidCaptive(value) { - assert(type(value) == "object", "Property interface.ssid.captive must be of type object"); - - let obj = {}; - - if (exists(value, "gateway-name")) { - assert(type(value["gateway-name"]) == "string", "Property interface.ssid.captive.gateway-name must be of type string"); - obj.gateway_name = value["gateway-name"]; - } - else { - obj.gateway_name = "uCentral - Captive Portal"; - } - - if (exists(value, "gateway-fqdn")) { - assert(type(value["gateway-fqdn"]) == "string", "Property interface.ssid.captive.gateway-fqdn must be of type string"); - assert(matchFqdn(value["gateway-fqdn"]), "Property interface.ssid.captive.gateway-fqdn must match fqdn format"); - obj.gateway_fqdn = value["gateway-fqdn"]; - } - else { - obj.gateway_fqdn = "ucentral.splash"; - } - - if (exists(value, "maxclients")) { - assert(type(value["maxclients"]) == "int", "Property interface.ssid.captive.maxclients must be of type integer"); - obj.maxclients = value["maxclients"]; - } - else { - obj.maxclients = 32; - } - - if (exists(value, "upload-rate")) { - assert(type(value["upload-rate"]) == "int", "Property interface.ssid.captive.upload-rate must be of type integer"); - obj.upload_rate = value["upload-rate"]; - } - else { - obj.upload_rate = 10000; - } - - if (exists(value, "download-rate")) { - assert(type(value["download-rate"]) == "int", "Property interface.ssid.captive.download-rate must be of type integer"); - obj.download_rate = value["download-rate"]; - } - else { - obj.download_rate = 10000; - } - - if (exists(value, "upload-quota")) { - assert(type(value["upload-quota"]) == "int", "Property interface.ssid.captive.upload-quota must be of type integer"); - obj.upload_quota = value["upload-quota"]; - } - else { - obj.upload_quota = 10000; - } - - if (exists(value, "download-quota")) { - assert(type(value["download-quota"]) == "int", "Property interface.ssid.captive.download-quota must be of type integer"); - obj.download_quota = value["download-quota"]; - } - else { - obj.download_quota = 10000; - } - - return obj; -} - function instantiateInterfaceSsidRrm(value) { assert(type(value) == "object", "Property interface.ssid.rrm must be of type object"); @@ -1245,10 +1245,6 @@ function instantiateInterfaceSsid(value) { obj.multi_psk = parseMultiPsk(value["multi-psk"]); } - if (exists(value, "captive")) { - obj.captive = instantiateInterfaceSsidCaptive(value["captive"]); - } - if (exists(value, "rrm")) { obj.rrm = instantiateInterfaceSsidRrm(value["rrm"]); } @@ -1461,6 +1457,10 @@ function instantiateInterface(value) { obj.ipv6 = instantiateInterfaceIpv6(value["ipv6"]); } + if (exists(value, "captive")) { + obj.captive = instantiateInterfaceCaptive(value["captive"]); + } + function parseSsids(value) { assert(type(value) == "array", "Property interface.ssids must be of type array"); @@ -1821,7 +1821,6 @@ function instantiateMetricsStatistics(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"]; } diff --git a/ucentral.schema.json b/ucentral.schema.json index 0a2dd70..d2742c3 100644 --- a/ucentral.schema.json +++ b/ucentral.schema.json @@ -477,6 +477,40 @@ } } }, + "interface.captive": { + "type": "object", + "properties": { + "gateway-name": { + "type": "string", + "default": "uCentral - Captive Portal" + }, + "gateway-fqdn": { + "type": "string", + "format": "fqdn", + "default": "ucentral.splash" + }, + "max-clients": { + "type": "integer", + "default": 32 + }, + "upload-rate": { + "type": "integer", + "default": 0 + }, + "download-rate": { + "type": "integer", + "default": 0 + }, + "upload-quota": { + "type": "integer", + "default": 0 + }, + "download-quota": { + "type": "integer", + "default": 0 + } + } + }, "interface.ssid.encryption": { "type": "object", "properties": { @@ -539,40 +573,6 @@ } } }, - "interface.ssid.captive": { - "type": "object", - "properties": { - "gateway-name": { - "type": "string", - "default": "uCentral - Captive Portal" - }, - "gateway-fqdn": { - "type": "string", - "format": "fqdn", - "default": "ucentral.splash" - }, - "maxclients": { - "type": "integer", - "default": 32 - }, - "upload-rate": { - "type": "integer", - "default": 10000 - }, - "download-rate": { - "type": "integer", - "default": 10000 - }, - "upload-quota": { - "type": "integer", - "default": 10000 - }, - "download-quota": { - "type": "integer", - "default": 10000 - } - } - }, "interface.ssid.rrm": { "type": "object", "properties": { @@ -1008,9 +1008,6 @@ "$ref": "#/$defs/interface.ssid.multi-psk" } }, - "captive": { - "$ref": "#/$defs/interface.ssid.captive" - }, "rrm": { "$ref": "#/$defs/interface.ssid.rrm" }, @@ -1167,6 +1164,9 @@ "ipv6": { "$ref": "#/$defs/interface.ipv6" }, + "captive": { + "$ref": "#/$defs/interface.captive" + }, "ssids": { "type": "array", "items": { @@ -1411,8 +1411,7 @@ "type": "object", "properties": { "interval": { - "type": "integer", - "minimum": 60 + "type": "integer" }, "types": { "type": "array",