mirror of
				https://github.com/Telecominfraproject/ols-ucentral-schema.git
				synced 2025-10-30 17:47:59 +00:00 
			
		
		
		
	Compare commits
	
		
			117 Commits
		
	
	
		
			schema_fix
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 02f749cc11 | ||
|   | 5b00327adc | ||
|   | 117aa70d4f | ||
|   | c3073a9a45 | ||
|   | 04a8901620 | ||
|   | 23ebf25cba | ||
|   | c9b4970b29 | ||
|   | fdf54a7e0e | ||
|   | 52e38ce792 | ||
|   | 05849922ed | ||
|   | 9228a6c290 | ||
|   | 41e621b455 | ||
|   | 30b5864ada | ||
|   | 598af29d51 | ||
|   | c79f7f4517 | ||
|   | 0e43b3cb3a | ||
|   | 67f3f14fab | ||
|   | 8c82a276d8 | ||
|   | a098465268 | ||
|   | 040650cb5c | ||
|   | adf3514ae9 | ||
|   | d8d4380977 | ||
|   | c63ac1f5d9 | ||
|   | 37b9152b6e | ||
|   | 5db059b95b | ||
|   | 5a7f055793 | ||
|   | 2b7ce76453 | ||
|   | d1ab8b453b | ||
|   | e80a6d2166 | ||
|   | 6469510af1 | ||
|   | d84e5ee624 | ||
|   | 029cdb4ed9 | ||
|   | cd7d50997c | ||
|   | f394cb4019 | ||
|   | 5e345b22a3 | ||
|   | dcd935359c | ||
|   | 362e03a363 | ||
|   | 51c5b1b9f4 | ||
|   | 5d50740f98 | ||
|   | 01d4c80824 | ||
|   | 76cc0646e0 | ||
|   | ffe61ea929 | ||
|   | 078c2021eb | ||
|   | a1e044834b | ||
|   | 18d5b2c475 | ||
|   | 374fab81db | ||
|   | 6bc313b440 | ||
|   | 1d052a18c2 | ||
|   | 69bc3a60b7 | ||
|   | cb0069a356 | ||
|   | e1a110bc7f | ||
|   | 63d1103ef3 | ||
|   | 77b79d1007 | ||
|   | b148155dea | ||
|   | 59ad89be0f | ||
|   | 8e32d2775a | ||
|   | 548b76a948 | ||
|   | 4f2a23741b | ||
|   | 44c07718e3 | ||
|   | adeeb0457b | ||
|   | 0ed83ba0a5 | ||
|   | a03b5620c5 | ||
|   | d37fc0b7eb | ||
|   | 00403aa20a | ||
|   | b5845fbd89 | ||
|   | b6ac5a6450 | ||
|   | 3faa3421d3 | ||
|   | 7bffbb1cc9 | ||
|   | e96efa25ae | ||
|   | 5622b66bb8 | ||
|   | 5bc20c20b3 | ||
|   | 9a7f469e61 | ||
|   | a8d8de9b4d | ||
|   | 53c239b60d | ||
|   | 9a994374b5 | ||
|   | 44ed03b3f7 | ||
|   | a72be45c21 | ||
|   | 7c62326155 | ||
|   | 5291c3da99 | ||
|   | 1b12452eeb | ||
|   | 76504b1ad6 | ||
|   | bcde6a7155 | ||
|   | 82f5eb7740 | ||
|   | ceccdef561 | ||
|   | 80a598fadf | ||
|   | 81e8cd5706 | ||
|   | 8a4815187f | ||
|   | e8da89616e | ||
|   | 5da5b090be | ||
|   | f9e15067ff | ||
|   | 4235960ab8 | ||
|   | 7e839b0681 | ||
|   | b98f8a2b46 | ||
|   | 1de6cad7e8 | ||
|   | 0f9f4489d2 | ||
|   | 5dc634f78e | ||
|   | 4d03a432c1 | ||
|   | ee945311e1 | ||
|   | 4336be981b | ||
|   | 747ccb4993 | ||
|   | a0fac0b3d7 | ||
|   | 1e33d3fa0a | ||
|   | dc754dc519 | ||
|   | b3374bb60b | ||
|   | 0ef7362930 | ||
|   | b9762df2cf | ||
|   | 0983abe2bf | ||
|   | 719fd97705 | ||
|   | 777e2b26a8 | ||
|   | ab43179a83 | ||
|   | 3a41591f85 | ||
|   | 7fa4d15f5e | ||
|   | d105fe165e | ||
|   | d3f610d9ef | ||
|   | 711d7d9066 | ||
|   | 89a78c61be | ||
|   | f5608bd42c | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,2 @@ | ||||
| /jsdoc.conf.json | ||||
| docs/ | ||||
| node_modules | ||||
|   | ||||
							
								
								
									
										28
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| BSD 3-Clause License | ||||
|  | ||||
| Copyright (c) 2024, Telecom Infra Project | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
|  | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
|    list of conditions and the following disclaimer. | ||||
|  | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|    this list of conditions and the following disclaimer in the documentation | ||||
|    and/or other materials provided with the distribution. | ||||
|  | ||||
| 3. Neither the name of the copyright holder nor the names of its | ||||
|    contributors may be used to endorse or promote products derived from | ||||
|    this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										311
									
								
								capabilities/connect.capabilities.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								capabilities/connect.capabilities.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | ||||
| description: | ||||
|   uCentral protocol (OpenLan) device and features capabilities schema | ||||
| type: object | ||||
| properties: | ||||
|   serial: | ||||
|     type: string | ||||
|     examples: | ||||
|     - aabbccddeeff | ||||
|   firmware: | ||||
|     type: string | ||||
|     description: | ||||
|       Platform revision | ||||
|     examples: | ||||
|     - Rel 1.6 build 5 | ||||
|   version: | ||||
|     type: object | ||||
|     description: | ||||
|       Switch version info, OLS release and schema. | ||||
|     properties: | ||||
|       switch: | ||||
|         type: object | ||||
|         description: the ols client version for this Switch | ||||
|         properties: | ||||
|           major: | ||||
|             type: integer | ||||
|           minor: | ||||
|             type: integer | ||||
|           patch: | ||||
|             type: integer | ||||
|         examples: | ||||
|         - 'major': 3 | ||||
|           'minor': 2 | ||||
|           'patch': 0 | ||||
|       schema: | ||||
|         type: object | ||||
|         description: the ols schema version used with the ols client. | ||||
|         properties: | ||||
|           major: | ||||
|             type: integer | ||||
|           minor: | ||||
|             type: integer | ||||
|           patch: | ||||
|             type: integer | ||||
|         examples: | ||||
|         - 'major': 3 | ||||
|           'minor': 2 | ||||
|           'patch': 0 | ||||
|   platform: | ||||
|     type: string | ||||
|     enum: | ||||
|     - Switch | ||||
|     - AP | ||||
|   model: | ||||
|     type: string | ||||
|     description: | ||||
|       Device model | ||||
|   hw-sku: | ||||
|     type: string | ||||
|     description: | ||||
|       Stock keeping unit | ||||
|   compatible: | ||||
|     type: string | ||||
|     description: | ||||
|       Compatibility string, that defines the family of the device | ||||
|   base-mac: | ||||
|     type: string | ||||
|     description: | ||||
|       Switch MAC address | ||||
|     format: uc-mac | ||||
|     examples: | ||||
|     - aa:bb:cc:dd:ee:ff | ||||
|   port-list: | ||||
|     type: array | ||||
|     description: | ||||
|       The list of physical network devices | ||||
|     items: | ||||
|       type: object | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|           description: | ||||
|             The logical name of the port that is used by the OS | ||||
|           examples: | ||||
|           - Ethernet0 | ||||
|           - Ethernet1 | ||||
|           - Ethernet76 | ||||
|         front-panel-number: | ||||
|           type: integer | ||||
|           description: | ||||
|             The identification number of the port as can be seen on the front-panel of the device | ||||
|   port-capabilities: | ||||
|     type: object | ||||
|     description: | ||||
|       Description of physical ports and their form-factors | ||||
|     properties: | ||||
|       form-factors: | ||||
|         type: array | ||||
|         items: | ||||
|           type: string | ||||
|           enum: | ||||
|           - RJ45 | ||||
|           - SFP | ||||
|           - SFP+ | ||||
|           - SFP28 | ||||
|           - SFP-DD | ||||
|           - QSFP | ||||
|           - QSFP+ | ||||
|           - QSFP28 | ||||
|           - QSFP-DD | ||||
|       ports-list: | ||||
|         type: array | ||||
|         items: | ||||
|           type: object | ||||
|           properties: | ||||
|             type: | ||||
|               type: string | ||||
|               examples: | ||||
|               - RJ45 | ||||
|             ports: | ||||
|               type: array | ||||
|               items: | ||||
|                 type: string | ||||
|                 examples: | ||||
|                 - Ethernet1 | ||||
|   poe-capabilities: | ||||
|     type: object | ||||
|     description: | ||||
|       Description of physical ports and their PoE capabilities | ||||
|     properties: | ||||
|       supported-standards: | ||||
|         type: array | ||||
|         items: | ||||
|           type: string | ||||
|           enum: | ||||
|           - .3AF-POE | ||||
|           - .3AT-POE+ | ||||
|           - .3BT-PoE++ | ||||
|           - PreStandard-Passive | ||||
|       power-budget: | ||||
|         type: integer | ||||
|         examples: | ||||
|         - 2000 | ||||
|       poe-ports: | ||||
|         type: array | ||||
|         items: | ||||
|           type: object | ||||
|           properties: | ||||
|             type: | ||||
|               type: string | ||||
|               examples: | ||||
|               - .3AF-POE | ||||
|             budget-capacity: | ||||
|               type: integer | ||||
|             ports: | ||||
|               type: array | ||||
|               items: | ||||
|                 type: string | ||||
|                 examples: | ||||
|                 - Ethernet1 | ||||
|   mclag-capabilities: | ||||
|     description: Capabilities of the MC-LAG (Multi-Chassis Link Aggregation) feature in the switch | ||||
|     type: object | ||||
|     properties: | ||||
|       max-mclag-groups: | ||||
|         description: Defines the maximum number of MC-LAG groups that can be configured on the switch. | ||||
|         type: integer | ||||
|       max-ports-per-mclag-group: | ||||
|         description: Specifies the maximum number of physical ports that can be part of a single MC-LAG group. | ||||
|         type: integer | ||||
|       max-vlans-per-mclag-group: | ||||
|         description: Indicates the maximum number of VLANs that can be supported within a single MC-LAG group. | ||||
|         type: integer | ||||
|       dual-active-detection: | ||||
|         description: Describes the dual-active detection mechanism to prevent both switches from becoming active simultaneously. | ||||
|         type: string | ||||
|         enum: | ||||
|           - ICCP | ||||
|           - Backup-Link | ||||
|           - None | ||||
|       failover-time-milliseconds: | ||||
|         description: Specifies the time (in milliseconds) required for traffic to fail over to the secondary switch when there is a failure in the primary switch. | ||||
|         type: integer | ||||
|       vlan-synchronization: | ||||
|         description: Indicates whether VLAN synchronization across MC-LAG peers is supported and the number of VLANs that can be synchronized. | ||||
|         type: boolean | ||||
|       max-mac-entries-per-mclag: | ||||
|         description: Maximum number of MAC address entries that can be synchronized across MC-LAG peers. | ||||
|         type: integer | ||||
|   lldp-capabilities: | ||||
|     type: object | ||||
|     description: | ||||
|       Description of LLDP capabilities across different switch models/vendors. | ||||
|     properties: | ||||
|       supported-tlvs: | ||||
|         type: array | ||||
|         items: | ||||
|           type: string | ||||
|           enum: | ||||
|           - lldp-basic-tlv-mgmt-ip-v4 | ||||
|           - lldp-basic-tlv-mgmt-ip-v6 | ||||
|           - lldp-basic-tlv-port-descr | ||||
|           - lldp-basic-tlv-sys-capab | ||||
|           - lldp-basic-tlv-sys-descr | ||||
|           - lldp-basic-tlv-sys-name | ||||
|           - lldp-dot1-tlv-proto-ident | ||||
|           - lldp-dot1-tlv-proto-vid | ||||
|           - lldp-dot1-tlv-pvid | ||||
|           - lldp-dot1-tlv-vlan-name | ||||
|           - lldp-dot3-tlv-link-agg | ||||
|           - lldp-dot3-tlv-mac-phy | ||||
|           - lldp-dot3-tlv-max-frame | ||||
|           - lldp-dot3-tlv-poe | ||||
|           - lldp-med-location-civic-addr | ||||
|           - lldp-med-tlv-ext-poe | ||||
|           - lldp-med-tlv-inventory | ||||
|           - lldp-med-tlv-location | ||||
|           - lldp-med-tlv-med-cap | ||||
|           - lldp-med-tlv-network-policy | ||||
|       max-neighbors: | ||||
|         type: integer | ||||
|         description: Maximum number of LLDP neighbors a switch can discover and maintain. | ||||
|         examples: | ||||
|         - 64 | ||||
|         - 128 | ||||
|         - 256 | ||||
|   supported-features: | ||||
|     type: array | ||||
|     description: | ||||
|       List of all features supported by the device | ||||
|     items: | ||||
|       type: string | ||||
|       enum: | ||||
|       # L2 | ||||
|       - VLAN | ||||
|       - VLAN-Voice | ||||
|       - Jumbo-Frames | ||||
|       - Link-Aggregation-LACP | ||||
|       - Link-Aggregation-Static | ||||
|       - Link-Aggregation-MCLAG | ||||
|       - Port-Isolation | ||||
|       - Spanning-Tree | ||||
|       - Spanning-Tree-Rapid | ||||
|       - Spanning-Tree-Per-VLAN | ||||
|       - Spanning-Tree-Per-VLAN-Rapid | ||||
|       - Spanning-Tree-MSTP | ||||
|       - BPDU-Guard | ||||
|       # L3 | ||||
|       - SVI-StaticIPv4 | ||||
|       - SVI-StaticIPv6 | ||||
|       - Interface-StaticIPv4 | ||||
|       - Interface-StaticIPv6 | ||||
|       - Routing-VRF | ||||
|       - Routing-IPv4-Route-Blackhole | ||||
|       - Routing-IPv4-Route-Unreachable | ||||
|       - Routing-IPv4-Nexthop | ||||
|       - Routing-IPv4-Broadcast | ||||
|       - Routing-IPv4-Multicast-IGMP-Snooping | ||||
|       - Routing-IPv4-Multicast-IGMP-Querier | ||||
|       - Routing-IPv4-Multicast-IGMP-Static | ||||
|       - Routing-IPv4-DHCP-Server | ||||
|       - Routing-IPv4-DHCP-Relay | ||||
|       - Routing-IPv4-DHCP-Snooping | ||||
|       - Routing-IPv4-Port-Forward | ||||
|       - Routing-IPv6-DHCP-Relay | ||||
|       - Routing-IPv6-DHCP-Stateful | ||||
|       - Routing-IPv6-DHCP-Stateless | ||||
|       - Routing-IPv6-Port-Forward | ||||
|       - Multicast-VLAN-Registration | ||||
|       # PoE | ||||
|       - PoE-Reset | ||||
|       # .1X | ||||
|       - Port-Access-Control | ||||
|       - PAC-Dynamic-Auth | ||||
|       - mac-address-bypass | ||||
|       # System | ||||
|       - System-PasswordChange | ||||
|       - System-SwUpdate | ||||
|       - System-SwUpdate-Partial | ||||
|       - Port-Mirroring | ||||
|       # Security | ||||
|       - MAC-ACL | ||||
|       - IP-ACL | ||||
|       - Guest-VLAN | ||||
|       - Storm-Control | ||||
|       # Services | ||||
|       - Service-SSH | ||||
|       - Service-RSSH | ||||
|       - Service-Telnet | ||||
|       - Service-LLDP | ||||
|       - Service-HTTP | ||||
|       - Service-HTTPS | ||||
|       - Service-GPS | ||||
|       - Service-IGMP | ||||
|       - Service-NTP | ||||
|       - Service-NTP-Client | ||||
|       - Service-MDNS | ||||
|       - Service-QoS | ||||
|       - Service-Syslog | ||||
|       - Service-PAC | ||||
|       - Service-Wireguard-Overlay | ||||
|       - Service-Radius-Proxy | ||||
|       - Service-Online-Check | ||||
|       - Service-CaptivePortal | ||||
|       - Service-PublicIpCheck | ||||
|       - Service-Global-DNS | ||||
|       # Tunneling | ||||
|       - Tunneling-VxLAN | ||||
|       - Tunneling-GRE | ||||
|       - Tunneling-GRE6 | ||||
|       - Tunneling-L2TP | ||||
|       - Tunneling-Mesh | ||||
| @@ -1,90 +0,0 @@ | ||||
| #!/usr/bin/ucode | ||||
| push(REQUIRE_SEARCH_PATH, '/usr/share/ucentral/*.uc'); | ||||
| let fs = require("fs"); | ||||
| let uci = require("uci"); | ||||
| let ubus = require("ubus"); | ||||
| let capabfile = fs.open("/etc/ucentral/capabilities.json", "r"); | ||||
| let capab = json(capabfile.read("all")); | ||||
| let pipe = fs.popen('fw_printenv developer'); | ||||
| let developer = replace(pipe.read("all"), '\n', ''); | ||||
| pipe.close(); | ||||
| let restrict = {}; | ||||
| if (developer != 'developer=1') { | ||||
| 	let restrictfile = fs.open("/etc/ucentral/restrictions.json", "r"); | ||||
| 	restrict = restrictfile ? json(restrictfile.read("all")) : {}; | ||||
| } | ||||
| let cmdfile = fs.open(ARGV[0], "r"); | ||||
| let cmd = json(cmdfile.read("all")); | ||||
| let id = ARGV[1]; | ||||
| let ctx = ubus.connect(); | ||||
|  | ||||
| if (!ctx) { | ||||
| 	warn("Unable to connect to ubus: " + ubus.error() + "\n"); | ||||
| 	exit(1); | ||||
| } | ||||
|  | ||||
| /* Convenience logger outputting to both stderr and remote central log */ | ||||
| function log(fmt, ...args) { | ||||
| 	let msg = sprintf(fmt, ...args); | ||||
|  | ||||
| 	warn(msg + "\n"); | ||||
|  | ||||
| 	ctx.call("ucentral", "log", { msg: msg }); | ||||
| } | ||||
|  | ||||
| function result(code, fmt, ...args) { | ||||
| 	let text = sprintf(fmt, ...args); | ||||
|  | ||||
| 	ctx.call("ucentral", "result", { | ||||
| 		"status": { | ||||
| 			"error": code, | ||||
| 			"text": text | ||||
| 		}, "id": +id | ||||
| 	}); | ||||
| 	warn(text + "\n"); | ||||
| } | ||||
|  | ||||
| function result_json(status) { | ||||
| 	ctx.call("ucentral", "result", {"id": +id, "status": status}); | ||||
| 	if (status.text) | ||||
| 		warn(status.text + "\n"); | ||||
| 	if (status.resultText) | ||||
| 		warn(status.resultText + "\n"); | ||||
| } | ||||
|  | ||||
| /* Scope of functions and ressources the command includes have access to */ | ||||
| let scope = { | ||||
| 	/* ressources */ | ||||
| 	uci, | ||||
| 	cursor: uci.cursor(), | ||||
| 	ctx, | ||||
| 	fs, | ||||
| 	restrict, | ||||
|  | ||||
| 	/* log helper */ | ||||
| 	log, | ||||
|  | ||||
| 	/* result helpers */ | ||||
| 	result, | ||||
| 	result_json, | ||||
|  | ||||
| 	/* command argument object */ | ||||
| 	args: (cmd.payload || {}), | ||||
|  | ||||
| 	/* cmd id */ | ||||
| 	id: (id || 0) | ||||
| }; | ||||
|  | ||||
| if (match(cmd.command, /^[A-Za-z0-9_]+$/)) { | ||||
| 	try { | ||||
| 		include(sprintf("cmd_%s.uc", cmd.command), scope); | ||||
| 	} | ||||
| 	catch (e) { | ||||
| 		log("Exception invoking '%s' command module: %s\n%s\n", | ||||
| 			cmd.cmd, e, e.stacktrace[0].context); | ||||
| 	} | ||||
| } | ||||
| else { | ||||
| 	log("Invalid command module name specified"); | ||||
| } | ||||
|  | ||||
| @@ -1,29 +0,0 @@ | ||||
| let tar = b64dec(args.certificates); | ||||
|  | ||||
| if (!tar || !fs.writefile('/tmp/certs.tar', tar)) { | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": 'failed to extract certificates' | ||||
| 	}); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| if (system('/sbin/certupdate')) { | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": 'failed to update certificates' | ||||
| 	}); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| include('reboot_cause.uc', { reason: 'certupdate' }); | ||||
|  | ||||
| ctx.call("ucentral", "result", { | ||||
|                 "status": { | ||||
|                         "error": 0, | ||||
|                         "text": 'Success' | ||||
|                 }, "id": +id | ||||
|         }); | ||||
| sleep(5000); | ||||
| system("(sleep 10; jffs2reset -y -r)&"); | ||||
| system("/etc/init.d/ucentral stop"); | ||||
| @@ -1,50 +0,0 @@ | ||||
| let reset_cmdline = [ 'jffs2reset', '-r', '-y' ]; | ||||
|  | ||||
| if (length(args) && args.keep_redirector) { | ||||
| 	let archive_cmdline = [ | ||||
| 		'tar', 'czf', '/sysupgrade.tgz', | ||||
| 		"/etc/config/ucentral" | ||||
| 	]; | ||||
|  | ||||
| 	let files = [ | ||||
| 			"/etc/ucentral/cas.pem", "/etc/ucentral/cert.pem", | ||||
| 			"/etc/ucentral/gateway.json", "/etc/ucentral/dev-id", | ||||
| 			"/etc/ucentral/key.pem", "/etc/ucentral/profile.json" | ||||
| 	]; | ||||
| 	for (let f in files) | ||||
| 		if (fs.stat(f)) | ||||
| 			push(archive_cmdline, f); | ||||
|  | ||||
| 	let active_config = fs.readlink("/etc/ucentral/ucentral.active"); | ||||
|  | ||||
| 	if (active_config) | ||||
| 		push(archive_cmdline, '/etc/ucentral/ucentral.active', active_config); | ||||
| 	else | ||||
| 		result_json({ | ||||
| 			"error": 2, | ||||
| 			"text": sprintf("Unable to determine active configuration: %s", fs.error()) | ||||
| 		}); | ||||
|  | ||||
| 	let rc = system(archive_cmdline); | ||||
|  | ||||
| 	if (rc != 0) { | ||||
| 		result_json({ | ||||
| 			"error": 2, | ||||
| 			"text": sprintf("Archive command %s exited with non-zero code %d", archive_cmdline, rc) | ||||
| 		}); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	push(reset_cmdline, '-k'); | ||||
| } | ||||
|  | ||||
| include('reboot_cause.uc', { reason: 'factory' }); | ||||
|  | ||||
| let rc = system(reset_cmdline); | ||||
|  | ||||
| if (rc != 0) | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": sprintf("Reset command %s exited with non-zero code %d", reset_cmdline, rc) | ||||
| 	}); | ||||
| @@ -1,15 +0,0 @@ | ||||
| function set_led(val, path) | ||||
| { | ||||
| 	let cursor = uci.cursor(path); | ||||
| 	cursor.set("system", "@system[-1]", "leds_off", val); | ||||
| 	cursor.commit(); | ||||
| } | ||||
|  | ||||
| let val = 0; | ||||
| if (args.pattern ==  "off") | ||||
| 	val = 1; | ||||
|  | ||||
| set_led(val); | ||||
| set_led(val, "/etc/config-shadow/"); | ||||
|  | ||||
| system("/etc/init.d/led restart"); | ||||
| @@ -1,15 +0,0 @@ | ||||
| let rc = system(args.command); | ||||
|  | ||||
| if (rc != 0) { | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": "Command returned an error", | ||||
| 		"resultCode": rc | ||||
| 	}); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
| result_json({ | ||||
| 	"error": 0, | ||||
| 	"text": "Command was executed" | ||||
| }); | ||||
| @@ -1,11 +0,0 @@ | ||||
| log("Initiating reboot"); | ||||
|  | ||||
| include('reboot_cause.uc', { reason: 'reboot' }); | ||||
|  | ||||
| system("(sleep 10; reboot)&"); | ||||
| system("/etc/init.d/ucentral stop"); | ||||
|  | ||||
| let err = ctx.error(); | ||||
|  | ||||
| if (err != null) | ||||
| 	result(2, "Reboot call failed with status %s", err); | ||||
| @@ -1,66 +0,0 @@ | ||||
| function log(msg) { | ||||
| 	system('logger RRM: ' + msg ); | ||||
| } | ||||
|  | ||||
| let handlers = { | ||||
|         // ubus call usteer2 command '{"action": "kick", "addr": "1c:57:dc:37:3c:b1", "reason": 5, "ban_time": 30 }' | ||||
| 	kick: function(params) { | ||||
| 		if (!params.addr) | ||||
| 			return false; | ||||
| 		params.reason ??= 5; | ||||
| 		params.ban_time ??= 30; | ||||
| 		return true; | ||||
| 	}, | ||||
|  | ||||
| 	// ubus call usteer2 command '{"action": "beacon_request", "addr": "4e:7f:3e:2c:8a:68", "channel": 36 }' | ||||
|         // ubus call usteer2 command '{"action": "beacon_request", "addr": "4e:7f:3e:2c:8a:68", "ssid": "Cockney" }' | ||||
| 	beacon_request: function(params) { | ||||
| 		if (!params.addr) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	}, | ||||
|  | ||||
| 	// ubus call usteer2 command '{"action": "channel_switch", "bssid": "34:eF:b6:aF:48:b1", "params": "channel": 4, "band": "2G"}' | ||||
| 	channel_switch: function(params) { | ||||
| 		if (!params.bssid || !params.channel) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	}, | ||||
|  | ||||
| 	// ubus call usteer2 command '{"action": "tx_power", "bssid": "34:eF:b6:aF:48:b1", "level": 20 }' | ||||
| 	tx_power: function(params) { | ||||
| 		if (!params.bssid || !params.level) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	}, | ||||
|  | ||||
| 	// ubus call usteer2 command '{"action": "bss_transition", "addr": "4e:7f:3e:2c:8a:68", "params": "neighbors": ["34:ef:b6:af:48:b1"] }' | ||||
| 	bss_transition: function(params) { | ||||
| 		if (!params.addr || !params.neighbors) | ||||
| 			return false; | ||||
| 		for (let neighbor in params.neighbors) | ||||
| 			if (type(neighbor) != 'string') | ||||
| 				return false; | ||||
| 		return true; | ||||
| 	}, | ||||
|  | ||||
| 	// ubus call usteer2 command '{"action": "neighbors", "neighbors": [ [ "00:11:22:33:44:55", "OpenWifi", "34efb6af48b1af4900005301070603010300" ], [ "aa:bb:cc:dd:ee:ff", "OpenWifi2", "34efb6af48b1af4900005301070603010300" ] ] }' | ||||
| 	neighbors: function(params) { | ||||
| 		if (!params.neighbors) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| if (type(args.actions) != 'array') | ||||
| 	return; | ||||
|  | ||||
| for (let action in args.actions) { | ||||
| 	if (type(action) != 'object') | ||||
| 		continue; | ||||
| 	if (!handlers[action.action] || !handlers[action.action](action)) | ||||
| 		continue; | ||||
| 	action.event = true; | ||||
| 	let result = ctx.call('rrm', 'command', action); | ||||
| 	log(result); | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| if (!args.id || !args.server || !args.port || !args.token || !args.timeout) { | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": "Invalid parameters.", | ||||
| 		"resultCode": -1 | ||||
| 	}); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| if (restrict.rtty) { | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": "RTTY is restricted.", | ||||
| 		"resultCode": -1 | ||||
| 	}); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
|  | ||||
| cursor.load("rtty"); | ||||
| cursor.set("rtty", "@rtty[-1]", "enable", 1); | ||||
| cursor.set("rtty", "@rtty[-1]", "id", args.id); | ||||
| cursor.set("rtty", "@rtty[-1]", "host", args.server); | ||||
| cursor.set("rtty", "@rtty[-1]", "port", args.port); | ||||
| cursor.set("rtty", "@rtty[-1]", "token", args.token); | ||||
| cursor.set("rtty", "@rtty[-1]", "timeout", args.timeout); | ||||
| cursor.commit(); | ||||
|  | ||||
| system("/etc/init.d/rtty restart"); | ||||
| result_json({ | ||||
| 	"error": 0, | ||||
| 	"text": "Command was executed" | ||||
| }); | ||||
| @@ -1,93 +0,0 @@ | ||||
| let uloop = require('uloop'); | ||||
| let fs = require('fs'); | ||||
| let result; | ||||
| let abort; | ||||
| let signature = require('signature'); | ||||
| if (args.type == 'diagnostic') { | ||||
| 	system('cp /usr/share/ucentral/diagnostic.uc /tmp/script.cmd'); | ||||
| } else { | ||||
| 	let decoded = b64dec(args.script); | ||||
| 	if (!decoded) { | ||||
| 		result_json({ | ||||
| 			"error": 2, | ||||
| 			"result": "invalid base64" | ||||
| 		}); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	let script = fs.open("/tmp/script.cmd", "w"); | ||||
| 	script.write(decoded); | ||||
| 	script.close(); | ||||
| 	fs.chmod("/tmp/script.cmd", 700); | ||||
| } | ||||
|  | ||||
| if (args.type != 'diagnostic' && | ||||
|     restrict.commands && | ||||
|     !signature.verify("/tmp/script.cmd", args.signature)) { | ||||
| 	result_json({ | ||||
| 		"error": 3, | ||||
| 		"result": "invalid signature" | ||||
| 	}); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| let out = ''; | ||||
| if (args.uri) { | ||||
| 	result_json({ error: 0, result: 'pending'}); | ||||
| 	out = `/tmp/bundle.${id}.tar.gz`; | ||||
| } | ||||
|  | ||||
| uloop.init(); | ||||
|  | ||||
| let t = uloop.task( | ||||
|         function(pipe) { | ||||
| 		switch (args.type) { | ||||
| 		case 'diagnostic': | ||||
| 		case 'bundle': | ||||
| 			let bundle = require('bundle'); | ||||
| 			bundle.init(id); | ||||
| 			try { | ||||
| 				include('/tmp/script.cmd', { bundle }); | ||||
| 			} catch(e) { | ||||
| 				//e.stacktrace[0].context | ||||
| 			}; | ||||
| 			bundle.complete(); | ||||
| 			return; | ||||
| 		default: | ||||
| 			let stdout = fs.popen("/tmp/script.cmd " + out); | ||||
| 			let result = stdout.read("all"); | ||||
| 			let error = stdout.close(); | ||||
| 			return { result, error }; | ||||
| 		} | ||||
|         }, | ||||
|  | ||||
|         function(res) { | ||||
|                 result = res;         | ||||
|                 uloop.end(); | ||||
|         } | ||||
| ); | ||||
| if (args.timeout) | ||||
|         uloop.timer(args.timeout * 1000, function() { | ||||
|                 t.kill(); | ||||
|                 uloop.end(); | ||||
|                 abort = true; | ||||
|         }); | ||||
|  | ||||
|  | ||||
| uloop.run(); | ||||
|  | ||||
| if (abort) | ||||
|         result = { | ||||
|                 "error": 255, | ||||
|                 "result": "timed out" | ||||
|         }; | ||||
|  | ||||
| if (args.uri && !fs.stat(out)) { | ||||
| 	result_json({ error: 1, | ||||
| 		      result: 'script did not generate any output'}); | ||||
| } else if (args.uri) { | ||||
| 	ctx.call("ucentral", "upload", {file: out, uri: args.uri, uuid: args.serial}); | ||||
| 	result_json({ error: 0, | ||||
| 		      result: 'done'}); | ||||
| } else | ||||
| 	result_json(result || { result: 255, error: 'unknown'}); | ||||
| @@ -1 +0,0 @@ | ||||
| include("./state.uc", { stats: args.stats }); | ||||
| @@ -1,20 +0,0 @@ | ||||
| let log_data = ctx.call("log", "read", { | ||||
| 	lines: +args.lines || 100, | ||||
| 	oneshot: true, | ||||
| 	stream: false | ||||
| }); | ||||
|  | ||||
| if (!log_data || !log_data.log) { | ||||
| 	result(2, "Unable to obtain system log contents: %s", ubus.error()); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| warn(sprintf("Read %d lines\n", length(log_data.log))); | ||||
|  | ||||
| result_json({ | ||||
| 	"error": 0, | ||||
| 	"text": "Success", | ||||
| 	"resultCode": 0, | ||||
| 	"resultData": log_data | ||||
| }); | ||||
| @@ -1,65 +0,0 @@ | ||||
| let serial = cursor.get("ucentral", "config", "serial"); | ||||
|  | ||||
| if (!serial) | ||||
| 	return; | ||||
|  | ||||
| if (args.network) { | ||||
| 	let net = ctx.call("network.interface", "status", { interface: args.network }); | ||||
|  | ||||
| 	if (net && net.l3_device) | ||||
| 		args.interface = net.l3_device; | ||||
| } | ||||
|  | ||||
| if (!args.interface || !length(args.interface)) | ||||
| 	args.interface = args.network; | ||||
|  | ||||
| if (!match(args.interface, /^[^\/]+$/) || (args.interface != "any" && !fs.stat("/sys/class/net/" + args.interface))) { | ||||
| 	result_json({ | ||||
| 		"error": 1, | ||||
| 		"text": "Failed", | ||||
| 		"resultCode": 1, | ||||
| 		"resultText": "Invalid network device specified" | ||||
| 	}); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| let duration = +args.duration || 0; | ||||
| let packets = +args.packets || 0; | ||||
| let filename = sprintf("/tmp/pcap-%s-%d", serial, time()); | ||||
| let sys = ctx.call('system', 'info'); | ||||
|  | ||||
| let command =	[ | ||||
| 	'tcpdump_timeout', | ||||
| 	duration, | ||||
| 	'-C', sys.memory.free / 4, | ||||
| 	'-W', '1', | ||||
| 	'-w', filename, | ||||
| 	'-i', args.interface | ||||
| ]; | ||||
|  | ||||
| if (!duration) { | ||||
| 	push(command, '-c'); | ||||
| 	push(command, packets); | ||||
| } | ||||
| let rc = system(command); | ||||
|  | ||||
| if (rc != 0) { | ||||
| 	result_json({ | ||||
| 		"error": 1, | ||||
| 		"text": "Failed", | ||||
| 		"resultCode": rc, | ||||
| 		"resultText": "tcpdump command exited with non-zero code" | ||||
| 	}); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| ctx.call("ucentral", "upload", {file: filename, uri: args.uri, uuid: args.serial}); | ||||
|  | ||||
| result_json({ | ||||
| 	"error": 0, | ||||
| 	"text": "Success", | ||||
| 	"resultCode": 0, | ||||
| 	"resultText": "Uploading file" | ||||
| }); | ||||
| @@ -1,20 +0,0 @@ | ||||
| log("Initiating gateway transfer"); | ||||
|  | ||||
| if (!args.server || !args.port) { | ||||
| 	result(2, "invalid arguments"); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| fs.writefile('/etc/ucentral/gateway.json', { server: args.server, port: args.port }); | ||||
| system('cp /etc/ucentral/ucentral.cfg.0000000001 /etc/ucentral/ucentral.cfg.0000000002'); | ||||
| system('rm /etc/ucentral/ucentral.cfg.1* /etc/ucentral/ucentral.active'); | ||||
|  | ||||
| include('reboot_cause.uc', { reason: 'transfer' }); | ||||
|  | ||||
| system("(sleep 10; reboot)&"); | ||||
| system("/etc/init.d/ucentral stop"); | ||||
|  | ||||
| let err = ctx.error(); | ||||
|  | ||||
| if (err != null) | ||||
| 	result(2, "Reboot call failed with status %s", err); | ||||
| @@ -1,91 +0,0 @@ | ||||
| let image_path = "/tmp/ucentral.upgrade"; | ||||
|  | ||||
| if (!args.uri) { | ||||
| 	result(2, "No firmware URL provided"); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| let download_cmdline = [ 'wget', '-O', image_path, args.uri ]; | ||||
| let rc = system(download_cmdline); | ||||
|  | ||||
| if (rc != 0) { | ||||
| 	result(2, "Download command %s exited with non-zero code %d", download_cmdline, rc); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| let validation_result = ctx.call("system", "validate_firmware_image", { path: image_path }); | ||||
|  | ||||
| if (!validation_result) { | ||||
| 	result(2, "Validation call failed with status %s", ubus.error()); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
| else if (!validation_result.valid) { | ||||
| 	result_json({ | ||||
| 		"error": 2, | ||||
| 		"text": "Firmware image validation failed", | ||||
| 		"data": sprintf("Archive command %s exited with non-zero code %d", archive_cmdline, rc) | ||||
| 	}); | ||||
|  | ||||
| 	warn(sprintf("ucentral-upgrade: firmware file validation failed: %J\n", validation_result)); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| if (restrict.sysupgrade) { | ||||
| 	let signature = require('signature'); | ||||
| 	if (!signature.verify(image_path, args.signature)) { | ||||
| 		result_json({ | ||||
| 			"error": 2, | ||||
| 			"text": "Invalid signature", | ||||
| 			"resultCode": -1 | ||||
| 		}); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| let archive_cmdline = [ | ||||
| 	'tar', 'czf', '/upgrade.tgz', | ||||
| 	'/etc/config/ucentral' | ||||
| ]; | ||||
|  | ||||
| let files = [ | ||||
| 		"/etc/ucentral/cas.pem", "/etc/ucentral/cert.pem", | ||||
| 		"/etc/ucentral/redirector.json", "/etc/ucentral/dev-id", | ||||
| 		"/etc/ucentral/key.pem", "/etc/ucentral/gateway.json", | ||||
| 		"/etc/ucentral/profile.json", "/etc/ucentral/restrictions.json", | ||||
| ]; | ||||
| for (let f in files) | ||||
| 	if (fs.stat(f)) | ||||
| 		push(archive_cmdline, f); | ||||
|  | ||||
| if (args.keep_redirector) { | ||||
| 	let active_config = fs.readlink("/etc/ucentral/ucentral.active"); | ||||
|  | ||||
| 	if (active_config) | ||||
| 		push(archive_cmdline, '/etc/ucentral/ucentral.active', active_config); | ||||
| 	else | ||||
| 		result(2, "Unable to determine active configuration: %s", fs.error()); | ||||
| } | ||||
|  | ||||
| let rc = system(archive_cmdline); | ||||
|  | ||||
| if (rc != 0) { | ||||
| 	result(2, "Archive command %s exited with non-zero code %d", archive_cmdline, rc); | ||||
|  | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| include('reboot_cause.uc', { reason: 'upgrade' }); | ||||
|  | ||||
| let sysupgrade_cmdline = sprintf("sysupgrade %s %s", | ||||
| 				 args.keep_redirector ? "-f /upgrade.tgz" : "-n", | ||||
| 				 image_path); | ||||
|  | ||||
| warn("Upgrading firmware\n"); | ||||
|  | ||||
| system("touch /ucentral.upgrade"); | ||||
| system("(sleep 10; /etc/init.d/network stop; " + sysupgrade_cmdline + ")&"); | ||||
| system("/etc/init.d/ucentral stop"); | ||||
| @@ -1,285 +0,0 @@ | ||||
| let verbose = args?.verbose == null ? true : args.verbose; | ||||
| let active = args?.active ? true : false; | ||||
| let bandwidth = args?.bandwidth || 0; | ||||
| let override_dfs = args?.override_dfs ? true : false; | ||||
| let nl = require("nl80211"); | ||||
| let rtnl = require("rtnl"); | ||||
| let def = nl.const; | ||||
|  | ||||
| if (!ctx) { | ||||
|         ubus = require("ubus"); | ||||
|         ctx = ubus.connect(); | ||||
| } | ||||
|  | ||||
| const SCAN_FLAG_AP = (1<<2); | ||||
| const frequency_list_2g = [ 2412, 2417, 2422, 2427, 2432, 2437, 2442, | ||||
| 			  2447, 2452, 2457, 2462, 2467, 2472, 2484 ]; | ||||
| const frequency_list_5g = { '3': [ 5180, 5260, 5500, 5580, 5660, 5745 ], | ||||
| 			  '2': [ 5180, 5220, 5260, 5300, 5500, 5540, | ||||
| 				 5580, 5620, 5660, 5745, 5785, 5825, | ||||
| 				 5865, 5920, 5960 ], | ||||
| 			  '1': [ 5180, 5200, 5220, 5240, 5260, 5280, | ||||
| 				 5300, 5320, 5500, 5520, 5540, 5560, | ||||
| 				 5580, 5600, 5620, 5640, 5660, 5680, | ||||
| 				 5700, 5720, 5745, 5765, 5785, 5805, | ||||
| 				 5825, 5845, 5865, 5885 ], | ||||
| }; | ||||
| const frequency_offset = { '80': 30, '40': 10 }; | ||||
| const frequency_width = { '80': 3, '40': 2, '20': 1 }; | ||||
| const IFTYPE_STATION = 2; | ||||
| const IFTYPE_AP = 3; | ||||
| const IFTYPE_MESH = 7; | ||||
| const IFF_UP = 1; | ||||
|  | ||||
| function frequency_to_channel(freq) { | ||||
| 	/* see 802.11-2007 17.3.8.3.2 and Annex J */ | ||||
| 	if (freq == 2484) | ||||
| 		return 14; | ||||
| 	else if (freq < 2484) | ||||
| 		return (freq - 2407) / 5; | ||||
| 	else if (freq >= 4910 && freq <= 4980) | ||||
| 		return (freq - 4000) / 5; | ||||
| 	else if (freq < 5935) /* DMG band lower limit */ | ||||
| 		return (freq - 5000) / 5; | ||||
| 	else if (freq == 5935) | ||||
| 		return 2; | ||||
| 	else if (freq >= 5955 && freq <= 7115) | ||||
| 		return ((freq - 5955) / 5) + 1; | ||||
| 	else if (freq >= 58320 && freq <= 64800) | ||||
| 		return (freq - 56160) / 2160; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| function iface_get(wdev) { | ||||
| 	let params = { dev: wdev }; | ||||
| 	let res = nl.request(def.NL80211_CMD_GET_INTERFACE, wdev ? null : def.NLM_F_DUMP, wdev ? params : null); | ||||
|  | ||||
| 	if (res === false) | ||||
| 		warn("Unable to lookup interface: " + nl.error() + "\n"); | ||||
| 	return res || []; | ||||
| } | ||||
|  | ||||
| function iface_find(wiphy, types, ifaces) { | ||||
| 	if (!ifaces) | ||||
| 		ifaces = iface_get(); | ||||
| 	for (let iface in ifaces) { | ||||
| 		if (iface.wiphy != wiphy) | ||||
| 			continue; | ||||
| 		if (iface.iftype in types) | ||||
| 			return iface; | ||||
| 	} | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| function scan_trigger(wdev, frequency, width) { | ||||
| 	let params = { dev: wdev, scan_flags: SCAN_FLAG_AP }; | ||||
|  | ||||
| 	if (frequency && type(frequency) == 'array') { | ||||
| 		params.scan_frequencies = frequency; | ||||
| 	} | ||||
| 	else if (frequency && width) { | ||||
| 		params.wiphy_freq = frequency; | ||||
| 		params.center_freq1 = frequency + frequency_offset[width]; | ||||
| 		params.channel_width = frequency_width[width]; | ||||
| 	} | ||||
|  | ||||
| 	if (active) | ||||
| 		params.scan_ssids = [ '' ]; | ||||
|  | ||||
| 	//printf("%.J\n", params); | ||||
| 	let res = nl.request(def.NL80211_CMD_TRIGGER_SCAN, 0, params); | ||||
|  | ||||
| 	if (res === false) | ||||
| 		die("Unable to trigger scan: " + nl.error() + "\n"); | ||||
|  | ||||
| 	else | ||||
| 		res = nl.waitfor([ | ||||
| 			def.NL80211_CMD_NEW_SCAN_RESULTS, | ||||
| 			def.NL80211_CMD_SCAN_ABORTED | ||||
| 		], (frequency && width) ? 500 : 5000); | ||||
|  | ||||
| 	if (!res) | ||||
| 		warn("Netlink error while awaiting scan results: " + nl.error() + "\n"); | ||||
|  | ||||
| 	else if (res.cmd == def.NL80211_CMD_SCAN_ABORTED) | ||||
| 		warn("Scan aborted by kernel\n"); | ||||
| } | ||||
|  | ||||
| function trigger_scan_width(wdev, freqs, width) { | ||||
| 	for (let freq in freqs) | ||||
| 		scan_trigger(wdev, freq, width); | ||||
| } | ||||
|  | ||||
| function phy_get(wdev) { | ||||
| 	let res = nl.request(def.NL80211_CMD_GET_WIPHY, def.NLM_F_DUMP, { split_wiphy_dump: true }); | ||||
|  | ||||
| 	if (res === false) | ||||
| 		warn("Unable to lookup phys: " + nl.error() + "\n"); | ||||
|  | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| function phy_get_frequencies(phy) { | ||||
| 	let freqs = []; | ||||
|  | ||||
| 	for (let band in phy.wiphy_bands) { | ||||
| 		for (let freq in band?.freqs || []) | ||||
| 			if (!freq.disabled) | ||||
| 				push(freqs, freq.freq); | ||||
| 	} | ||||
| 	return freqs; | ||||
| } | ||||
|  | ||||
| function phy_frequency_dfs(phy, curr) { | ||||
| 	let freqs = []; | ||||
|  | ||||
| 	for (let band in phy.wiphy_bands) { | ||||
| 		for (let freq in band?.freqs || []) | ||||
| 			if (freq.freq == curr && freq.dfs_state >= 0) | ||||
| 				return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| let phys = phy_get(); | ||||
| let ifaces = iface_get(); | ||||
|  | ||||
| function intersect(list, filter) { | ||||
| 	let res = []; | ||||
|  | ||||
| 	for (let item in list) | ||||
| 		if (index(filter, item) >= 0) | ||||
| 			push(res, item); | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| function wifi_scan() { | ||||
| 	let scan = []; | ||||
|  | ||||
| 	for (let phy in phys) { | ||||
| 		let iface = iface_find(phy.wiphy, [ IFTYPE_STATION, IFTYPE_AP ], ifaces); | ||||
| 		let scan_iface = false; | ||||
| 		if (!iface) { | ||||
| 			warn('no valid interface found for phy' + phy.wiphy + '\n'); | ||||
| 			nl.request(def.NL80211_CMD_NEW_INTERFACE, 0, { wiphy: phy.wiphy, ifname: 'scan', iftype: IFTYPE_STATION }); | ||||
| 			nl.waitfor([ def.NL80211_CMD_NEW_INTERFACE ], 1000); | ||||
| 			scan_iface = true; | ||||
| 			iface = { | ||||
| 				dev: 'scan', | ||||
| 				channel_width: 1, | ||||
| 			}; | ||||
| 			rtnl.request(rtnl.const.RTM_NEWLINK, 0, { dev: 'scan', flags: IFF_UP, change: 1}); | ||||
| 			sleep(1000); | ||||
| 		} | ||||
|  | ||||
| 		printf("scanning on phy%d\n", phy.wiphy); | ||||
|  | ||||
| 		let freqs = phy_get_frequencies(phy); | ||||
| 		if (length(intersect(freqs, frequency_list_2g))) | ||||
| 			scan_trigger(iface.dev, frequency_list_2g); | ||||
|  | ||||
| 		let ch_width = iface.channel_width; | ||||
| 		if (frequency_width[bandwith]) | ||||
| 			ch_width = frequency_width[bandwith]; | ||||
| 		let freqs_5g = intersect(freqs, frequency_list_5g[ch_width]); | ||||
| 		if (length(freqs_5g)) { | ||||
| 			if (override_dfs && !scan_iface && phy_frequency_dfs(phy, iface.wiphy_freq)) { | ||||
| 				ctx.call(sprintf('hostapd.%s', iface.dev), 'switch_chan', { freq: 5180, bcn_count: 10 }); | ||||
| 				sleep(2000) | ||||
| 			} | ||||
| 			trigger_scan_width(iface.dev, freqs_5g, ch_width); | ||||
| 		} | ||||
| 		let res = nl.request(def.NL80211_CMD_GET_SCAN, def.NLM_F_DUMP, { dev: iface.dev }); | ||||
| 		for (let bss in res) { | ||||
| 			bss = bss.bss; | ||||
| 			let res = { | ||||
| 				bssid: bss.bssid, | ||||
| 				frequency: +bss.frequency, | ||||
| 				channel: frequency_to_channel(+bss.frequency), | ||||
| 				signal: +bss.signal_mbm / 100, | ||||
| 			}; | ||||
| 			if (verbose) { | ||||
| 				res.tsf = +bss.tsf; | ||||
| 				res.last_seen = +bss.seen_ms_ago; | ||||
| 				res.capability = +bss.capability; | ||||
| 				res.ies = []; | ||||
| 			} | ||||
|  | ||||
|  | ||||
| 			for (let ie in bss.beacon_ies) { | ||||
| 				switch (ie.type) { | ||||
| 				case 0: | ||||
| 					res.ssid = ie.data; | ||||
| 					break; | ||||
| 				case 114: | ||||
| 					if (verbose) | ||||
| 						res.meshid = ie.data; | ||||
| 					break; | ||||
| 				case 0x3d: | ||||
| 					if (verbose) | ||||
| 						res.ht_oper = b64enc(ie.data); | ||||
| 					break; | ||||
| 				case 0xc0: | ||||
| 					if (verbose) | ||||
| 						res.vht_oper = b64enc(ie.data); | ||||
| 					break; | ||||
| 				case 0xdd: | ||||
| 					let oui = hexenc(substr(ie.data, 0, 3)); | ||||
| 					let type = ord(ie.data, 3); | ||||
| 					let data = substr(ie.data, 4); | ||||
| 					switch (oui) { | ||||
| 					case '48d017': | ||||
| 						res.tip_oui = true; | ||||
| 						switch(type) { | ||||
| 						case 1: | ||||
| 							if (data) | ||||
| 								res.tip_name = data; | ||||
| 							break; | ||||
| 						case 2: | ||||
| 							if (data) | ||||
| 								res.tip_serial = data; | ||||
| 							break; | ||||
| 						case 3: | ||||
| 							if (data) | ||||
| 								res.tip_network_id = data; | ||||
| 							break; | ||||
| 						} | ||||
| 						break; | ||||
| 					} | ||||
| 					break; | ||||
| 				default: | ||||
| 					if (verbose) | ||||
| 						push(res.ies, { type: ie.type, data: b64enc(ie.data) }); | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (args.periodic && !args.information_elements) | ||||
| 				delete res.ies; | ||||
| 			push(scan, res); | ||||
| 		} | ||||
| 		if (scan_iface) { | ||||
| 			warn('removing temporary interface\n'); | ||||
| 			nl.request(def.NL80211_CMD_DEL_INTERFACE, 0, { dev: 'scan' }); | ||||
| 		} | ||||
| 	} | ||||
| 	printf("%.J\n", scan); | ||||
| 	return scan; | ||||
| } | ||||
|  | ||||
| let scan = wifi_scan(); | ||||
|  | ||||
| if (args.periodic) { | ||||
| 	ctx.call('ucentral', 'send', { | ||||
| 		method: 'wifiscan', | ||||
| 		params: { data: scan } | ||||
| 	}); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| result_json({ | ||||
| 	error: 0, | ||||
| 	text: "Success", | ||||
| 	resultCode: 1, | ||||
| 	scan, | ||||
| }); | ||||
							
								
								
									
										181
									
								
								docs/schema_doc.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								docs/schema_doc.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| body { | ||||
|   font: 16px/1.5em "Overpass", "Open Sans", Helvetica, sans-serif; | ||||
|   color: #333; | ||||
|   font-weight: 300; | ||||
|   padding: 40px; | ||||
| } | ||||
|  | ||||
| .btn.btn-link { | ||||
|     font-size: 18px; | ||||
|     user-select: text; | ||||
| } | ||||
|  | ||||
| .jsfh-animated-property { | ||||
|     animation: eclair; | ||||
|     animation-iteration-count: 1; | ||||
|     animation-fill-mode: forwards; | ||||
|     animation-duration: .75s; | ||||
|  | ||||
| } | ||||
|  | ||||
| @keyframes eclair { | ||||
|     0%,100% { | ||||
|         transform: scale(1); | ||||
|     } | ||||
|     50% { | ||||
|         transform: scale(1.03); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .btn.btn-primary { | ||||
|     margin: 10px; | ||||
| } | ||||
|  | ||||
| .btn.example-show.collapsed:before { | ||||
|     content: "show" | ||||
| } | ||||
|  | ||||
| .btn.example-show:before { | ||||
|     content: "hide" | ||||
| } | ||||
|  | ||||
| .description.collapse:not(.show) { | ||||
|     max-height: 100px !important; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     display: -webkit-box; | ||||
|     -webkit-line-clamp: 2; | ||||
|     -webkit-box-orient: vertical; | ||||
| } | ||||
|  | ||||
| .description.collapsing { | ||||
|     min-height: 100px !important; | ||||
| } | ||||
|  | ||||
| .collapse-description-link.collapsed:after  { | ||||
|     content: '+ Read More'; | ||||
| } | ||||
|  | ||||
| .collapse-description-link:not(.collapsed):after { | ||||
|     content: '- Read Less'; | ||||
| } | ||||
|  | ||||
| .badge { | ||||
|     font-size: 100%; | ||||
| 	margin-bottom: 0.5rem; | ||||
|     margin-top: 0.5rem; | ||||
| } | ||||
|  | ||||
| .badge.value-type { | ||||
|     font-size: 120%; | ||||
|     margin-right: 5px; | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
|  | ||||
| .badge.default-value { | ||||
|     font-size: 120%; | ||||
|     margin-left: 5px; | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
| .badge.restriction { | ||||
|     display: inline-block; | ||||
| } | ||||
|  | ||||
| .badge.required-property,.badge.deprecated-property,.badge.pattern-property,.badge.no-additional { | ||||
|     font-size: 100%; | ||||
|     margin-left: 10px; | ||||
| } | ||||
|  | ||||
| .accordion div.card:only-child { | ||||
|     border-bottom: 1px solid rgba(0, 0, 0, 0.125); | ||||
| } | ||||
|  | ||||
| .examples { | ||||
| 	padding: 1rem !important; | ||||
| } | ||||
|  | ||||
| .examples pre { | ||||
|     margin-bottom: 0; | ||||
| } | ||||
|  | ||||
| .highlight.jumbotron { | ||||
|     padding: 1rem !important; | ||||
| } | ||||
|  | ||||
| .generated-by-footer { | ||||
|     margin-top: 1em; | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| /* From https://github.com/richleland/pygments-css/blob/master/friendly.css, see https://github.com/trentm/python-markdown2/wiki/fenced-code-blocks */ | ||||
| .highlight  { background: #e9ecef; } /* Changed from #f0f0f0 in the original style to be the same as bootstrap's jumbotron */ | ||||
| .highlight .hll { background-color: #ffffcc } | ||||
| .highlight .c { color: #60a0b0; font-style: italic } /* Comment */ | ||||
| .highlight .err { border: 1px solid #FF0000 } /* Error */ | ||||
| .highlight .k { color: #007020; font-weight: bold } /* Keyword */ | ||||
| .highlight .o { color: #666666 } /* Operator */ | ||||
| .highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */ | ||||
| .highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ | ||||
| .highlight .cp { color: #007020 } /* Comment.Preproc */ | ||||
| .highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */ | ||||
| .highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ | ||||
| .highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ | ||||
| .highlight .gd { color: #A00000 } /* Generic.Deleted */ | ||||
| .highlight .ge { font-style: italic } /* Generic.Emph */ | ||||
| .highlight .gr { color: #FF0000 } /* Generic.Error */ | ||||
| .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ | ||||
| .highlight .gi { color: #00A000 } /* Generic.Inserted */ | ||||
| .highlight .go { color: #888888 } /* Generic.Output */ | ||||
| .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ | ||||
| .highlight .gs { font-weight: bold } /* Generic.Strong */ | ||||
| .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ | ||||
| .highlight .gt { color: #0044DD } /* Generic.Traceback */ | ||||
| .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ | ||||
| .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ | ||||
| .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ | ||||
| .highlight .kp { color: #007020 } /* Keyword.Pseudo */ | ||||
| .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ | ||||
| .highlight .kt { color: #902000 } /* Keyword.Type */ | ||||
| .highlight .m { color: #40a070 } /* Literal.Number */ | ||||
| .highlight .s { color: #4070a0 } /* Literal.String */ | ||||
| .highlight .na { color: #4070a0 } /* Name.Attribute */ | ||||
| .highlight .nb { color: #007020 } /* Name.Builtin */ | ||||
| .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ | ||||
| .highlight .no { color: #60add5 } /* Name.Constant */ | ||||
| .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ | ||||
| .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ | ||||
| .highlight .ne { color: #007020 } /* Name.Exception */ | ||||
| .highlight .nf { color: #06287e } /* Name.Function */ | ||||
| .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ | ||||
| .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ | ||||
| .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ | ||||
| .highlight .nv { color: #bb60d5 } /* Name.Variable */ | ||||
| .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ | ||||
| .highlight .w { color: #bbbbbb } /* Text.Whitespace */ | ||||
| .highlight .mb { color: #40a070 } /* Literal.Number.Bin */ | ||||
| .highlight .mf { color: #40a070 } /* Literal.Number.Float */ | ||||
| .highlight .mh { color: #40a070 } /* Literal.Number.Hex */ | ||||
| .highlight .mi { color: #40a070 } /* Literal.Number.Integer */ | ||||
| .highlight .mo { color: #40a070 } /* Literal.Number.Oct */ | ||||
| .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ | ||||
| .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ | ||||
| .highlight .sc { color: #4070a0 } /* Literal.String.Char */ | ||||
| .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ | ||||
| .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ | ||||
| .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ | ||||
| .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ | ||||
| .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ | ||||
| .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ | ||||
| .highlight .sx { color: #c65d09 } /* Literal.String.Other */ | ||||
| .highlight .sr { color: #235388 } /* Literal.String.Regex */ | ||||
| .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ | ||||
| .highlight .ss { color: #517918 } /* Literal.String.Symbol */ | ||||
| .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ | ||||
| .highlight .fm { color: #06287e } /* Name.Function.Magic */ | ||||
| .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ | ||||
| .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ | ||||
| .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ | ||||
| .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ | ||||
| .highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ | ||||
							
								
								
									
										1
									
								
								docs/schema_doc.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs/schema_doc.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| $(document).on("click",'a[href^="#"]',function(event){event.preventDefault();history.pushState({},"",this.href)});function flashElement(elementId){myElement=document.getElementById(elementId);myElement.classList.add("jsfh-animated-property");setTimeout(function(){myElement.classList.remove("jsfh-animated-property")},1e3)}function setAnchor(anchorLinkDestination){history.pushState({},"",anchorLinkDestination)}function anchorOnLoad(){let linkTarget=decodeURIComponent(window.location.hash.split("?")[0].split("&")[0]);if(linkTarget[0]==="#"){linkTarget=linkTarget.substr(1)}if(linkTarget.length>0){anchorLink(linkTarget)}}function anchorLink(linkTarget){const target=$("#"+linkTarget);target.parents().addBack().filter(".collapse:not(.show), .tab-pane, [role='tab']").each(function(index){if($(this).hasClass("collapse")){$(this).collapse("show")}else if($(this).hasClass("tab-pane")){const tabToShow=$("a[href='#"+$(this).attr("id")+"']");if(tabToShow){tabToShow.tab("show")}}else if($(this).attr("role")==="tab"){$(this).tab("show")}});setTimeout(function(){let targetElement=document.getElementById(linkTarget);if(targetElement){targetElement.scrollIntoView({block:"center",behavior:"smooth"});setTimeout(function(){flashElement(linkTarget)},500)}},1e3)} | ||||
							
								
								
									
										117
									
								
								docs/ucentral-schema.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								docs/ucentral-schema.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										30
									
								
								docs/ucentral-state.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docs/ucentral-state.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										25
									
								
								docs/ucentral.capabilities.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								docs/ucentral.capabilities.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -7,9 +7,11 @@ set -x | ||||
| ./merge-schema.py schema schema ucentral.yml ucentral.schema.pretty.json 0 1 | ||||
| ./merge-schema.py schema schema ucentral.yml ucentral.schema.full.json 0 0 | ||||
| ./merge-schema.py state state state.yml ucentral.state.pretty.json 0 1 | ||||
| ./generate-reader.uc  > schemareader.uc | ||||
| ./merge-schema.py capabilities capabilities connect.capabilities.yml ucentral.capabilities.pretty.json 0 1  | ||||
| #./generate-reader.uc  > schemareader.uc | ||||
| #./generate-example.uc > input.json | ||||
| mkdir -p docs | ||||
| which generate-schema-doc > /dev/null | ||||
| generate-schema-doc --config expand_buttons=true ucentral.schema.pretty.json docs/ucentral-schema.html | ||||
| generate-schema-doc --config expand_buttons=true ucentral.state.pretty.json docs/ucentral-state.html | ||||
| generate-schema-doc --config expand_buttons=true ucentral.capabilities.pretty.json docs/ucentral.capabilities.html | ||||
|  | ||||
|   | ||||
| @@ -1,650 +0,0 @@ | ||||
| /* | ||||
|  * ucode-transpiler.js - JSDoc plugin to naively transpile ucode into JS. | ||||
|  * | ||||
|  * Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io> | ||||
|  * | ||||
|  * Permission to use, copy, modify, and/or distribute this software for any | ||||
|  * purpose with or without fee is hereby granted, provided that the above | ||||
|  * copyright notice and this permission notice appear in all copies. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||
|  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||
|  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||
|  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||
|  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||
|  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||
|  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| function skipString(s) { | ||||
|   let q = s.charAt(0); | ||||
|   let esc = false; | ||||
|  | ||||
|   for (let i = 1; i < s.length; i++) { | ||||
|     let c = s.charAt(i); | ||||
|  | ||||
|     if (esc) { | ||||
|       esc = false; | ||||
|       continue; | ||||
|     } | ||||
|     else if (c == '\\') { | ||||
|       esc = true; | ||||
|       continue; | ||||
|     } | ||||
|     else if (c == q) { | ||||
|       // consume regex literal flags | ||||
|       while (q == '/' && s.charAt(i + 1).match(/[gis]/)) | ||||
|         i++; | ||||
|  | ||||
|       return [ s.substring(0, i + 1), s.substring(i + 1) ]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   throw 'Unterminated string literal'; | ||||
| } | ||||
|  | ||||
| function skipComment(s) { | ||||
|   let q = s.charAt(1), | ||||
|       end = (q == '/') ? '\n' : '*/', | ||||
|       esc = false; | ||||
|  | ||||
|   for (let i = 2; i < s.length; i++) { | ||||
|     let c = s.charAt(i); | ||||
|  | ||||
|     if (esc) { | ||||
|       esc = false; | ||||
|       continue; | ||||
|     } | ||||
|     else if (c == '\\') { | ||||
|       esc = true; | ||||
|       continue; | ||||
|     } | ||||
|     else if (s.substring(i, i + end.length) == end) { | ||||
|       return [ s.substring(0, i + end.length), s.substring(i + end.length) ]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (q == '*') | ||||
|     throw 'Unterminated multiline comment'; | ||||
|  | ||||
|   return [ s, '' ]; | ||||
| } | ||||
|  | ||||
| function escapeString(s) { | ||||
|   return "'" + s.replace(/[\\\n']/g, '\\$&') + "';"; | ||||
| } | ||||
|  | ||||
| const keywords = [ | ||||
|   'break', | ||||
|   'case', | ||||
|   'catch', | ||||
|   'const', | ||||
|   'continue', | ||||
|   'default', | ||||
|   'delete', | ||||
|   'elif', | ||||
|   'else', | ||||
|   'endfor', | ||||
|   'endfunction', | ||||
|   'endif', | ||||
|   'endwhile', | ||||
|   'false', | ||||
|   'for', | ||||
|   'function', | ||||
|   'if', | ||||
|   'in', | ||||
|   'let', | ||||
|   'null', | ||||
|   'return', | ||||
|   'switch', | ||||
|   'this', | ||||
|   'true', | ||||
|   'try', | ||||
|   'while' | ||||
| ]; | ||||
|  | ||||
| const reserved = [ | ||||
|   'await', | ||||
|   'class', | ||||
|   'debugger', | ||||
|   'enum', | ||||
|   'export', | ||||
|   'extends', | ||||
|   'finally', | ||||
|   'implements', | ||||
|   'import', | ||||
|   'instanceof', | ||||
|   'interface', | ||||
|   'new', | ||||
|   'package', | ||||
|   'private', | ||||
|   'protected', | ||||
|   'public', | ||||
|   'super', | ||||
|   'throw', | ||||
|   'typeof', | ||||
|   'var', | ||||
|   'void', | ||||
|   'with', | ||||
|   'yield' | ||||
| ]; | ||||
|  | ||||
| function Transpiler(s, raw) { | ||||
|   this.source = s; | ||||
|   this.offset = 0; | ||||
|   this.tokens = []; | ||||
|  | ||||
|   if (raw) { | ||||
|     this.state = 'identify_token'; | ||||
|     this.block = 'block_statement'; | ||||
|   } | ||||
|   else { | ||||
|     this.state = 'identify_block'; | ||||
|   } | ||||
|  | ||||
|   let token = null; | ||||
|  | ||||
|   do { | ||||
|     token = this.parse(); | ||||
|  | ||||
|     switch (token.type) { | ||||
|     case '-}}': | ||||
|     case '-%}': | ||||
|     case '}}': | ||||
|     case '%}': | ||||
|       if (raw) | ||||
|         throw 'Unexpected token "' + token.type + '"'; | ||||
|  | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     this.tokens.push(token); | ||||
|   } | ||||
|   while (token.type != 'eof'); | ||||
| } | ||||
|  | ||||
| Transpiler.prototype = { | ||||
|   parse: function() { | ||||
|     let m; | ||||
|  | ||||
|     switch (this.state) { | ||||
|     case 'identify_block': | ||||
|       m = this.source.match(/^((?:.|\n)*?)((\{[{%#])(?:.|\n)*)$/); | ||||
|  | ||||
|       if (m) { | ||||
|         switch (m[3]) { | ||||
|         case '{#': | ||||
|           this.state = 'block_comment'; | ||||
|           break; | ||||
|  | ||||
|         case '{{': | ||||
|           this.state = 'block_expression'; | ||||
|           break; | ||||
|  | ||||
|         case '{%': | ||||
|           this.state = 'block_statement'; | ||||
|           break; | ||||
|         } | ||||
|  | ||||
|         this.source = m[2]; | ||||
|  | ||||
|         return { type: 'text', value: escapeString(m[1]), prefix: '' }; | ||||
|       } | ||||
|       else if (this.source.length) { | ||||
|         let t = { type: 'text', value: escapeString(this.source), prefix: '' }; | ||||
|  | ||||
|         this.source = ''; | ||||
|  | ||||
|         return t; | ||||
|       } | ||||
|       else { | ||||
|         return { type: 'eof', value: '', prefix: '' }; | ||||
|       } | ||||
|  | ||||
|       break; | ||||
|  | ||||
|     case 'block_comment': | ||||
|       m = this.source.match(/^((?:.|\n)*?#\})((?:.|\n)*)$/); | ||||
|  | ||||
|       if (!m) | ||||
|         throw 'Unterminated comment block'; | ||||
|  | ||||
|       this.source = m[2]; | ||||
|       this.state = 'identify_block'; | ||||
|       this.block = null; | ||||
|  | ||||
|       return { | ||||
|         type: 'comment', | ||||
|         value: m[1].replace(/\*\}/g, '*\\}').replace(/^\{##/, '/**').replace(/^\{#/, '/*').replace(/#\}$/, '*/'), | ||||
|         prefix: '' | ||||
|       }; | ||||
|  | ||||
|     case 'block_expression': | ||||
|       this.state = 'identify_token'; | ||||
|       this.block = 'expression'; | ||||
|       this.source = this.source.replace(/^\{\{[+-]?/, ''); | ||||
|  | ||||
|       return this.parse(); | ||||
|  | ||||
|     case 'block_statement': | ||||
|       this.state = 'identify_token'; | ||||
|       this.block = 'statement'; | ||||
|       this.source = this.source.replace(/^\{%[+-]?/, ''); | ||||
|  | ||||
|       return this.parse(); | ||||
|  | ||||
|     case 'identify_token': | ||||
|       let t = this.parsetoken(); | ||||
|  | ||||
|       if ((this.block == 'expression' && (t.type == '-}}' || t.type == '}}')) || | ||||
|           (this.block == 'statement' && (t.type == '-%}' || t.type == '%}'))) { | ||||
|         this.state = 'identify_block'; | ||||
|         this.block = null; | ||||
|  | ||||
|         return { | ||||
|           type: ';', | ||||
|           value: ';', | ||||
|           prefix: t.prefix | ||||
|         }; | ||||
|       } | ||||
|  | ||||
|       if (this.block == 'expression' && t.type == 'eof') | ||||
|         throw 'Unterminated expression block'; | ||||
|  | ||||
|       return t; | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   parsetoken: function() { | ||||
|     let token = this.source.match(/^((?:\s|\n)*)(-[%}]\}|<<=|>>=|===|!==|\.\.\.|\?\.\[|\?\.\(|[%}]\}|\/\*|\/\/|&&|[+&|^\/%*-=!<>]=|--|\+\+|<<|>>|\|\||=>|\?\.|[+=&|[\]\^{}:,~\/>!<%*()?;.'"-]|(\d+(?:\.\d+)?)|(\w+))((?:.|\n)*)$/); | ||||
|     let rv, r, t; | ||||
|  | ||||
|     if (token) { | ||||
|       switch (token[2]) { | ||||
|       case '"': | ||||
|       case "'": | ||||
|         r = skipString(token[2] + token[5]); | ||||
|         rv = r[0]; | ||||
|         t = 'string'; | ||||
|         this.source = r[1]; | ||||
|         break; | ||||
|  | ||||
|       case '//': | ||||
|       case '/*': | ||||
|         r = skipComment(token[2] + token[5]); | ||||
|         rv = r[0]; | ||||
|         t = 'comment'; | ||||
|         this.source = r[1]; | ||||
|         break; | ||||
|  | ||||
|       case '/': | ||||
|       case '/=': | ||||
|         if (this.lastToken.match(/[(,=:[!&|?{};]/)) { | ||||
|           r = skipString(token[2] + token[5]); | ||||
|           rv = r[0]; | ||||
|           t = 'regexp'; | ||||
|           this.source = r[1]; | ||||
|         } | ||||
|         else { | ||||
|           rv = token[2]; | ||||
|           t = token[2]; | ||||
|           this.source = token[5]; | ||||
|         } | ||||
|  | ||||
|         break; | ||||
|  | ||||
|       default: | ||||
|         this.source = token[5]; | ||||
|  | ||||
|         if (token[3]) { | ||||
|           rv = token[3]; | ||||
|  | ||||
|           if (token[3].indexOf('.') != -1) | ||||
|             t = 'double'; | ||||
|           else | ||||
|             t = 'number'; | ||||
|         } | ||||
|         else if (token[4]) { | ||||
|           rv = token[4]; | ||||
|  | ||||
|           if (keywords.indexOf(token[4]) != -1) { | ||||
|             t = token[4]; | ||||
|           } | ||||
|           else { | ||||
|             t = 'label'; | ||||
|  | ||||
|             if (reserved.indexOf(token[4]) != -1) | ||||
|               rv += '_'; | ||||
|           } | ||||
|         } | ||||
|         else { | ||||
|           rv = token[2]; | ||||
|           t = token[2]; | ||||
|         } | ||||
|  | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       this.lastToken = token[2]; | ||||
|  | ||||
|       return { | ||||
|         type: t, | ||||
|         value: rv, | ||||
|         prefix: token[1] | ||||
|       }; | ||||
|     } | ||||
|     else if (this.source.match(/^\s*$/)) { | ||||
|       rv = this.source; | ||||
|       this.source = ''; | ||||
|  | ||||
|       return { | ||||
|         type: 'eof', | ||||
|         value: '', | ||||
|         prefix: rv | ||||
|       }; | ||||
|     } | ||||
|     else { | ||||
|       throw 'Unrecognized character near [...' + this.source + ']'; | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   next: function() { | ||||
|     let idx = this.offset++; | ||||
|  | ||||
|     return this.tokens[Math.min(idx, this.tokens.length - 1)]; | ||||
|   }, | ||||
|  | ||||
|   skip_statement: function(tokens, ends) { | ||||
|     let nest = 0; | ||||
|  | ||||
|     while (true) { | ||||
|       let token = this.next(); | ||||
|  | ||||
|       if (token.type == 'eof') { | ||||
|         this.offset--; | ||||
|  | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       if (nest == 0 && ends.indexOf(token.type) != -1) { | ||||
|         this.offset--; | ||||
|  | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       switch (token.type) { | ||||
|       case '(': | ||||
|       case '[': | ||||
|       case '{': | ||||
|         nest++; | ||||
|         break; | ||||
|  | ||||
|       case ')': | ||||
|       case ']': | ||||
|       case '}': | ||||
|         nest--; | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       tokens.push(token); | ||||
|  | ||||
|       if (token.type == ';') | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     return tokens; | ||||
|   }, | ||||
|  | ||||
|   skip_paren: function(tokens) { | ||||
|     let token = this.next(); | ||||
|     let depth = 0; | ||||
|  | ||||
|     if (token.type != '(') | ||||
|       throw 'Unexpected token, expected "(", got "' + token.type + '"'; | ||||
|  | ||||
|     do { | ||||
|       tokens.push(token); | ||||
|  | ||||
|       switch (token.type) { | ||||
|       case '(': | ||||
|         depth++; | ||||
|         break; | ||||
|  | ||||
|       case ')': | ||||
|         depth--; | ||||
|  | ||||
|         if (depth == 0) | ||||
|           return token; | ||||
|  | ||||
|         break; | ||||
|  | ||||
|       case 'eof': | ||||
|         throw 'Unexpected EOF'; | ||||
|       } | ||||
|  | ||||
|       token = this.next(); | ||||
|     } | ||||
|     while (depth != 0); | ||||
|   }, | ||||
|  | ||||
|   assert_token: function(tokens, type) { | ||||
|     let token = this.next(); | ||||
|  | ||||
|     if (token.type != type) | ||||
|       throw 'Unexpected token, expected "' + type + '", got "' + token.type + '"'; | ||||
|  | ||||
|     tokens.push(token); | ||||
|  | ||||
|     return tokens; | ||||
|   }, | ||||
|  | ||||
|   check_token: function(tokens, type) { | ||||
|     let token = this.next(); | ||||
|  | ||||
|     if (token.type != type) { | ||||
|       this.offset--; | ||||
|  | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     tokens.push(token); | ||||
|  | ||||
|     return true; | ||||
|   }, | ||||
|  | ||||
|   patch: function(tokens, type, value) { | ||||
|     tokens[tokens.length - 1].type = type; | ||||
|     tokens[tokens.length - 1].value = (value != null) ? value : type; | ||||
|   }, | ||||
|  | ||||
|   skip_block: function(tokens, ends) { | ||||
|     while (true) { | ||||
|       let off = tokens.length; | ||||
|  | ||||
|       if (this.check_token(tokens, 'if')) { | ||||
|         this.skip_paren(tokens); | ||||
|  | ||||
|         if (this.check_token(tokens, ':')) { | ||||
|           this.patch(tokens, '{'); | ||||
|  | ||||
|           this.skip_block(tokens, ['else', 'elif', 'endif']); | ||||
|  | ||||
|           while (tokens[tokens.length - 1].type == 'elif') { | ||||
|             let elif = tokens.pop(); | ||||
|  | ||||
|             tokens.push( | ||||
|               { type: '}',    value: '}',    prefix: '' }, | ||||
|               { type: 'else', value: 'else', prefix: elif.prefix }, | ||||
|               { type: 'if',   value: 'if',   prefix: ' ' } | ||||
|             ); | ||||
|  | ||||
|             this.skip_paren(tokens); | ||||
|  | ||||
|             this.assert_token(tokens, ':'); | ||||
|             this.patch(tokens, '{'); | ||||
|  | ||||
|             this.skip_block(tokens, ['elif', 'else', 'endif']); | ||||
|           } | ||||
|  | ||||
|           if (tokens[tokens.length - 1].type == 'else') { | ||||
|             let else_ = tokens.pop(); | ||||
|  | ||||
|             tokens.push( | ||||
|               { type: '}',    value: '}',    prefix: '' }, | ||||
|               { type: 'else', value: 'else', prefix: else_.prefix }, | ||||
|               { type: '{',    value: '{',    prefix: ' ' } | ||||
|             ); | ||||
|  | ||||
|             this.skip_block(tokens, ['endif']); | ||||
|           } | ||||
|  | ||||
|           this.patch(tokens, '}'); | ||||
|         } | ||||
|         else if (this.check_token(tokens, '{')) { | ||||
|           this.skip_block(tokens, ['}']); | ||||
|  | ||||
|           if (!this.check_token(tokens, 'else')) | ||||
|             continue; | ||||
|  | ||||
|           if (this.check_token(tokens, '{')) | ||||
|             this.skip_block(tokens, ['}']); | ||||
|           else | ||||
|             this.skip_statement(tokens, ends); | ||||
|         } | ||||
|         else { | ||||
|           this.skip_statement(tokens, ends); | ||||
|         } | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'for')) { | ||||
|         let cond = []; | ||||
|  | ||||
|         this.skip_paren(cond); | ||||
|  | ||||
|         // Transform `for (x, y in ...)` into `for (x/*, y*/ in ...)` | ||||
|         if (cond.length > 5 && | ||||
|             cond[1].type == 'label' && | ||||
|             cond[2].type == ',' && | ||||
|             cond[3].type == 'label' && | ||||
|             cond[4].type == 'in') { | ||||
|           cond[2].type = 'comment'; | ||||
|           cond[2].value = '/*' + cond[2].value; | ||||
|           cond[3].type = 'comment'; | ||||
|           cond[3].value = cond[3].value + '*/'; | ||||
|         } | ||||
|  | ||||
|         // Transform `for (let x, y in ...)` into `for (let x/*, y*/ in ...)` | ||||
|         else if (cond.length > 6 && | ||||
|                  cond[1].type == 'let' && | ||||
|                  cond[2].type == 'label' && | ||||
|                  cond[3].type == ',' && | ||||
|                  cond[4].type == 'label' && | ||||
|                  cond[5].type == 'in') { | ||||
|           cond[3].type = 'comment'; | ||||
|           cond[3].value = '/*' + cond[3].value; | ||||
|           cond[4].type = 'comment'; | ||||
|           cond[4].value = cond[4].value + '*/'; | ||||
|         } | ||||
|  | ||||
|         tokens.push(...cond); | ||||
|  | ||||
|         if (this.check_token(tokens, ':')) { | ||||
|           this.patch(tokens, '{'); | ||||
|           this.skip_block(tokens, ['endfor']); | ||||
|           this.patch(tokens, '}'); | ||||
|         } | ||||
|         else if (this.check_token(tokens, '{')) | ||||
|           this.skip_block(tokens, ['}']); | ||||
|         else | ||||
|           this.skip_statement(tokens, ends); | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'while')) { | ||||
|         this.skip_paren(tokens); | ||||
|  | ||||
|         if (this.check_token(tokens, ':')) { | ||||
|           this.patch(tokens, '{'); | ||||
|           this.skip_block(tokens, ['endwhile']); | ||||
|           this.patch(tokens, '}'); | ||||
|         } | ||||
|         else if (this.check_token(tokens, '{')) | ||||
|           this.skip_block(tokens, ['}']); | ||||
|         else | ||||
|           this.skip_statement(tokens, ends); | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'function')) { | ||||
|         this.check_token(tokens, 'label'); | ||||
|         this.skip_paren(tokens); | ||||
|  | ||||
|         if (this.check_token(tokens, ':')) { | ||||
|           this.patch(tokens, '{'); | ||||
|           this.skip_block(tokens, ['endfunction']); | ||||
|           this.patch(tokens, '}'); | ||||
|         } | ||||
|         else if (this.check_token(tokens, '{')) | ||||
|           this.skip_block(tokens, ['}']); | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'try')) { | ||||
|         this.assert_token(tokens, '{'); | ||||
|         this.skip_block(tokens, ['}']); | ||||
|         this.assert_token(tokens, 'catch'); | ||||
|  | ||||
|         // Transform `try { ... } catch { ... }` into `try { ... } catch(e) { ... }` | ||||
|         if (this.tokens[this.offset].type == '(') | ||||
|           this.skip_paren(tokens); | ||||
|         else | ||||
|           tokens.push( | ||||
|             { type: '(',     value: '(', prefix: '' }, | ||||
|             { type: 'label', value: 'e', prefix: '' }, | ||||
|             { type: ')',     value: ')', prefix: '' } | ||||
|           ); | ||||
|  | ||||
|         this.assert_token(tokens, '{'); | ||||
|         this.skip_block(tokens, ['}']); | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'switch')) { | ||||
|         this.skip_paren(tokens); | ||||
|         this.assert_token(tokens, '{'); | ||||
|         this.skip_block(tokens, ['}']); | ||||
|       } | ||||
|       else if (this.check_token(tokens, '{')) { | ||||
|         this.skip_block(tokens, ['}']); | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'text')) { | ||||
|         /* pass */ | ||||
|       } | ||||
|       else if (this.check_token(tokens, 'comment')) { | ||||
|         /* pass */ | ||||
|       } | ||||
|       else { | ||||
|         this.skip_statement(tokens, ends); | ||||
|       } | ||||
|  | ||||
|       for (let type of ends) | ||||
|         if (this.check_token(tokens, type)) | ||||
|           return tokens; | ||||
|  | ||||
|       if (this.check_token([], 'eof')) | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     throw 'Unexpected EOF'; | ||||
|   }, | ||||
|  | ||||
|   transpile: function() { | ||||
|     let tokens = []; | ||||
|  | ||||
|     this.skip_block(tokens, ['eof']); | ||||
|  | ||||
|     return tokens.map(t => t.prefix + t.value).join(''); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| exports.handlers = { | ||||
|   beforeParse: function(e) { | ||||
|     let raw = !e.source.match(/\{[{%]/) || e.source.match(/^#!([a-z\/]*)ucode[ \t]+-[A-Z]*R/), | ||||
|         t = new Transpiler(e.source, raw); | ||||
|  | ||||
|     e.source = t.transpile(); | ||||
|   } | ||||
| }; | ||||
| @@ -1,153 +0,0 @@ | ||||
| { | ||||
| 	"classes": { | ||||
| 		"network_services": { | ||||
| 			"ingress": "CS3", | ||||
| 			"eggress": "CS3", | ||||
| 			"bulk-pps": 100, | ||||
| 			"bulk-timeout": 5, | ||||
| 			"bulk-dscp": "CS0" | ||||
| 		}, | ||||
|  | ||||
| 		"conferencing": { | ||||
| 			"source": "https://support.zoom.us/hc/en-us/articles/207368756-Using-QoS-DSCP-Marking", | ||||
| 			"ingress": "EF", | ||||
| 			"egress": "EF" | ||||
| 		}, | ||||
|  | ||||
| 		"telephony": { | ||||
| 			"source": "https://support.zoom.us/hc/en-us/articles/207368756-Using-QoS-DSCP-Marking", | ||||
| 			"ingress": "EF", | ||||
| 			"egress": "EF" | ||||
| 		}, | ||||
| 		 | ||||
| 		"streaming": { | ||||
| 			"ingress": "AF41", | ||||
| 			"egress": "AF41" | ||||
| 		}, | ||||
|  | ||||
| 		"browsing": { | ||||
| 			"ingress": "CS0", | ||||
| 			"egress": "CS0" | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	"services": { | ||||
| 		"networking": { | ||||
| 			"classifier": "network_services", | ||||
| 			"tcp": [ 22, 123, 53, 5353 ], | ||||
| 			"udp": [ 53, 5353 ] | ||||
| 		}, | ||||
|  | ||||
| 		"browsing": { | ||||
| 			"classifier": "browsing", | ||||
| 			"tcp": [ 80, 443 ], | ||||
| 			"udp": [ 80, 443 ] | ||||
| 		}, | ||||
|  | ||||
| 		"youtube": { | ||||
| 			"source": "https://services.google.com/fh/files/blogs/enabling_remote_working_with_hangouts_meet_quick_deployment_guide.pdf", | ||||
| 			"classifier": "streaming", | ||||
| 			"fqdn": [ "*.googlevideo.com", "*.youtube-nocookie.com", "*.ytimg.com" ], | ||||
| 			"uses": [ "rtmp" ] | ||||
| 		}, | ||||
|  | ||||
| 		"netflix": { | ||||
| 			"source": "https://www.netify.ai/resources/applications/netflix", | ||||
| 			"classifier": "streaming", | ||||
| 			"fqdn": [ "*.nflxvideo.com", "*.nflxvideo.net" ] | ||||
| 		}, | ||||
|  | ||||
| 		"amazon-prime": { | ||||
| 			"source": "https://www.netify.ai/resources/applications/amazon-prime", | ||||
| 			"classifier": "streaming", | ||||
| 			"fqdn": [ | ||||
| 				"*aiv-cdn.net", "*aiv-delivery.net", "*amazonvideo.com", "*atv-ext.amazon.com", "*atv-ext-eu.amazon.com", | ||||
| 				"atv-ext-fe.amazon.com", "atv-ps.amazon.com", "atv-ps-eu.amazon.com", "atv-ps-eu.amazon.co.uk", | ||||
| 				"atv-ps-fe.amazon.co.jp", "atv-ps-fe.amazon.com", "primevideo.com", "pv-cdn.net", "video.a2z.com" | ||||
| 			] | ||||
| 		}, | ||||
|  | ||||
| 		"disney-plus": { | ||||
| 			"source": "https://www.netify.ai/resources/applications/disney-plus", | ||||
| 			"classifier": "streaming", | ||||
| 			"fqdn": [ | ||||
| 				"*disneyplus.com", "*disneyplus.disney.co.jp", "*disney-plus.net", "*disneystreaming.service-now.com", | ||||
| 				"*disney-vod-na-west-1.top.comcast.net", "*dssott.com" | ||||
| 			] | ||||
| 		}, | ||||
|  | ||||
| 		"hbo": { | ||||
| 			"source": "https://www.netify.ai/resources/applications/hbo", | ||||
| 			"classifier": "streaming", | ||||
| 			"fqdn": [ | ||||
| 				"*hbo.com", "*hbogoasia.com", "*hbogoasia.id", "*hbogoasia.ph", "*hbogo.com", "*hbogo.co.th", "*hbogo.eu", "*hbomaxcdn.com", | ||||
| 				"*hbomax.com", "*hbomax-images.warnermediacdn.com", "*hbonow.com", "maxgo.com" | ||||
| 			] | ||||
| 		}, | ||||
|  | ||||
| 		"rtmp": { | ||||
| 			"comment": "RTMP (YouTube Live, Twitch, Vimeo and LinkedIn Live)", | ||||
| 			"classifier": "streaming", | ||||
| 			"tcp": [ "1935-1936", 2396, 2935 ] | ||||
| 		}, | ||||
|  | ||||
| 		"stun": { | ||||
| 			"comment": "STUN (Session Traversal Utilities for NAT)", | ||||
| 			"classifier": "conferencing", | ||||
| 			"udp": [ "3478-3497" ] | ||||
| 		}, | ||||
|  | ||||
| 		"zoom": { | ||||
| 			"source": "https://support.zoom.us/hc/en-us/articles/201362683-Zoom-network-firewall-or-proxy-server-settings", | ||||
| 			"classifier": "conferencing", | ||||
| 			"fqdn": [ "*zoom.us" ], | ||||
| 			"tcp": [ "8801-8802" ], | ||||
| 			"udp": [ "8801-8810" ], | ||||
| 			"uses": [ "stun" ] | ||||
| 		}, | ||||
|  | ||||
| 		"facetime": { | ||||
| 			"source": "https://support.apple.com/en-us/HT202078", | ||||
| 			"classifier": "conferencing", | ||||
| 			"udp": [ "16384-16387", "16393-16402" ] | ||||
| 		}, | ||||
|  | ||||
| 		"webex": { | ||||
| 			"source": "https://help.webex.com/en-us/article/WBX264/How-Do-I-Allow-Webex-Meetings-Traffic-on-My-Network?#id_135400", | ||||
| 			"classifier": "conferencing", | ||||
| 			"tcp": [ 5004 ], | ||||
| 			"udp": [ 9000 ] | ||||
| 		}, | ||||
|  | ||||
| 		"jitsi": { | ||||
| 			"source": "https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart/#setup-and-configure-your-firewall", | ||||
| 			"classifier": "conferencing", | ||||
| 			"tcp": [ 5349 ], | ||||
| 			"udp": [ 10000 ], | ||||
| 			"uses": [ "stun" ] | ||||
| 		}, | ||||
|  | ||||
| 		"google-meet": { | ||||
| 			"source": "https://services.google.com/fh/files/blogs/enabling_remote_working_with_hangouts_meet_quick_deployment_guide.pdf", | ||||
| 			"classifier": "conferencing", | ||||
| 			"udp": [ "19302-19309" ] | ||||
| 		}, | ||||
|  | ||||
| 		"teams": { | ||||
| 			"source": "https://learn.microsoft.com/en-us/microsoft-365/enterprise/urls-and-ip-address-ranges?view=o365-worldwide#skype-for-business-online-and-microsoft-teams", | ||||
| 			"classifier": "conferencing", | ||||
| 			"uses": [ "stun" ] | ||||
| 		}, | ||||
|  | ||||
| 		"voip": { | ||||
| 			"classifier": "telephony", | ||||
| 			"tcp": [ "5060-5061" ], | ||||
| 			"udp": [ "5060-5061" ] | ||||
| 		}, | ||||
|  | ||||
| 		"vowifi": { | ||||
| 			"classifier": "telephony", | ||||
| 			"udp": [ 500, 4500 ] | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										1172
									
								
								renderer/renderer.uc
									
									
									
									
									
								
							
							
						
						
									
										1172
									
								
								renderer/renderer.uc
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,45 +0,0 @@ | ||||
| let fs = require('fs'); | ||||
|  | ||||
| let key_info = { | ||||
| 	'dummy_static': function(file, signature) { | ||||
| 		return signature == 'aaaaaaaaaa'; | ||||
| 	}, | ||||
| 	'cig_sha256': function(file, signature) { | ||||
| 		// Decrypt from base64 to binary and write to a tmp file | ||||
| 		let decoded = b64dec(signature); | ||||
| 		if (!decoded) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		let pub_key_file_name = "/etc/ucentral/sign_pubkey.pem"; | ||||
| 		let sign_file_name = "/tmp/sign_file.txt"; | ||||
| 		let sign_file = fs.open(sign_file_name, "w"); | ||||
| 		sign_file.write(decoded); | ||||
| 		sign_file.close(); | ||||
|  | ||||
| 		// Verify the signature | ||||
| 		let sign_verify_cmd = "openssl dgst -sha256 -verify " + pub_key_file_name + " -signature " + sign_file_name + " " + file; | ||||
| 		let pipe = fs.popen(sign_verify_cmd); | ||||
| 		let result = pipe.read("all"); | ||||
| 		let retcode = pipe.close(); | ||||
| 		// Return code of 0 is valid signature | ||||
| 		if (retcode == 0) { | ||||
| 			return true; | ||||
| 		} else { | ||||
| 			return false; | ||||
| 		} | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| return { | ||||
|  | ||||
| verify: function(file, signature) { | ||||
| 	let func = key_info[restrict?.key_info?.vendor + '_' + restrict?.key_info?.algo]; | ||||
|  | ||||
| 	if (!func) | ||||
| 		return false; | ||||
|  | ||||
| 	return func(file, signature); | ||||
| }, | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,42 +0,0 @@ | ||||
| {% | ||||
| 	let admin_ui = state.services?.admin_ui; | ||||
| 	if (!admin_ui?.wifi_ssid) | ||||
| 		return; | ||||
|  | ||||
| 	let interface = { | ||||
| 		admin_ui: true, | ||||
| 		name: 'Admin-UI', | ||||
| 		role: 'downstream', | ||||
| 		auto_start: 0, | ||||
| 		services: [ 'ssh', 'http' ], | ||||
| 		ipv4: { | ||||
| 			addressing: 'static', | ||||
| 			subnet: '10.254.254.1/24', | ||||
| 			dhcp: { | ||||
| 				lease_first: 10, | ||||
| 				lease_count: 10, | ||||
| 				lease_time: '6h' | ||||
| 			} | ||||
| 		}, | ||||
| 		ssids: [ | ||||
| 			{ | ||||
| 				name: admin_ui.wifi_ssid, | ||||
| 				wifi_bands: [ '2G', '5G' ], | ||||
| 				bss_mode: 'ap', | ||||
| 				encryption: { | ||||
| 					proto: 'none' | ||||
| 				} | ||||
| 			} | ||||
| 		], | ||||
| 	}; | ||||
|  | ||||
| 	if (admin_ui.wifi_bands) | ||||
| 		interface.ssids[0].wifi_bands = admin_ui.wifi_bands; | ||||
| 	if (admin_ui.wifi_key) { | ||||
| 		interface.ssids[0].encryption.proto = 'psk2'; | ||||
| 		interface.ssids[0].encryption.key = admin_ui.wifi_key; | ||||
| 	} | ||||
| 	push(state.interfaces, interface); | ||||
| %} | ||||
|  | ||||
| set state.ui.offline_trigger={{ admin_ui.offline_trigger }} | ||||
| @@ -1,57 +0,0 @@ | ||||
| {% | ||||
| let roles = (state.switch && state.switch.loop_detection && | ||||
| 	     state.switch.loop_detection.roles) ? | ||||
| 			state.switch.loop_detection.roles : []; | ||||
|  | ||||
| services.set_enabled("ustpd", length(roles)); | ||||
|  | ||||
| function loop_detect(role) { | ||||
| 	return (index(roles, role) >= 0) ? 1 : 0; | ||||
| } | ||||
| %} | ||||
|  | ||||
| # Basic configuration | ||||
| set network.loopback=interface | ||||
| set network.loopback.ifname='lo' | ||||
| set network.loopback.proto='static' | ||||
| set network.loopback.ipaddr='127.0.0.1' | ||||
| set network.loopback.netmask='255.0.0.0' | ||||
|  | ||||
| set network.up=device | ||||
| set network.up.name=up | ||||
| set network.up.type=bridge | ||||
| set network.up.stp={{ loop_detect("upstream") }} | ||||
| set network.up.igmp_snooping='1' | ||||
|  | ||||
| {% if (capab.platform != "switch"): %} | ||||
| set network.down=device | ||||
| set network.down.name=down | ||||
| set network.down.type=bridge | ||||
| set network.down.stp={{ loop_detect("downstream") }} | ||||
| set network.down.igmp_snooping='1' | ||||
|  | ||||
| {% endif %} | ||||
| set network.up_none=interface | ||||
| set network.up_none.ifname=up | ||||
| set network.up_none.proto=none | ||||
|  | ||||
| {% for (let k, v in capab.macaddr): %} | ||||
| add network device | ||||
| set network.@device[-1].name={{ s(k) }} | ||||
| set network.@device[-1].macaddr={{ s(v) }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% for (let k, v in capab.switch): %} | ||||
| add network switch | ||||
| set network.@switch[-1].name={{ s(v.name) }} | ||||
| set network.@switch[-1].reset={{ b(v.reset) }} | ||||
| set network.@switch[-1].enable_vlan={{ b(v.enable) }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% for (let k, port in ethernet.ports): %} | ||||
| {%   if (!port.switch) continue; %} | ||||
| add network switch_vlan | ||||
| set network.@switch_vlan[-1].device={{ s(port.switch.name) }} | ||||
| set network.@switch_vlan[-1].vlan={{ s(port.vlan) }} | ||||
| set network.@switch_vlan[-1].ports={{s(port.switch.port + 't ' + port.swconfig)}} | ||||
| {% endfor %} | ||||
| @@ -1,100 +0,0 @@ | ||||
| {% | ||||
| /* find and replace the first upstream interface using vid with | ||||
|  * the broadband settings */ | ||||
| let uplink = ethernet.get_interface("upstream", 0); | ||||
| if (!uplink) | ||||
| 	return; | ||||
|  | ||||
| if (index([ "wwan", "pppoe", "static", "dhcp", "wds" ], broadband.protocol) < 0) | ||||
| 	return; | ||||
|  | ||||
| for (let k, v in uplink) | ||||
| 	if (index([ "vlan", "index" ], k) < 0) | ||||
| 		delete uplink[k]; | ||||
|  | ||||
| uplink.name = "ISP_UPLINK"; | ||||
| uplink.role = "upstream"; | ||||
| uplink.vlan = {	id: 0 }; | ||||
| uplink.metric = 1; | ||||
|  | ||||
| if (broadband.protocol == "wwan") { | ||||
| 	let wwan = { }; | ||||
|  | ||||
| 	wwan.protocol = 'wwan'; | ||||
| 	wwan.modem_type = broadband['modem-type'] || 'wwan'; | ||||
| 	wwan.pin_code = broadband['pin-code'] || ''; | ||||
| 	wwan.access_point_name = broadband['access-point-name'] || ''; | ||||
| 	wwan.packet_data_protocol = broadband['packet-data-protocol'] || 'dual-stack'; | ||||
| 	wwan.authentication_type = broadband['authentication-type'] || 'none'; | ||||
| 	wwan.username = broadband.username || ''; | ||||
| 	wwan.password = broadband.password || ''; | ||||
|  | ||||
| 	uplink.broad_band = wwan; | ||||
| } | ||||
|  | ||||
| if (broadband.protocol == "pppoe") { | ||||
| 	let pppoe = { }; | ||||
|  | ||||
| 	pppoe.protocol = 'pppoe'; | ||||
| 	pppoe.username = broadband['user-name'] || ''; | ||||
| 	pppoe.password = broadband.password || ''; | ||||
| 	pppoe.timeout = broadband.timeout || '30'; | ||||
|  | ||||
| 	uplink.broad_band = pppoe; | ||||
| 	uplink.ethernet = [ | ||||
| 		{ | ||||
| 			"select_ports": [ "WAN*" ] | ||||
| 		} | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| if (broadband.protocol == "static") { | ||||
| 	uplink.ethernet = [ | ||||
| 		{ | ||||
| 			"select_ports": [ "WAN*" ] | ||||
| 		} | ||||
| 	]; | ||||
| 	uplink.ipv4 = { | ||||
| 		addressing: "static", | ||||
| 		subnet: broadband['ipv4-address'], | ||||
| 		gateway: broadband['ipv4-gateway'], | ||||
| 		use_dns: broadband['use-dns'], | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| if (broadband.protocol == "dhcp") { | ||||
| 	uplink.ethernet = [ | ||||
| 		{ | ||||
| 			"select_ports": [ "WAN*" ] | ||||
| 		} | ||||
| 	]; | ||||
| 	uplink.ipv4 = { | ||||
| 		addressing: "dynamic", | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| if (broadband.protocol == "wds") { | ||||
| 	uplink.ipv4 = { | ||||
| 		addressing: "dynamic", | ||||
| 	}; | ||||
|  | ||||
| 	let wds = { | ||||
| 		"name": broadband.ssid, | ||||
| 		"wifi_bands": [ | ||||
| 			"2G", "5G" | ||||
| 		], | ||||
| 		"bss_mode": "wds-sta", | ||||
| 		"encryption": { | ||||
| 			"proto": broadband.encryption, | ||||
| 			"key": broadband.passphrase, | ||||
| 			"ieee80211w": "optional" | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	if (!uplink.ssids) | ||||
| 		uplink.ssids = [ wds ]; | ||||
| 	else | ||||
| 		push(uplink.ssids, wds); | ||||
| } | ||||
|  | ||||
| %} | ||||
| @@ -1,5 +0,0 @@ | ||||
|  | ||||
| # Raw Configuration | ||||
| {% for (let config in config_raw): %} | ||||
| {{ config[0] }} {{ config[1] }}{{config[2] ? '=' + config[2] : ''}} | ||||
| {% endfor %} | ||||
| @@ -1,4 +0,0 @@ | ||||
| {% for (let user in users): %} | ||||
| "{{ user.user_name }}"	PWD	"{{ user.password }}" | ||||
| {% endfor %} | ||||
| * TLS,TTLS | ||||
| @@ -1,13 +0,0 @@ | ||||
| {% let eth_ports = ethernet.lookup_by_select_ports(ports.select_ports) %} | ||||
| {% for (let port in eth_ports): | ||||
| 	port = replace(port, '.', '_'); | ||||
| %} | ||||
| set network.{{ port }}=device | ||||
| set network.{{ port }}.name={{ s(port) }} | ||||
| set network.{{ port }}.ifname={{ s(port) }} | ||||
| set network.{{ port }}.enabled={{ ports.enabled }} | ||||
| {% if (!ports.speed && !ports.duplex) continue %} | ||||
| set network.{{ port }}.speed={{ ports.speed }} | ||||
| set network.{{ port }}.duplex={{ ports.duplex == "full" ? true : false }} | ||||
|  | ||||
| {% endfor %} | ||||
| @@ -1,216 +0,0 @@ | ||||
| {% | ||||
| 	let has_downstream_relays = false; | ||||
| 	let dest; | ||||
|  | ||||
| 	// Skip interfaces previously marked as conflicting. | ||||
| 	if (interface.conflicting) { | ||||
| 		warn("Skipping conflicting interface declaration"); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Skip upstream interfaces that try to use a wireguard overlay | ||||
| 	if (interface.role == 'upstream' && 'wireguard-overlay' in interface.services) { | ||||
| 		warn("Skipping interface. wireguard-overlay is not allowed on upstream interfaces."); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Check this interface for role/vlan uniqueness... | ||||
| 	let this_vid = interface.vlan.id || interface.vlan.dyn_id; | ||||
|  | ||||
| 	for (let other_interface in state.interfaces) { | ||||
| 		if (other_interface == interface) | ||||
| 			continue; | ||||
|  | ||||
| 		if (!other_interface.ethernet && length(interface.ssids) == 1) | ||||
| 			continue; | ||||
|  | ||||
| 		let other_vid = other_interface.vlan.id || ''; | ||||
|  | ||||
| 		if (interface.role === other_interface.role && this_vid === other_vid) { | ||||
| 			warn("Multiple interfaces with same role and VLAN ID defined, ignoring conflicting interface"); | ||||
| 			other_interface.conflicting = true; | ||||
| 		} | ||||
|  | ||||
| 		if (other_interface.role == 'downstream' && | ||||
| 		    other_interface.ipv6 && | ||||
| 		    other_interface.ipv6.dhcpv6 && | ||||
| 		    other_interface.ipv6.dhcpv6.mode == 'relay') | ||||
| 		    has_downstream_relays = true; | ||||
| 	} | ||||
|  | ||||
| 	// check if a downstream interface with a vlan has a matching upstream interface | ||||
| 	if (ethernet.has_vlan(interface) && interface.role == "downstream" && index(vlans, this_vid) < 0) { | ||||
| 		warn("Trying to create a downstream interface with a VLAN ID, without matching upstream interface."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// reject static config that has no subnet | ||||
| 	if (interface.role == 'upstream' && interface.ipv4?.addressing == 'static') | ||||
| 		if (!interface.ipv4?.subnet || !interface.ipv4?.use_dns || !interface.ipv4?.gateway) | ||||
| 			die('invalid static interface settings'); | ||||
|  | ||||
| 	// resolve auto prefixes | ||||
| 	if (wildcard(interface.ipv4?.subnet, 'auto/*')) { | ||||
| 		try { | ||||
| 			interface.ipv4.subnet = ipcalc.generate_prefix(state, interface.ipv4.subnet, false); | ||||
| 		} | ||||
| 		catch (e) { | ||||
| 			warn("Unable to allocate a suitable IPv4 prefix: %s, ignoring interface", e); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (wildcard(interface.ipv6?.subnet, 'auto/*')) { | ||||
| 		try { | ||||
| 			interface.ipv6.subnet = ipcalc.generate_prefix(state, interface.ipv6.subnet, true); | ||||
| 		} | ||||
| 		catch (e) { | ||||
| 			warn("Unable to allocate a suitable IPv6 prefix: %s, ignoring interface", e); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Captive Portal is only supported on downstream interfaces | ||||
| 	if (interface.captive && interface.role != 'downstream') { | ||||
| 		warn("Trying to create a Captive Portal on a none downstream interface."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Port forwardings are only supported on downstream interfaces | ||||
| 	if ((interface.ipv4?.port_forward || interface.ipv6?.port_forward) && interface.role != 'downstream') { | ||||
| 		warn("Port forwardings are only supported on downstream interfaces."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Traffic accept rules are only supported on downstream interfaces | ||||
| 	if (interface.ipv6?.traffic_allow && interface.role != 'downstream') { | ||||
| 		warn("Traffic accept rules are only supported on downstream interfaces."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Gather related BSS modes and ethernet ports. | ||||
| 	let bss_modes = map(interface.ssids, ssid => ssid.bss_mode); | ||||
| 	let eth_ports = ethernet.lookup_by_interface_vlan(interface); | ||||
| 	let swconfig; | ||||
| 	if (interface.role == 'upstream') | ||||
| 		swconfig = ethernet.switch_by_interface_vlan(interface); | ||||
|  | ||||
| 	// If at least one station mode SSID is part of this interface then we must | ||||
| 	// not bridge at all. Having any other SSID or any number of matching ethernet | ||||
| 	// ports in such a case is a semantic error. | ||||
| 	if ('sta' in bss_modes && (length(eth_ports) > 0 || length(bss_modes) > 1)) { | ||||
| 		warn("Station mode SSIDs cannot be bridged with ethernet ports or other SSIDs, ignoring interface"); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Compute unique logical name and netdev name to use | ||||
| 	let name = ethernet.calculate_name(interface); | ||||
| 	let bridgedev = 'up'; | ||||
| 	if (capab.platform != "switch" && interface.role == "downstream") | ||||
| 		bridgedev = 'down'; | ||||
| 	let netdev = name; | ||||
| 	let network = name; | ||||
|  | ||||
| 	// Determine the IPv4 and IPv6 configuration modes and figure out if we | ||||
| 	// can set them both in a single interface (automatic) or whether we need | ||||
| 	// two logical interfaces due to different protocols. | ||||
| 	let ipv4_mode = interface.ipv4 ? interface.ipv4.addressing : 'none'; | ||||
| 	let ipv6_mode = interface.ipv6 ? interface.ipv6.addressing : 'none'; | ||||
|  | ||||
| 	// If no metric is defined explicitly, any upstream interfaces will default | ||||
| 	// to 5 and downstream interfaces will default to 10 | ||||
| 	if (!interface.metric && interface.role == "upstream") | ||||
| 		interface.metric = 5; | ||||
| 	if (!interface.metric && interface.role == "downstream") | ||||
| 		interface.metric = 10; | ||||
|  | ||||
| 	// If this interface is a tunnel, we need to create the interface | ||||
| 	// in a different way | ||||
| 	let tunnel_proto = interface.tunnel ? interface.tunnel.proto : ''; | ||||
|  | ||||
| 	// | ||||
| 	// Create the actual UCI sections | ||||
| 	// | ||||
|  | ||||
| 	if (interface.broad_band) { | ||||
| 		include("interface/broadband.uc", { interface, name, location, eth_ports, raw_ports }); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// tunnel interfaces need additional sections | ||||
| 	if (tunnel_proto in [ "mesh", "l2tp", "vxlan", "gre", "gre6" ]) | ||||
| 		include("interface/" + tunnel_proto + ".uc", { interface, name, eth_ports, location, netdev, ipv4_mode, ipv6_mode, this_vid }); | ||||
|  | ||||
| 	if (!interface.ethernet && length(interface.ssids) == 1 && !tunnel_proto && !("vxlan-overlay" in interface.services)) { | ||||
| 		if (interface.role == 'downstream') | ||||
| 			interface.type = 'bridge'; | ||||
| 		netdev = ''; | ||||
| 	} else if (tunnel_proto == 'vxlan') { | ||||
| 		netdev = '@' + name + '_vx'; | ||||
| 		interface.type = 'bridge'; | ||||
| 	} else if (tunnel_proto != 'gre' && tunnel_proto != 'gre6') | ||||
| 		// anything else requires a bridge-vlan | ||||
| 		include("interface/bridge-vlan.uc", { interface, name, eth_ports, this_vid, bridgedev, swconfig }); | ||||
|  | ||||
| 	if (interface.role == "downstream" && "wireguard-overlay" in interface.services) | ||||
| 		dest = 'unet'; | ||||
|  | ||||
| 	include("interface/common.uc", { | ||||
| 		name, this_vid, netdev, | ||||
| 		ipv4_mode, ipv4: interface.ipv4 || {}, | ||||
| 		ipv6_mode, ipv6: interface.ipv6 || {} | ||||
| 	}); | ||||
|  | ||||
| 	include('interface/firewall.uc', { name, ipv4_mode, ipv6_mode, dest }); | ||||
|  | ||||
| 	if (interface.ipv4 || interface.ipv6) { | ||||
| 		include('interface/dhcp.uc', { | ||||
| 			ipv4: interface.ipv4 || {}, | ||||
| 			ipv6: interface.ipv6 || {}, | ||||
| 			has_downstream_relays | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	let count = 0; | ||||
| 	for (let i, ssid in interface.ssids) { | ||||
| 		let modes = (ssid.bss_mode == "wds-repeater") ? | ||||
| 			[ "wds-sta", "wds-ap" ] : [ ssid.bss_mode ]; | ||||
| 		for (let mode in modes) { | ||||
| 			include('interface/ssid.uc', { | ||||
| 				location: location + '/ssids/' + i, | ||||
| 				ssid: { ...ssid, bss_mode: mode }, | ||||
| 				count, | ||||
| 				name, | ||||
| 				network, | ||||
| 			}); | ||||
| 			if (ssid?.encryption?.proto == 'owe-transition') { | ||||
| 				ssid.encryption.proto = 'none'; | ||||
| 				include('interface/ssid.uc', { | ||||
| 					location: location + '/ssids/' + i + '_owe', | ||||
| 					ssid: { ...ssid, bss_mode: mode }, | ||||
| 					count, | ||||
| 					name, | ||||
| 					network, | ||||
| 					owe: true, | ||||
| 			}); | ||||
|  | ||||
| 			} | ||||
| 			count++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (interface.captive) | ||||
| 		include('interface/captive.uc', { name }); | ||||
| %} | ||||
| {% if (tunnel_proto == 'mesh'): %} | ||||
| set network.{{ name }}.batman=1 | ||||
| {% endif %} | ||||
|  | ||||
| {% if (interface.role == "downstream" && "wireguard-overlay" in interface.services): %} | ||||
| add network rule | ||||
| set network.@rule[-1].in='{{name}}' | ||||
| set network.@rule[-1].lookup='{{ routing_table.get('wireguard_overlay') }}' | ||||
| {% endif %} | ||||
| @@ -1,64 +0,0 @@ | ||||
| add network bridge-vlan | ||||
| set network.@bridge-vlan[-1].device={{ bridgedev }} | ||||
| set network.@bridge-vlan[-1].vlan={{ this_vid }} | ||||
| {%  for (let port in keys(eth_ports)): %} | ||||
| add_list network.@bridge-vlan[-1].ports={{ port }}{{ ethernet.port_vlan(interface, eth_ports[port]) }} | ||||
| {%  endfor %} | ||||
| {% if (interface.tunnel?.proto == "mesh"): %} | ||||
| add_list network.@bridge-vlan[-1].ports=batman{{ ethernet.has_vlan(interface) ? "." + this_vid + ":t" : '' }} | ||||
| {% endif %} | ||||
| {% if (interface.tunnel?.proto == "vxlan"): %} | ||||
| add_list network.@bridge-vlan[-1].ports={{ name }}_vx | ||||
| {% endif %} | ||||
| {% if (interface.tunnel?.proto == "gre"): %} | ||||
| add_list network.@bridge-vlan[-1].ports=gre4t-gre.{{ interface.vlan.id }} | ||||
| {% endif %} | ||||
| {% if (interface.tunnel?.proto == "gre6"): %} | ||||
| add_list network.@bridge-vlan[-1].ports=gre6t-greip6.{{ interface.vlan.id }} | ||||
| {% endif %} | ||||
| {% if ('vxlan-overlay' in interface.services): %} | ||||
| add_list network.@bridge-vlan[-1].ports=vx-unet | ||||
| {% endif %} | ||||
| {% if (interface.bridge): %} | ||||
| set network.@bridge-vlan[-1].txqueuelen={{ interface.bridge.tx_queue_len }} | ||||
| set network.@bridge-vlan[-1].isolate={{interface.bridge.isolate_ports }} | ||||
| set network.@bridge-vlan[-1].mtu={{ interface.bridge.mtu }} | ||||
| {% endif %} | ||||
|  | ||||
| add network device | ||||
| set network.@device[-1].type=8021q | ||||
| set network.@device[-1].name={{ name }} | ||||
| set network.@device[-1].ifname={{ bridgedev }} | ||||
| set network.@device[-1].vid={{ this_vid }} | ||||
|  | ||||
| {% if (interface.vlan_awareness?.first): %} | ||||
| {%   let vlan = interface.vlan_awareness.first; | ||||
|      if (interface.vlan_awareness.last) | ||||
| 	     vlan += '-' + interface.vlan_awareness.last; %} | ||||
| {%   for (let port in keys(eth_ports)): %} | ||||
| add network device | ||||
| set network.@device[-1].name={{ port }} | ||||
| set network.@device[-1].vlan={{ vlan }} | ||||
| {%   endfor %} | ||||
| {%   if (interface.role == 'upstream'): %} | ||||
| set network.up.vlan={{ vlan }} | ||||
| {%   endif %} | ||||
| {%   if (interface.role == 'downstream'): %} | ||||
| set network.down.vlan={{ vlan }} | ||||
| {%   endif %} | ||||
| {% endif %} | ||||
|  | ||||
| {% if (interface.role == 'upstream'): %} | ||||
| {%  for (let port in keys(eth_ports)): %} | ||||
| set udevstats.{{ port }}=device | ||||
| set udevstats.{{ port }}.name={{ s(port) }} | ||||
| add_list udevstats.{{ port }}.vlan={{ s(interface.vlan.id || 0) }} | ||||
| {%  endfor %} | ||||
| {% endif %} | ||||
|  | ||||
| {% if (interface.vlan.id && swconfig): %} | ||||
| add network switch_vlan | ||||
| set network.@switch_vlan[-1].device={{ s(swconfig.name) }} | ||||
| set network.@switch_vlan[-1].vlan={{ s(this_vid) }} | ||||
| set network.@switch_vlan[-1].ports={{s(swconfig.ports)}} | ||||
| {% endif %} | ||||
| @@ -1,45 +0,0 @@ | ||||
| {% if (interface.broad_band.protocol == 'wwan'): %} | ||||
| {% | ||||
| function match_pdptype() { | ||||
| 	let pdptypes = { | ||||
| 		'ipv4': 'ip', | ||||
| 		'ipv6': 'ipv6', | ||||
| 		'dual-stack': 'ipv4v6' | ||||
| 	}; | ||||
| 	return pdptypes[interface.broad_band.packet_data_protocol]; | ||||
| } | ||||
|  | ||||
| function match_authtype() { | ||||
| 	if (interface.broad_band.authentication_type == 'papchap') | ||||
| 		return 'both'; | ||||
| 	return interface.broad_band.authentication_type; | ||||
| } | ||||
| %} | ||||
|  | ||||
| set network.{{ name }}=interface | ||||
| set network.{{ name }}.ucentral_name={{ s(interface.name) }} | ||||
| set network.{{ name }}.ucentral_path={{ s(location) }} | ||||
| set network.{{ name }}.proto={{ s(interface.broad_band.modem_type) }} | ||||
| set network.{{ name }}.pincode={{ s(interface.broad_band.pin_code) }} | ||||
| set network.{{ name }}.apn={{ s(interface.broad_band.access_point_name) }} | ||||
| set network.{{ name }}.device='/dev/cdc-wdm0' | ||||
| set network.{{ name }}.pdptype={{ s(match_pdptype()) }} | ||||
| set network.{{ name }}.auth={{ s(match_authtype()) }} | ||||
| set network.{{ name }}.username={{ s(interface.broad_band.user_name) }} | ||||
| set network.{{ name }}.password={{ s(interface.broad_band.password) }} | ||||
| {% endif %} | ||||
|  | ||||
| {% if (interface.broad_band.protocol == 'pppoe'): %} | ||||
|  | ||||
| set network.{{ name }}=interface | ||||
| set network.{{ name }}.ucentral_name={{ s(interface.name) }} | ||||
| set network.{{ name }}.ucentral_path={{ s(location) }} | ||||
| set network.{{ name }}.ifname={{ s(keys(eth_ports)[0]) }} | ||||
| set network.{{ name }}.proto='pppoe' | ||||
| set network.{{ name }}.username={{ s(interface.broad_band.user_name) }} | ||||
| set network.{{ name }}.password={{ s(interface.broad_band.password) }} | ||||
| set network.{{ name }}.timeout={{ s(interface.broad_band.timeout) }} | ||||
| {% endif %} | ||||
|  | ||||
|  | ||||
| {% include('firewall.uc', { name, ipv4: true, ipv6: true }); %} | ||||
| @@ -1,119 +0,0 @@ | ||||
| {% | ||||
| if (config.radius_gw_proxy) | ||||
| 	services.set_enabled("radius-gw-proxy", true); | ||||
|  | ||||
| function radius_proxy_tlv(server, port, name) { | ||||
| 	let tlv = replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1$2$3$4$5$6") + sprintf(":%s:%s:%s", server, port, name); | ||||
| 	return tlv; | ||||
| } | ||||
|  | ||||
| captive.interface(section, config); | ||||
| let name = split(section, '_')[0]; | ||||
|  | ||||
| if (!captive.web_root) | ||||
| 	system('cp -r /www-uspot /tmp/ucentral/'); | ||||
| else { | ||||
| 	let fs = require('fs'); | ||||
| 	fs.mkdir('/tmp/ucentral/www-uspot'); | ||||
| 	let web_root = fs.open('/tmp/ucentral/web-root.tar', 'w'); | ||||
| 	web_root.write(b64dec(captive.web_root)); | ||||
| 	web_root.close(); | ||||
| 	system('tar x -C /tmp/ucentral/www-uspot -f /tmp/ucentral/web-root.tar'); | ||||
| } | ||||
|  | ||||
| if (captive.radius_gw_proxy) | ||||
| 	services.set_enabled("radius-gw-proxy", true); | ||||
| %} | ||||
|  | ||||
| # Captive Portal service configuration | ||||
|  | ||||
| set uspot.{{ section }}=uspot | ||||
| set uspot.{{ section }}.auth_mode={{ s(config.auth_mode) }} | ||||
| set uspot.{{ section }}.web_root={{ b(config.web_root) }} | ||||
| set uspot.{{ section }}.idle_timeout={{ config.idle_timeout }} | ||||
| set uspot.{{ section }}.session_timeout={{ config.session_timeout }} | ||||
|  | ||||
| {% if (config.auth_mode in [ 'radius', 'uam']): %} | ||||
| {%   if (config.radius_gw_proxy): %} | ||||
| set uspot.{{ section }}.auth_server='127.0.0.1' | ||||
| set uspot.{{ section }}.auth_port='1812' | ||||
| set uspot.{{ section }}.auth_proxy={{ s(radius_proxy_tlv(config.auth_server, config.auth_port, 'captive')) }} | ||||
| {%     if (config.acct_server): %} | ||||
| set uspot.{{ section }}.acct_server='127.0.0.1' | ||||
| set uspot.{{ section }}.acct_port='1813' | ||||
| set uspot.{{ section }}.acct_proxy={{ s(radius_proxy_tlv(config.acct_server, config.acct_port, 'captive')) }} | ||||
| {%     endif %} | ||||
| {%   else %} | ||||
| set uspot.{{ section }}.auth_server={{ s(config.auth_server) }} | ||||
| set uspot.{{ section }}.auth_port={{ s(config.auth_port) }} | ||||
| set uspot.{{ section }}.acct_server={{ s(config.acct_server) }} | ||||
| set uspot.{{ section }}.acct_port={{ s(config.acct_port) }} | ||||
| {%   endif %} | ||||
| set uspot.{{ section }}.auth_secret={{ s(config.auth_secret) }} | ||||
| set uspot.{{ section }}.acct_secret={{ s(config.acct_secret) }} | ||||
| set uspot.{{ section }}.acct_interval={{ config.acct_interval }} | ||||
| {% endif %} | ||||
|  | ||||
| {% if (config.auth_mode == 'credentials'): %} | ||||
| {%   for (let cred in config.credentials): %} | ||||
| add uspot credentials | ||||
| set uspot.@credentials[-1].username={{ s(cred.username) }} | ||||
| set uspot.@credentials[-1].password={{ s(cred.password) }} | ||||
| set uspot.@credentials[-1].interface={{ s(section) }} | ||||
| {%   endfor %} | ||||
| {% endif %} | ||||
|  | ||||
| {% if (config.auth_mode == 'uam'): %} | ||||
| {% | ||||
| let math = require('math'); | ||||
| let challenge = ""; | ||||
| for (let i = 0; i < 16; i++) | ||||
| 	challenge += sprintf('%02x', math.rand() % 255); | ||||
| %} | ||||
| set uspot.{{ section }}.challenge={{ s(challenge) }} | ||||
|  | ||||
| set uspot.{{ section }}.uam_port={{ s(config.uam_port) }} | ||||
| set uspot.{{ section }}.uam_secret={{ s(config.uam_secret) }} | ||||
| set uspot.{{ section }}.uam_server={{ s(config.uam_server) }} | ||||
| set uspot.{{ section }}.nasid={{ s(config.nasid) }} | ||||
| set uspot.{{ section }}.nasmac={{ s(config.nasmac || serial) }} | ||||
| set uspot.{{ section }}.ssid={{ s(config.ssid) }} | ||||
| set uspot.{{ section }}.mac_format={{ s(config.mac_format) }} | ||||
| set uspot.{{ section }}.final_redirect_url={{ s(config.final_redirect_url) }} | ||||
| set uspot.{{ section }}.mac_auth={{ b(config.mac_auth) }} | ||||
|  | ||||
| set uhttpd.uam{{ config.uam_port }}=uhttpd | ||||
| set uhttpd.@uhttpd[-1].redirect_https='0' | ||||
| set uhttpd.@uhttpd[-1].rfc1918_filter='1' | ||||
| set uhttpd.@uhttpd[-1].max_requests='5' | ||||
| set uhttpd.@uhttpd[-1].max_connections='100' | ||||
| set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt' | ||||
| set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key' | ||||
| set uhttpd.@uhttpd[-1].script_timeout='60' | ||||
| set uhttpd.@uhttpd[-1].network_timeout='30' | ||||
| set uhttpd.@uhttpd[-1].http_keepalive='20' | ||||
| set uhttpd.@uhttpd[-1].tcp_keepalive='1' | ||||
| add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:{{ config.uam_port }}' | ||||
| add_list uhttpd.@uhttpd[-1].listen_http='[::]:{{ config.uam_port }}' | ||||
| set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot | ||||
| add_list uhttpd.@uhttpd[-1].ucode_prefix='/logon=/usr/share/uspot/handler-uam.uc' | ||||
| add_list uhttpd.@uhttpd[-1].ucode_prefix='/logoff=/usr/share/uspot/handler-uam.uc' | ||||
| add_list uhttpd.@uhttpd[-1].ucode_prefix='/logout=/usr/share/uspot/handler-uam.uc' | ||||
|  | ||||
| set firewall.{{ name + config.uam_port}}_1=rule | ||||
| set firewall.@rule[-1].name='Allow-UAM-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='{{ config.uam_port }}' | ||||
| set firewall.@rule[-1].proto='tcp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].mark='1/127' | ||||
|  | ||||
| set firewall.{{ name + config.uam_port}}_2=rule | ||||
| set firewall.@rule[-1].name='Allow-UAM-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='{{ config.uam_port }}' | ||||
| set firewall.@rule[-1].proto='tcp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].mark='2/127' | ||||
|  | ||||
| {% endif %} | ||||
| @@ -1,39 +0,0 @@ | ||||
| {% let afnames = ethernet.calculate_names(interface) %} | ||||
| {% if (length(afnames) >= 2): %} | ||||
| set network.{{ netdev }}=interface | ||||
| set network.{{ netdev }}.ucentral_name={{ s(interface.name) }} | ||||
| set network.{{ netdev }}.ucentral_path={{ s(location) }} | ||||
| set network.{{ netdev }}.ifname={{ netdev }} | ||||
| set network.{{ netdev }}.metric={{ interface.metric }} | ||||
| set network.{{ netdev }}.proto=none | ||||
| {% endif %} | ||||
| {% for (let afidx, afname in afnames): %} | ||||
| set network.{{ afname }}=interface | ||||
| set network.{{ afname }}.ucentral_name={{ s(interface.name) }} | ||||
| set network.{{ afname }}.ucentral_path={{ s(location) }} | ||||
| set network.{{ afname }}.ifname={{ netdev }} | ||||
| set network.{{ afname }}.metric={{ interface.metric }} | ||||
| set network.{{ afname }}.mtu={{ interface.mtu }} | ||||
| set network.{{ afname }}.type={{ interface.type }} | ||||
| set network.{{ afname }}.auto={{ interface.auto_start }} | ||||
| {%  if (ipv4_mode == 'static' || ipv6_mode == 'static'): %} | ||||
| set network.{{ afname }}.proto=static | ||||
| {%  elif ((length(afnames) == 1 || afidx == 0) && ipv4_mode == 'dynamic'): %} | ||||
| set network.{{ afname }}.proto=dhcp | ||||
| {%  elif ((length(afnames) == 1 || afidx == 1) && ipv6_mode == 'dynamic'): %} | ||||
| set network.{{ afname }}.proto=dhcpv6 | ||||
| {%  else %} | ||||
| set network.{{ afname }}.proto=none | ||||
| {%  endif %} | ||||
| {%  if (interface.role == "downstream" && ethernet.has_vlan(interface)): %} | ||||
| add network rule | ||||
| set network.@rule[-1].in={{ afname }} | ||||
| set network.@rule[-1].lookup={{ routing_table.get(interface.vlan.id) }} | ||||
| {%  endif %} | ||||
| {%  if ((length(afnames) == 1 && ipv4_mode != 'none') || (afidx == 0 && ipv4_mode != 'none')): %} | ||||
| {%   include('ipv4.uc', { name: afname }) %} | ||||
| {%  endif %} | ||||
| {%  if ((length(afnames) == 1 && ipv6_mode != 'none') || (afidx == 1 && ipv6_mode != 'none')): %} | ||||
| {%   include('ipv6.uc', { name: afname }) %} | ||||
| {%  endif %} | ||||
| {% endfor %} | ||||
| @@ -1,60 +0,0 @@ | ||||
| {% let name = ethernet.calculate_name(interface) %} | ||||
| {% let dhcp = ipv4.dhcp || { ignore: 1 } %} | ||||
| {% let dhcpv6 = ipv6.dhcpv6 || {} %} | ||||
|  | ||||
| set dhcp.{{ name }}=dhcp | ||||
| set dhcp.{{ name }}.interface={{ s(ethernet.calculate_ipv4_name(interface)) }} | ||||
| set dhcp.{{ name }}.start={{ dhcp.lease_first }} | ||||
| set dhcp.{{ name }}.limit={{ dhcp.lease_count }} | ||||
| set dhcp.{{ name }}.leasetime={{ dhcp.lease_time }} | ||||
| set dhcp.{{ name }}.ignore={{ b(dhcp.ignore) }} | ||||
| {% if (interface.role != 'upstream'): %} | ||||
| {%  if (dhcpv6.mode == 'hybrid'): %} | ||||
| set dhcp.{{ name }}.ra=server | ||||
| set dhcp.{{ name }}.dhcpv6=server | ||||
| set dhcp.{{ name }}.ndp=disabled | ||||
| set dhcp.{{ name }}.ra_slaac=1 | ||||
| add_list dhcp.{{ name }}.ra_flags=other-config | ||||
| add_list dhcp.{{ name }}.ra_flags=managed-config | ||||
| {%  elif (dhcpv6.mode == 'stateful'): %} | ||||
| set dhcp.{{ name }}.ra=server | ||||
| set dhcp.{{ name }}.dhcpv6=server | ||||
| set dhcp.{{ name }}.ndp=disabled | ||||
| set dhcp.{{ name }}.ra_slaac=0 | ||||
| add_list dhcp.{{ name }}.ra_flags=other-config | ||||
| add_list dhcp.{{ name }}.ra_flags=managed-config | ||||
| {%  elif (dhcpv6.mode == 'stateless'): %} | ||||
| set dhcp.{{ name }}.ra=server | ||||
| set dhcp.{{ name }}.dhcpv6=server | ||||
| set dhcp.{{ name }}.ndp=disabled | ||||
| set dhcp.{{ name }}.ra_slaac=1 | ||||
| add_list dhcp.{{ name }}.ra_flags=other-config | ||||
| {%  elif (dhcpv6.mode == 'relay'): %} | ||||
| set dhcp.{{ name }}.ra=relay | ||||
| set dhcp.{{ name }}.dhcpv6=relay | ||||
| set dhcp.{{ name }}.ndp=relay | ||||
| {%  else %} | ||||
| set dhcp.{{ name }}.ra=disabled | ||||
| set dhcp.{{ name }}.dhcpv6=disabled | ||||
| set dhcp.{{ name }}.ndp=disabled | ||||
| {%  endif %} | ||||
| set dhcp.{{ name }}.prefix_filter={{ s(dhcpv6.filter_prefix) }} | ||||
| set dhcp.{{ name }}.dns_service={{ b(!length(dhcpv6.announce_dns)) }} | ||||
| {%  for (let i, addr in dhcpv6.announce_dns): %} | ||||
| add_list dhcp.{{ name }}.dns={{ s(addr) }} | ||||
| {%  endfor %} | ||||
| {% else %} | ||||
| set dhcp.{{ name }}.master={{ b(has_downstream_relays) }} | ||||
| set dhcp.{{ name }}.ra={{ has_downstream_relays ? 'relay' : 'disabled' }} | ||||
| set dhcp.{{ name }}.dhcpv6={{ has_downstream_relays ? 'relay' : 'disabled' }} | ||||
| set dhcp.{{ name }}.ndp={{ has_downstream_relays ? 'relay' : 'disabled' }} | ||||
| {% endif %} | ||||
| {% for (let lease in interface.ipv4.dhcp_leases): %} | ||||
|  | ||||
| add dhcp host | ||||
| set dhcp.@host[-1].hostname={{ lease.hostname }} | ||||
| set dhcp.@host[-1].mac={{ lease.macaddr }} | ||||
| set dhcp.@host[-1].ip={{ lease.static_lease_offset }} | ||||
| set dhcp.@host[-1].leasetime={{ lease.lease_time }} | ||||
| set dhcp.@host[-1].instance={{ s(name) }} | ||||
| {% endfor %} | ||||
| @@ -1,171 +0,0 @@ | ||||
|  | ||||
| add firewall zone | ||||
| set firewall.@zone[-1].name={{ s(name) }} | ||||
| {% if (interface.role == 'upstream'): %} | ||||
| set firewall.@zone[-1].input='REJECT' | ||||
| set firewall.@zone[-1].output='ACCEPT' | ||||
| set firewall.@zone[-1].forward='REJECT' | ||||
| set firewall.@zone[-1].masq=1 | ||||
| set firewall.@zone[-1].mtu_fix=1 | ||||
| {% else %} | ||||
| set firewall.@zone[-1].input='REJECT' | ||||
| set firewall.@zone[-1].output='ACCEPT' | ||||
| set firewall.@zone[-1].forward='ACCEPT' | ||||
|  | ||||
| add firewall forwarding | ||||
| set firewall.@forwarding[-1].src={{ s(name) }} | ||||
| set firewall.@forwarding[-1].dest='{{ s(dest || ethernet.find_interface("upstream", interface.vlan.id)) }}' | ||||
| {% endif %} | ||||
| {% for (let network in networks || ethernet.calculate_names(interface)): %} | ||||
| add_list firewall.@zone[-1].network={{ s(network) }} | ||||
| {% endfor %} | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-Ping' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].proto='icmp' | ||||
| set firewall.@rule[-1].icmp_type='echo-request' | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-IGMP' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].proto='igmp' | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
|  | ||||
| {% if (ipv4_mode || !ipv6_mode): %} | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Support-UDP-Traceroute' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].dest_port='33434:33689' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| set firewall.@rule[-1].target='REJECT' | ||||
| set firewall.@rule[-1].enabled='false' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-DHCP-Renew' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].dest_port='68' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| {% endif %} | ||||
|  | ||||
| {%   if (ipv6_mode || !ipv4_mode): %} | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-DHCPv6' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].src_ip='fc00::/6' | ||||
| set firewall.@rule[-1].dest_ip='fc00::/6' | ||||
| set firewall.@rule[-1].dest_port='546' | ||||
| set firewall.@rule[-1].family='ipv6' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-MLD' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].proto='icmp' | ||||
| set firewall.@rule[-1].src_ip='fe80::/10' | ||||
| set firewall.@rule[-1].icmp_type='130/0' | ||||
| set firewall.@rule[-1].icmp_type='131/0' | ||||
| set firewall.@rule[-1].icmp_type='132/0' | ||||
| set firewall.@rule[-1].icmp_type='143/0' | ||||
| set firewall.@rule[-1].family='ipv6' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-ICMPv6-Input' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].proto='icmp' | ||||
| add_list firewall.@rule[-1].icmp_type='echo-request' | ||||
| add_list firewall.@rule[-1].icmp_type='echo-reply' | ||||
| add_list firewall.@rule[-1].icmp_type='destination-unreachable' | ||||
| add_list firewall.@rule[-1].icmp_type='packet-too-big' | ||||
| add_list firewall.@rule[-1].icmp_type='time-exceeded' | ||||
| add_list firewall.@rule[-1].icmp_type='bad-header' | ||||
| add_list firewall.@rule[-1].icmp_type='unknown-header-type' | ||||
| add_list firewall.@rule[-1].icmp_type='router-solicitation' | ||||
| add_list firewall.@rule[-1].icmp_type='neighbour-solicitation' | ||||
| add_list firewall.@rule[-1].icmp_type='router-advertisement' | ||||
| add_list firewall.@rule[-1].icmp_type='neighbour-advertisement' | ||||
| set firewall.@rule[-1].limit='1000/sec' | ||||
| set firewall.@rule[-1].family='ipv6' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-ICMPv6-Forward' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].dest='*' | ||||
| set firewall.@rule[-1].proto='icmp' | ||||
| add_list firewall.@rule[-1].icmp_type='echo-request' | ||||
| add_list firewall.@rule[-1].icmp_type='echo-reply' | ||||
| add_list firewall.@rule[-1].icmp_type='destination-unreachable' | ||||
| add_list firewall.@rule[-1].icmp_type='packet-too-big' | ||||
| add_list firewall.@rule[-1].icmp_type='time-exceeded' | ||||
| add_list firewall.@rule[-1].icmp_type='bad-header' | ||||
| add_list firewall.@rule[-1].icmp_type='unknown-header-type' | ||||
| set firewall.@rule[-1].limit='1000/sec' | ||||
| set firewall.@rule[-1].family='ipv6' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| {% endif %} | ||||
|  | ||||
| {% if (interface.role == "downstream"): %} | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-DNS-{{ name }}' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].dest_port='53' | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| add_list firewall.@rule[-1].proto='tcp' | ||||
| add_list firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-DHCP-{{ name }}' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].dest_port=67 | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-DHCPv6-{{ name }}' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].dest_port=547 | ||||
| set firewall.@rule[-1].family='ipv6' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| {% endif %} | ||||
|  | ||||
| {% | ||||
| 	for (let forward in interface.ipv4?.port_forward) | ||||
| 		include('firewall/forward.uc', { | ||||
| 			forward, | ||||
| 			family: 'ipv4', | ||||
| 			source_zone: ethernet.find_interface('upstream', interface.vlan?.id), | ||||
| 			destination_zone: name, | ||||
| 			destination_subnet: interface.ipv4.subnet | ||||
| 		}); | ||||
|  | ||||
| 	for (let forward in interface.ipv6?.port_forward) | ||||
| 		include('firewall/forward.uc', { | ||||
| 			forward, | ||||
| 			family: 'ipv6', | ||||
| 			source_zone: ethernet.find_interface('upstream', interface.vlan?.id), | ||||
| 			destination_zone: name, | ||||
| 			destination_subnet: interface.ipv6.subnet | ||||
| 		}); | ||||
|  | ||||
| 	for (let allow in interface.ipv6?.traffic_allow) | ||||
| 		include('firewall/allow.uc', { | ||||
| 			allow, | ||||
| 			family: 'ipv6', | ||||
| 			source_zone: ethernet.find_interface('upstream', interface.vlan?.id), | ||||
| 			destination_zone: name, | ||||
| 			destination_subnet: interface.ipv6.subnet | ||||
| 		}); | ||||
| %} | ||||
| @@ -1,20 +0,0 @@ | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow traffic to {{ allow.destination_address }}' | ||||
| set firewall.@rule[-1].family={{ s(family) }} | ||||
| set firewall.@rule[-1].src={{ s(source_zone || '*') }} | ||||
| set firewall.@rule[-1].dest={{ s(destination_zone) }} | ||||
| {%  for (let proto in ((allow.protocol in ['any', 'all', '*'] && (allow.source_ports || allow.destination_ports)) ? ['tcp', 'udp'] : [ allow.protocol ])): %} | ||||
| add_list firewall.@rule[-1].proto={{ s(proto) }} | ||||
| {%  endfor %} | ||||
| {%  if (allow.source_address): %} | ||||
| set firewall.@rule[-1].src_ip={{ s(allow.source_address) }} | ||||
| {%  endif %} | ||||
| {%  for (let sport in allow.source_ports): %} | ||||
| add_list firewall.@rule[-1].src_port={{ s(sport) }} | ||||
| {%  endfor %} | ||||
| set firewall.@rule[-1].dest_ip={{ ipcalc.expand_wildcard_address(allow.destination_address, destination_subnet) }} | ||||
| {%  for (let dport in allow.destination_ports): %} | ||||
| add_list firewall.@rule[-1].dest_port={{ s(dport) }} | ||||
| {%  endfor %} | ||||
| set firewall.@rule[-1].target=ACCEPT | ||||
|  | ||||
| @@ -1,15 +0,0 @@ | ||||
| {% if (true || source_zone): %} | ||||
| add firewall redirect | ||||
| set firewall.@redirect[-1].name='Forward port {{ forward.external_port }} to {{ forward.internal_address }}' | ||||
| set firewall.@redirect[-1].family={{ s(family) }} | ||||
| set firewall.@redirect[-1].src={{ s(source_zone || '*') }} | ||||
| set firewall.@redirect[-1].dest={{ s(destination_zone) }} | ||||
| {%  for (let proto in ((forward.protocol in ['any', 'all', '*']) ? ['tcp', 'udp'] : [ forward.protocol ])): %} | ||||
| add_list firewall.@redirect[-1].proto={{ s(proto) }} | ||||
| {%  endfor %} | ||||
| set firewall.@redirect[-1].src_dport={{ s(forward.external_port) }} | ||||
| set firewall.@redirect[-1].dest_ip={{ ipcalc.expand_wildcard_address(forward.internal_address, destination_subnet) }} | ||||
| set firewall.@redirect[-1].dest_port={{ s(forward.internal_port) }} | ||||
| set firewall.@redirect[-1].target=DNAT | ||||
|  | ||||
| {% endif %} | ||||
| @@ -1,45 +0,0 @@ | ||||
| {% | ||||
| if (!interface.tunnel.peer_address) { | ||||
|         warn("A GRE tunnel requires a valid peer-address"); | ||||
|         return; | ||||
| } | ||||
| %} | ||||
|  | ||||
| # GRE Configuration | ||||
| set network.gre=interface | ||||
| set network.gre.proto='gretap' | ||||
| set network.gre.peeraddr='{{ interface.tunnel.peer_address }}' | ||||
| set network.gre.nohostroute='1' | ||||
| set network.gre.df='{{ b(interface.tunnel.dont_fragment) }}' | ||||
|  | ||||
| {% | ||||
| let suffix = ''; | ||||
| let cfg = { | ||||
| 	name: 'gretun', | ||||
| 	netdev: 'gre4t-gre', | ||||
| 	ipv4_mode, ipv4: interface.ipv4 || {}, | ||||
| 	ipv6_mode, ipv6: interface.ipv6 || {} | ||||
| }; | ||||
|  | ||||
| if (ethernet.has_vlan(interface)) { | ||||
| 	cfg.name = 'gretun_' + interface.vlan.id; | ||||
| 	cfg.netdev = 'gre4t-gre.' + interface.vlan.id; | ||||
| 	cfg.this_vid = interface.vlan.id; | ||||
| 	suffix = '.' + interface.vlan.id; | ||||
| } | ||||
|  | ||||
| include("common.uc", cfg); | ||||
| %} | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].family='ipv4' | ||||
| set firewall.@rule[-1].proto='47' | ||||
| set firewall.@rule[-1].name='Allow-GRE-{{ name }}' | ||||
|  | ||||
| add network device | ||||
| set network.@device[-1].name={{ s(name) }} | ||||
| set network.@device[-1].type='bridge' | ||||
| set network.@device[-1].ports='gre4t-gre{{ suffix }}' | ||||
| set network.@device[-1].dhcp_healthcheck='{{ b(interface.tunnel.dhcp_healthcheck) }}' | ||||
| @@ -1,44 +0,0 @@ | ||||
| {% | ||||
| if (!interface.tunnel.peer_address) { | ||||
|         warn("A GRE tunnel requires a valid peer-address"); | ||||
|         return; | ||||
| } | ||||
| %} | ||||
|  | ||||
| # GRE Configuration | ||||
| set network.greip6=interface | ||||
| set network.greip6.proto='grev6tap' | ||||
| set network.greip6.peer6addr='{{ interface.tunnel.peer_address }}' | ||||
| set network.greip6.nohostroute='1' | ||||
|  | ||||
| {% | ||||
| let suffix = ''; | ||||
| let cfg = { | ||||
| 	name: 'gretun6', | ||||
| 	netdev: 'gre6t-greip6', | ||||
| 	ipv4_mode, ipv4: interface.ipv4 || {}, | ||||
| 	ipv6_mode, ipv6: interface.ipv6 || {} | ||||
| }; | ||||
|  | ||||
| if (ethernet.has_vlan(interface)) { | ||||
| 	cfg.name = 'gretun6_' + interface.vlan.id; | ||||
| 	cfg.netdev = 'gre6t-greip6.' + interface.vlan.id; | ||||
| 	cfg.this_vid = interface.vlan.id; | ||||
| 	suffix = '.' + interface.vlan.id; | ||||
| } | ||||
|  | ||||
| include("common.uc", cfg); | ||||
| %} | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].src={{ s(name) }} | ||||
| set firewall.@rule[-1].family='ipv6' | ||||
| set firewall.@rule[-1].proto='47' | ||||
| set firewall.@rule[-1].name='Allow-GREv6-{{ name }}' | ||||
|  | ||||
| add network device | ||||
| set network.@device[-1].name={{ s(name) }} | ||||
| set network.@device[-1].type='bridge' | ||||
| set network.@device[-1].ports='gre6t-greip6{{ suffix }}' | ||||
| set network.@device[-1].dhcp_healthcheck='{{ b(interface.tunnel.dhcp_healthcheck) }}' | ||||
| @@ -1,12 +0,0 @@ | ||||
| {% if (interface.role == 'upstream' && ethernet.has_vlan(interface)): %} | ||||
| set network.{{ name }}.ip4table={{ routing_table.get(this_vid) }} | ||||
| {% endif %} | ||||
| {% if (ipv4_mode == 'static'): %} | ||||
| set network.{{ name }}.ipaddr={{ ipv4.subnet }} | ||||
| set network.{{ name }}.gateway={{ ipv4.gateway }} | ||||
| {% else %} | ||||
| set network.{{ name }}.peerdns={{ b(!length(ipv4.use_dns)) }} | ||||
| {% endif %} | ||||
| {%  for (let dns in ipv4.use_dns): %} | ||||
| add_list network.{{ name }}.dns={{ dns }} | ||||
| {%  endfor %} | ||||
| @@ -1,10 +0,0 @@ | ||||
| {% if (interface.role == 'upstream' && ethernet.has_vlan(interface)): %} | ||||
| set network.{{ name }}.ip6table={{ routing_table.get(this_vid) }} | ||||
| {% endif %} | ||||
| {% if (ipv6_mode == 'static'): %} | ||||
| set network.{{ name }}.ip6addr={{ ipv6.subnet }} | ||||
| set network.{{ name }}.ip6gw={{ ipv6.gateway }} | ||||
| set network.{{ name }}.ip6assign={{ ipv6.prefix_size || '64' }} | ||||
| {% else %} | ||||
| set network.{{ name }}.reqprefix={{ ipv6.prefix_size || 'auto' }} | ||||
| {% endif %} | ||||
| @@ -1,31 +0,0 @@ | ||||
| {% | ||||
| if (!interface.tunnel.server || !interface.tunnel.user_name || !interface.tunnel.password ) { | ||||
|         warn("A L2TP tunnel can only be created with a server, username and password"); | ||||
|         return; | ||||
| } | ||||
| %} | ||||
|  | ||||
| # L2TP Configuration | ||||
| set network.l2tp="interface" | ||||
| set network.l2tp.proto="l2tp" | ||||
| set network.l2tp.server={{ s(interface.tunnel.server) }} | ||||
| set network.l2tp.username={{ s(interface.tunnel.user_name) }} | ||||
| set network.l2tp.password={{ s(interface.tunnel.password) }} | ||||
| set network.l2tp.ip4table="{{ routing_table.get(this_vid) }}" | ||||
|  | ||||
| add firewall zone | ||||
| set firewall.@zone[-1].name="l2tp" | ||||
| set firewall.@zone[-1].network="l2tp" | ||||
| set firewall.@zone[-1].input="REJECT" | ||||
| set firewall.@zone[-1].forward="REJECT" | ||||
| set firewall.@zone[-1].output="REJECT" | ||||
| set firewall.@zone[-1].masq="1" | ||||
| set firewall.@zone[-1].mtu_fix="1" | ||||
|  | ||||
| add firewall forwarding | ||||
| set firewall.@forwarding[-1].src="{{ name }}" | ||||
| set firewall.@forwarding[-1].dest="l2tp" | ||||
|  | ||||
| add network rule | ||||
| set network.@rule[-1].in="{{ name }}" | ||||
| set network.@rule[-1].lookup="{{ routing_table.get(this_vid) }}" | ||||
| @@ -1,17 +0,0 @@ | ||||
|  | ||||
| set network.batman=interface | ||||
| set network.batman.proto=batadv | ||||
| set network.batman.multicast_mode=0 | ||||
| set network.batman.distributed_arp_table=0 | ||||
| set network.batman.orig_interval=5000 | ||||
|  | ||||
| {% if (ethernet.has_vlan(interface)): %} | ||||
| set network.batman_v{{ this_vid }}=interface | ||||
| set network.batman_v{{ this_vid }}.proto=batadv_vlan | ||||
| set network.batman_v{{ this_vid }}.ifname='batman.{{ this_vid }}' | ||||
| {% else %} | ||||
| set network.batman_mesh=interface | ||||
| set network.batman_mesh.proto=batadv_hardif | ||||
| set network.batman_mesh.master=batman | ||||
| set network.batman_mesh.mtu=1532 | ||||
| {% endif %} | ||||
| @@ -1,616 +0,0 @@ | ||||
| {% | ||||
| 	let purpose = { | ||||
| 		"onboarding-ap": { | ||||
| 			"name": "OpenWifi-onboarding", | ||||
| 			"isolate_clients": true, | ||||
| 			"hidden": true, | ||||
| 			"wifi_bands": [ | ||||
| 				"2G" | ||||
| 			], | ||||
| 			"bss_mode": "ap", | ||||
| 			"encryption": { | ||||
| 				"proto": "wpa2", | ||||
| 				"ieee80211w": "required" | ||||
| 			}, | ||||
| 			"certificates": { | ||||
| 				"use_local_certificates": true | ||||
| 			}, | ||||
| 			"radius": { | ||||
| 				"local": { | ||||
| 					"server-identity": "uCentral-EAP" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"onboarding-sta": { | ||||
| 			"name": "OpenWifi-onboarding", | ||||
| 			"wifi_bands": [ | ||||
| 				"2G" | ||||
| 			], | ||||
| 			"bss_mode": "sta", | ||||
| 			"encryption": { | ||||
| 				"proto": "wpa2", | ||||
| 				"ieee80211w": "required" | ||||
| 			}, | ||||
| 			"certificates": { | ||||
| 				"use_local_certificates": true | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	if (purpose[ssid.purpose]) | ||||
| 		ssid = purpose[ssid.purpose]; | ||||
|  | ||||
| 	let phys = []; | ||||
|  | ||||
| 	for (let band in ssid.wifi_bands) | ||||
| 		for (let phy in wiphy.lookup_by_band(band)) | ||||
| 			if (phy.section) | ||||
| 				push(phys, phy); | ||||
|  | ||||
| 	if (!length(phys)) { | ||||
| 		warn("Can't find any suitable radio phy for SSID '%s' settings", ssid.name); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (type(ssid.roaming) == 'bool') | ||||
| 		ssid.roaming = { | ||||
| 			message_exchange: true | ||||
| 		}; | ||||
|  | ||||
| 	if (ssid.roaming && ssid.encryption.proto in [ "wpa", "psk", "none" ]) { | ||||
| 		delete ssid.roaming; | ||||
| 		warn("Roaming requires wpa2 or later"); | ||||
| 	} | ||||
|  | ||||
| 	let certificates = ssid.certificates || {}; | ||||
| 	if (certificates.use_local_certificates) { | ||||
| 		cursor.load("system"); | ||||
| 		let certs = cursor.get_all("system", "@certificates[-1]"); | ||||
| 		certificates.ca_certificate = certs.ca; | ||||
| 		certificates.certificate = certs.cert; | ||||
| 		certificates.private_key = certs.key; | ||||
| 	} | ||||
|  | ||||
| 	if (ssid.radius?.dynamic_authorization && 'radius-gw-proxy' in ssid.services) { | ||||
| 		ssid.radius.dynamic_authorization.host = '127.0.0.1'; | ||||
| 		ssid.radius.dynamic_authorization.port = 3799; | ||||
| 	} | ||||
|  | ||||
| 	function validate_encryption_ap() { | ||||
| 		if (ssid.encryption.proto in [ "wpa", "wpa2", "wpa-mixed", "wpa3", "wpa3-mixed", "wpa3-192", "psk2-radius" ] && | ||||
| 		    ssid.radius && ssid.radius.local && | ||||
| 		    length(certificates)) | ||||
| 			return { | ||||
| 				proto: ssid.encryption.proto, | ||||
| 				eap_local: ssid.radius.local, | ||||
| 				eap_user: "/tmp/ucentral/" + replace(location, "/", "_") + ".eap_user" | ||||
| 			}; | ||||
|  | ||||
|  | ||||
| 		if (ssid.encryption.proto in [ "wpa", "wpa2", "wpa-mixed", "wpa3", "wpa3-mixed", "wpa3-192", "psk2-radius" ] && | ||||
| 		    ssid.radius && ssid.radius.authentication && | ||||
| 		    ssid.radius.authentication.host && | ||||
| 		    ssid.radius.authentication.port && | ||||
| 		    ssid.radius.authentication.secret) | ||||
| 			return { | ||||
| 				proto: ssid.encryption.proto, | ||||
| 				auth: ssid.radius.authentication, | ||||
| 				acct: ssid.radius.accounting, | ||||
| 				health: ssid.radius.health || {}, | ||||
| 				dyn_auth: ssid.radius?.dynamic_authorization, | ||||
| 				radius: ssid.radius | ||||
| 			}; | ||||
| 		warn("Can't find any valid encryption settings"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	function validate_encryption_sta() { | ||||
| 		if (ssid.encryption.proto in [ "wpa", "wpa2", "wpa-mixed", "wpa3", "wpa3-mixed", "wpa3-192" ] && | ||||
| 		    length(certificates)) | ||||
| 			return { | ||||
| 				proto: ssid.encryption.proto, | ||||
| 				client_tls: certificates | ||||
| 			}; | ||||
| 		warn("Can't find any valid encryption settings"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	function validate_encryption(phy) { | ||||
| 		if ('6G' in phy.band && !(ssid?.encryption.proto in [ "wpa3", "wpa3-mixed", "wpa3-192", "sae", "sae-mixed", "owe" ])) { | ||||
| 			warn("Invalid encryption settings for 6G band"); | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		if (!ssid.encryption || ssid.encryption.proto in [ "none" ]) { | ||||
| 			if (ssid.radius?.authentication?.mac_filter && | ||||
| 			    ssid.radius.authentication?.host && | ||||
| 			    ssid.radius.authentication?.port && | ||||
| 			    ssid.radius.authentication?.secret) | ||||
| 				return { | ||||
| 					proto: 'none', | ||||
| 					auth: ssid.radius.authentication, | ||||
| 					acct: ssid.radius.accounting, | ||||
| 					dyn_auth: ssid.radius?.dynamic_authorization, | ||||
| 					health: ssid.radius.health || {}, | ||||
| 					radius: ssid.radius | ||||
| 				}; | ||||
| 			return { | ||||
| 				proto: 'none', | ||||
| 				dyn_auth: ssid.radius?.dynamic_authorization, | ||||
| 			}; | ||||
| 		} | ||||
|  | ||||
| 		if (ssid?.encryption?.proto in [ "owe", "owe-transition" ]) | ||||
| 			return { | ||||
| 				proto: 'owe' | ||||
| 			}; | ||||
|  | ||||
| 		if (ssid.encryption.proto in [ "psk", "psk2", "psk-mixed", "sae", "sae-mixed" ] && | ||||
| 		    ssid.encryption.key) { | ||||
| 			if (ssid.radius?.authentication?.mac_filter && | ||||
| 			    ssid.radius.authentication?.host && | ||||
| 			    ssid.radius.authentication?.port && | ||||
| 			    ssid.radius.authentication?.secret) | ||||
| 				return { | ||||
| 					proto: ssid.encryption.proto, | ||||
| 					key: ssid.encryption.key, | ||||
| 					auth: ssid.radius.authentication, | ||||
| 					acct: ssid.radius.accounting, | ||||
| 					dyn_auth: ssid.radius?.dynamic_authorization, | ||||
| 					health: ssid.radius.health || {}, | ||||
| 					radius: ssid.radius | ||||
| 				}; | ||||
|  | ||||
| 			return { | ||||
| 				proto: ssid.encryption.proto, | ||||
| 				key: ssid.encryption.key, | ||||
| 				dyn_auth: ssid.radius?.dynamic_authorization, | ||||
| 			}; | ||||
| 		}; | ||||
|  | ||||
| 		switch(ssid.bss_mode) { | ||||
| 		case 'ap': | ||||
| 		case 'wds-ap': | ||||
| 			return validate_encryption_ap(); | ||||
|  | ||||
| 		case 'sta': | ||||
| 		case 'wds-sta': | ||||
| 			return validate_encryption_sta(); | ||||
|  | ||||
| 		} | ||||
| 		warn("Can't find any valid encryption settings"); | ||||
| 	} | ||||
|  | ||||
| 	function match_ieee80211w(phy) { | ||||
| 		if ('6G' in phy.band) | ||||
| 			return 2; | ||||
|  | ||||
| 		if (!ssid.encryption) | ||||
| 			return 0; | ||||
|  | ||||
| 		if (ssid.encryption.proto in [ "sae-mixed", "wpa3-mixed" ]) | ||||
| 			return 1; | ||||
|  | ||||
| 		if (ssid.encryption.proto in [ "sae", "wpa3", "wpa3-192" ]) | ||||
| 			return 2; | ||||
|  | ||||
| 		return index([ "disabled", "optional", "required" ], ssid.encryption.ieee80211w); | ||||
| 	} | ||||
|  | ||||
| 	function match_sae_pwe(phy) { | ||||
| 		if ('6G' in phy.band) | ||||
| 			return 1; | ||||
| 		return ''; | ||||
| 	} | ||||
|  | ||||
| 	function match_wds() { | ||||
| 		return index([ "wds-ap", "wds-sta", "wds-repeater" ], ssid.bss_mode) >= 0; | ||||
| 	} | ||||
|  | ||||
| 	function match_hs20_auth_type(auth_type) { | ||||
| 		let types = { | ||||
| 			"terms-and-conditions": "00", | ||||
| 			"online-enrollment": "01", | ||||
| 			"http-redirection": "02", | ||||
| 			"dns-redirection": "03" | ||||
| 		}; | ||||
| 		return (auth_type && auth_type.type) ? types[auth_type.type] : ''; | ||||
| 	} | ||||
|  | ||||
| 	function get_hs20_wan_metrics() { | ||||
| 		if (!ssid.pass_point.wan_metrics || | ||||
| 		    !ssid.pass_point.wan_metrics.info || | ||||
| 		    !ssid.pass_point.wan_metrics.downlink || | ||||
| 		    ! ssid.pass_point.wan_metrics.uplink) | ||||
| 			return ''; | ||||
| 		let map = {"up": 1, "down": 2, "testing": 3}; | ||||
| 		let info = map[ssid.pass_point.wan_metrics.info] ? map[ssid.pass_point.wan_metrics.info] : 1; | ||||
| 		return sprintf("%02d:%d:%d:0:0:0", info, ssid.pass_point.wan_metrics.downlink, ssid.pass_point.wan_metrics.uplink); | ||||
| 	} | ||||
|  | ||||
| 	let bss_mode = ssid.bss_mode; | ||||
| 	if (ssid.bss_mode == "wds-ap") | ||||
| 		bss_mode =  "ap"; | ||||
| 	if (ssid.bss_mode == "wds-sta") | ||||
| 		bss_mode =  "sta"; | ||||
|  | ||||
| 	function radius_vendor_tlv(server, port) { | ||||
| 		let radius_serial = replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1-$2-$3-$4-$5-$6"); | ||||
| 		let radius_serial_len = length(radius_serial) + 2; | ||||
| 		let radius_vendor = "26:x:0000e608" + // vendor element | ||||
| 			"0113" + replace(radius_serial, /./g, (m) => sprintf("%02x", ord(m))); | ||||
|  | ||||
| 		let radius_ip = sprintf("%s:%s", server, port); | ||||
| 		let radius_ip_len = length(radius_ip) + 2; | ||||
| 		radius_vendor += "02" + sprintf("%02x", radius_ip_len) + replace(radius_ip, /./g, (m) => sprintf("%02x", ord(m))); | ||||
| 		return radius_vendor; | ||||
| 	} | ||||
|  | ||||
| 	function radius_proxy_tlv(server, port, name) { | ||||
| 		let tlv = "33:x:" + | ||||
| 			replace(replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1$2$3$4$5$6") + sprintf(":%s:%s:%s", server, port, name), | ||||
| 				/./g, (m) => sprintf("%02x", ord(m))); | ||||
| 		return tlv; | ||||
| 	} | ||||
|  | ||||
| 	function radius_request_attribute(request) { | ||||
| 		if (request.id && request.hex_value) | ||||
| 			return sprintf('%d:x:%s', request.id, request.hex_value); | ||||
| 		if (request.id && type(request.value) == 'string') | ||||
| 			return sprintf('%d:s:%s', request.id, request.value); | ||||
| 		if (request.id && type(request.value) == 'int') | ||||
| 			return sprintf('%d:d:%d', request.id, request.value); | ||||
| 		if (request.vendor_id && request.vendor_attributes) { | ||||
| 			let tlv = sprintf('26:x:%04x', request.vendor_id); | ||||
| 			for (let vsa in request.vendor_attributes) | ||||
| 				tlv += sprintf('%02x%02x', vsa.type, length(vsa.value)) + vsa.id; | ||||
| 			return tlv; | ||||
| 		} | ||||
| 		return ''; | ||||
| 	} | ||||
|  | ||||
| 	function calculate_ifname(name) { | ||||
| 		if ('captive' in ssid.services) | ||||
| 			return 'wlanc' + captive.get(name); | ||||
| 		return ''; | ||||
| 	} | ||||
|  | ||||
| 	let radius_gw_proxy = ssid.services && (index(ssid.services, "radius-gw-proxy") >= 0); | ||||
|  | ||||
| 	if ('captive' in ssid.services && !ssid.captive) | ||||
| 		ssid.captive = state?.services?.captive || {}; | ||||
|  | ||||
| 	if (ssid.captive) | ||||
| 		include("captive.uc", { | ||||
| 			section: name + '_' + count, | ||||
| 			config: ssid.captive | ||||
| 		}); | ||||
| 	if (ssid.strict_forwarding) | ||||
| 		services.set_enabled("bridger", 'early'); | ||||
|  | ||||
| 	ssid.vendor_elements ??= ''; | ||||
|  | ||||
| 	if (ssid.tip_information_element) { | ||||
| 		if (state.unit?.beacon_advertisement) { | ||||
| 			if (state.unit.beacon_advertisement.device_serial) | ||||
| 				ssid.vendor_elements += 'dd1048d01701' + replace(serial, /./g, (m) => sprintf("%02x", ord(m))); | ||||
| 			if (state.unit.beacon_advertisement.device_name && state.unit.name) | ||||
| 				ssid.vendor_elements += 'dd' + sprintf('%02x', 4 + length(state.unit.name)) + '48d01702' + replace(state.unit.name, /./g, (m) => sprintf("%02x", ord(m))); | ||||
| 			if (state.unit.beacon_advertisement.network_id) { | ||||
| 				let id = sprintf('%d', state.unit.beacon_advertisement.network_id); | ||||
| 				ssid.vendor_elements += 'dd' + sprintf('%02x', 4 + length(id)) + '48d01703' + replace(id, /./g, (m) => sprintf("%02x", ord(m)));                 | ||||
| 			} | ||||
| 		} else { | ||||
| 			ssid.vendor_elements += 'dd0448d01700'; | ||||
| 		} | ||||
| 	} | ||||
| %} | ||||
|  | ||||
| # Wireless configuration | ||||
| {% for (let n, phy in phys): %} | ||||
| {%   let basename = name + '_' + count; %} | ||||
| {%   let ssidname = basename + '_' + n + '_' + count; %} | ||||
| {%   let section = (owe ? 'o' : '' ) + ssidname; %} | ||||
| {%   let id = wiphy.allocate_ssid_section_id(phy) %} | ||||
| {%   let crypto = validate_encryption(phy); %} | ||||
| {%   let ifname = calculate_ifname(basename) %} | ||||
| {%   if (!crypto) continue; %} | ||||
| set wireless.{{ section }}=wifi-iface | ||||
| set wireless.{{ section }}.ucentral_path={{ s(location) }} | ||||
| set wireless.{{ section }}.uci_section={{ s(section) }} | ||||
| set wireless.{{ section }}.device={{ phy.section }} | ||||
| {%   if ('captive' in ssid.services): %} | ||||
| set wireless.{{ section }}.ifname={{ s(ifname) }} | ||||
| add_list uspot.{{ basename}}.ifname={{ ifname }} | ||||
| add_list bridger.@defaults[0].blacklist={{ ifname }} | ||||
| {%   endif %} | ||||
| {%   if (ssid?.encryption?.proto == 'owe-transition'): %} | ||||
| {%      ssid.hidden_ssid = 1 %} | ||||
| {%      ssid.name += '-OWE' %} | ||||
| set wireless.{{ section }}.ifname={{ s(section) }} | ||||
| set wireless.{{ section }}.owe_transition_ifname={{ s('o' + section) }} | ||||
| {%   endif %} | ||||
| {%   if (owe): %} | ||||
| set wireless.{{ section }}.ifname={{ s(section) }} | ||||
| set wireless.{{ section }}.owe_transition_ifname={{ s(ssidname) }} | ||||
| {%   endif %} | ||||
| {%   if (bss_mode == 'mesh'): %} | ||||
| set wireless.{{ section }}.mode={{ bss_mode }} | ||||
| set wireless.{{ section }}.mesh_id={{ s(ssid.name) }} | ||||
| set wireless.{{ section }}.mesh_fwding=0 | ||||
| set wireless.{{ section }}.network=batman_mesh | ||||
| set wireless.{{ section }}.mcast_rate=24000 | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (index([ 'ap', 'sta' ], bss_mode) >= 0): %} | ||||
| set wireless.{{ section }}.network={{ network }} | ||||
| set wireless.{{ section }}.ssid={{ s(ssid.name) }} | ||||
| set wireless.{{ section }}.mode={{ s(bss_mode) }} | ||||
| set wireless.{{ section }}.bssid={{ ssid.bssid }} | ||||
| set wireless.{{ section }}.wds='{{ b(match_wds()) }}' | ||||
| set wireless.{{ section }}.wpa_disable_eapol_key_retries='{{ b(ssid.wpa_disable_eapol_key_retries) }}' | ||||
| set wireless.{{ section }}.vendor_elements='{{ ssid.vendor_elements }}' | ||||
| set wireless.{{ section }}.disassoc_low_ack='{{ b(ssid.disassoc_low_ack) }}' | ||||
| set wireless.{{ section }}.auth_cache='{{ b(ssid.encryption?.key_caching) }}' | ||||
| {%   endif %} | ||||
|  | ||||
| {% if ('6G' in phy.band): %} | ||||
| set wireless.{{ section }}.fils_discovery_max_interval={{ ssid.fils_discovery_interval }} | ||||
| {%   endif %} | ||||
|  | ||||
| # Crypto settings | ||||
| set wireless.{{ section }}.ieee80211w={{ match_ieee80211w(phy) }} | ||||
| set wireless.{{ section }}.sae_pwe={{ match_sae_pwe(phy) }} | ||||
| set wireless.{{ section }}.encryption={{ crypto.proto }} | ||||
| set wireless.{{ section }}.key={{ s(crypto.key) }} | ||||
|  | ||||
| {%   if (crypto.eap_local): %} | ||||
| set wireless.{{ section }}.eap_server=1 | ||||
| set wireless.{{ section }}.ca_cert={{ s(certificates.ca_certificate) }} | ||||
| set wireless.{{ section }}.server_cert={{ s(certificates.certificate) }} | ||||
| set wireless.{{ section }}.private_key={{ s(certificates.private_key) }} | ||||
| set wireless.{{ section }}.private_key_passwd={{ s(certificates.private_key_password) }} | ||||
| set wireless.{{ section }}.server_id={{ s(crypto.eap_local.server_identity) }} | ||||
| set wireless.{{ section }}.eap_user_file={{ s(crypto.eap_user) }} | ||||
| {%     files.add_named(crypto.eap_user, render("../eap_users.uc", { users: crypto.eap_local.users })) %} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (crypto.auth): %} | ||||
| {%     if (radius_gw_proxy): %} | ||||
| set wireless.{{ section }}.radius_gw_proxy=1 | ||||
| {%     endif %} | ||||
| set wireless.{{ section }}.auth_server={{ radius_gw_proxy ? '127.0.0.1' : crypto.auth.host }} | ||||
| set wireless.{{ section }}.auth_port={{ radius_gw_proxy ? 1812 : crypto.auth.port }} | ||||
| set wireless.{{ section }}.auth_secret={{ crypto.auth.secret }} | ||||
| {%     for (let request in crypto.auth.request_attribute): %} | ||||
| add_list wireless.{{ section }}.radius_auth_req_attr={{ s(radius_request_attribute(request)) }} | ||||
| {%     endfor %} | ||||
| {%     if (radius_gw_proxy): %} | ||||
| add_list wireless.{{ section }}.radius_auth_req_attr={{ s(radius_proxy_tlv(crypto.auth.host, crypto.auth.port, name + '_' + n + '_' + count)) }} | ||||
| {%     else %} | ||||
| add_list wireless.{{ section }}.radius_auth_req_attr={{ s(radius_vendor_tlv(crypto.auth.host, crypto.auth.port)) }} | ||||
| {%     endif %} | ||||
| {%     if (crypto.auth.secondary): %} | ||||
| set wireless.{{ section }}.auth_server_secondary={{ crypto.auth.secondary.host }} | ||||
| set wireless.{{ section }}.auth_port_secondary={{ crypto.auth.secondary.port }} | ||||
| set wireless.{{ section }}.auth_secret_secondary={{ crypto.auth.secondary.secret }} | ||||
| {%     endif %} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (crypto.acct): %} | ||||
| set wireless.{{ section }}.acct_server={{ radius_gw_proxy ? '127.0.0.1' : crypto.acct.host }} | ||||
| set wireless.{{ section }}.acct_port={{ radius_gw_proxy ? 1813 : crypto.acct.port }} | ||||
| set wireless.{{ section }}.acct_secret={{ crypto.acct.secret }} | ||||
| set wireless.{{ section }}.acct_interval={{ crypto.acct.interval }} | ||||
| {%     for (let request in crypto.acct.request_attribute): %} | ||||
| add_list wireless.{{ section }}.radius_acct_req_attr={{ s(radius_request_attribute(request)) }} | ||||
| {%     endfor %} | ||||
| {%     if (radius_gw_proxy): %} | ||||
| add_list wireless.{{ section }}.radius_acct_req_attr={{ s(radius_proxy_tlv(crypto.acct.host, crypto.acct.port, name + '_' + n + '_' + count)) }} | ||||
| {%     else %} | ||||
| add_list wireless.{{ section }}.radius_acct_req_attr={{ s(radius_vendor_tlv(crypto.acct.host, crypto.acct.port)) }} | ||||
| {%     endif %} | ||||
| {%     if (crypto.acct.secondary): %} | ||||
| set wireless.{{ section }}.acct_server_secondary={{ crypto.acct.secondary.host }} | ||||
| set wireless.{{ section }}.acct_port_secondary={{ crypto.acct.secondary.port }} | ||||
| set wireless.{{ section }}.acct_secret_secondary={{ crypto.acct.secondary.secret }} | ||||
| {%     endif %} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (crypto.health): %} | ||||
| set wireless.{{ section }}.health_username={{ s(crypto.health.username) }} | ||||
| set wireless.{{ section }}.health_password={{ s(crypto.health.password) }} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (crypto.dyn_auth): %} | ||||
| set wireless.{{ section }}.dae_client={{ crypto.dyn_auth.host }} | ||||
| set wireless.{{ section }}.dae_port={{ crypto.dyn_auth.port }} | ||||
| set wireless.{{ section }}.dae_secret={{ crypto.dyn_auth.secret }} | ||||
|  | ||||
| set firewall.dyn_auth=rule | ||||
| set firewall.dyn_auth.name='Allow-CoA' | ||||
| set firewall.dyn_auth.src='{{ s(ethernet.find_interface("upstream", 0)) }}' | ||||
| set firewall.dyn_auth.dest_port='{{ crypto.dyn_auth.port }}' | ||||
| set firewall.dyn_auth.proto='udp' | ||||
| set firewall.dyn_auth.target='ACCEPT' | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (crypto.radius): %} | ||||
| set wireless.{{ section }}.request_cui={{ b(crypto.radius.chargeable_user_id) }} | ||||
| set wireless.{{ section }}.nasid={{ s(crypto.radius.nas_identifier) }} | ||||
| set wireless.{{ section }}.dynamic_vlan=1 | ||||
| {%     if (crypto.radius?.authentication?.mac_filter): %} | ||||
| set wireless.{{ section }}.macfilter=radius | ||||
| {%     endif %} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (crypto.client_tls): %} | ||||
| set wireless.{{ section }}.eap_type='tls' | ||||
| set wireless.{{ section }}.ca_cert={{ s(certificates.ca_certificate) }} | ||||
| set wireless.{{ section }}.client_cert={{ s(certificates.certificate)}} | ||||
| set wireless.{{ section }}.priv_key={{ s(certificates.private_key) }} | ||||
| set wireless.{{ section }}.priv_key_pwd={{ s(certificates.private_key_password) }} | ||||
| set wireless.{{ section }}.identity='uCentral' | ||||
| {%   endif %} | ||||
|  | ||||
| {% if (interface.vlan_awareness?.first): %} | ||||
| {%   let vlan = interface.vlan_awareness.first; | ||||
|      if (interface.vlan_awareness.last) | ||||
| 	     vlan += '-' + interface.vlan_awareness.last; %} | ||||
| set wireless.{{ section }}.network_vlan={{ vlan }} | ||||
| {% endif %} | ||||
|  | ||||
|  | ||||
| # AP specific setings | ||||
| {%   if (bss_mode == 'ap'): %} | ||||
| set wireless.{{ section }}.proxy_arp={{ b(length(network) ? ssid.proxy_arp : false) }} | ||||
| set wireless.{{ section }}.hidden={{ b(ssid.hidden_ssid) }} | ||||
| set wireless.{{ section }}.time_advertisement={{ ssid.broadcast_time ? 2 : 0 }} | ||||
| set wireless.{{ section }}.isolate={{ b(ssid.isolate_clients) }} | ||||
| set wireless.{{ section }}.uapsd={{ b(ssid.power_save) }} | ||||
| set wireless.{{ section }}.rts_threshold={{ ssid.rts_threshold }} | ||||
| set wireless.{{ section }}.multicast_to_unicast={{ b(ssid.unicast_conversion) }} | ||||
| set wireless.{{ section }}.maxassoc={{ ssid.maximum_clients }} | ||||
| set wireless.{{ section }}.dtim_period={{ ssid.dtim_period }} | ||||
| set wireless.{{ section }}.strict_forwarding={{ b(ssid.strict_forwarding) }} | ||||
|  | ||||
| {%     if (interface?.vlan.id): %} | ||||
| set wireless.{{ section }}.vlan_id={{ interface.vlan.id }} | ||||
| {%     endif %} | ||||
|  | ||||
|  | ||||
| {%     if (ssid.rate_limit): %} | ||||
| set wireless.{{ section }}.ratelimit=1 | ||||
| {%     endif %} | ||||
|  | ||||
| {%     if (ssid.access_control_list?.mode): %} | ||||
| set wireless.{{ section }}.macfilter={{ s(ssid.access_control_list.mode) }} | ||||
| {%       for (let mac in ssid.access_control_list.mac_address): %} | ||||
| add_list wireless.{{ section }}.maclist={{ s(mac) }} | ||||
| {%       endfor %} | ||||
| {%     endif %} | ||||
|  | ||||
| {%     if (ssid.rrm): %} | ||||
| set wireless.{{ section }}.ieee80211k={{ b(ssid.rrm.neighbor_reporting) }} | ||||
| set wireless.{{ section }}.rnr={{ b(ssid.rrm.reduced_neighbor_reporting) }} | ||||
| set wireless.{{ section }}.ftm_responder={{ b(ssid.rrm.ftm_responder) }} | ||||
| set wireless.{{ section }}.stationary_ap={{ b(ssid.rrm.stationary_ap) }} | ||||
| set wireless.{{ section }}.lci={{ b(ssid.rrm.lci) }} | ||||
| set wireless.{{ section }}.civic={{ ssid.rrm.civic }} | ||||
| {%     endif %} | ||||
|  | ||||
| {%     if (ssid.roaming): %} | ||||
| set wireless.{{ section }}.ieee80211r=1 | ||||
| set wireless.{{ section }}.ft_over_ds={{ b(ssid.roaming.message_exchange == "ds") }} | ||||
| set wireless.{{ section }}.ft_psk_generate_local={{ b(ssid.roaming.generate_psk) }} | ||||
| set wireless.{{ section }}.mobility_domain={{ ssid.roaming.domain_identifier }} | ||||
| set wireless.{{ section }}.r0kh={{ s(ssid.roaming.pmk_r0_key_holder) }} | ||||
| set wireless.{{ section }}.r1kh={{ s(ssid.roaming.pmk_r1_key_holder) }} | ||||
| {%     endif %} | ||||
|  | ||||
| {%     if (ssid.quality_thresholds): %} | ||||
| set wireless.{{ phy.section }}.rssi_reject_assoc_rssi={{ ssid.quality_thresholds.association_request_rssi }} | ||||
| set wireless.{{ phy.section }}.rssi_ignore_probe_request={{ ssid.quality_thresholds.probe_request_rssi }} | ||||
| {%       if (ssid.quality_thresholds.probe_request_rssi): %} | ||||
| set wireless.{{ section }}.hidden=1 | ||||
| set wireless.{{ section }}.dynamic_probe_resp=1 | ||||
| {%       endif %} | ||||
| set usteer2.{{ section }}=ssid | ||||
| set usteer2.{{ section }}.client_kick_rssi={{ ssid.quality_thresholds.client_kick_rssi }} | ||||
| set usteer2.{{ section }}.client_kick_ban_time={{ ssid.quality_thresholds.client_kick_ban_time }} | ||||
| {%     endif %} | ||||
|  | ||||
| {%  for (let raw in ssid.hostapd_bss_raw): %} | ||||
| add_list wireless.{{ section }}.hostapd_bss_options={{ s(raw) }} | ||||
| {%  endfor %} | ||||
|  | ||||
| {%     if (ssid.pass_point): %} | ||||
| set wireless.{{ section }}.iw_enabled=1 | ||||
| set wireless.{{ section }}.hs20=1 | ||||
| {%       for (let name in ssid.pass_point.venue_name): %} | ||||
| add_list wireless.{{ section }}.iw_venue_name={{ s(name) }} | ||||
| {%       endfor %} | ||||
| set wireless.{{ section }}.iw_venue_group='{{ ssid.pass_point.venue_group }}' | ||||
| set wireless.{{ section }}.iw_venue_type='{{ ssid.pass_point.venue_type }}' | ||||
| {%       for (let n, url in ssid.pass_point.venue_url): %} | ||||
| add_list wireless.{{ section }}.iw_venue_url={{ s((n + 1) + ":" +url) }} | ||||
| {%       endfor %} | ||||
| set wireless.{{ section }}.iw_network_auth_type='{{ match_hs20_auth_type(ssid.pass_point.auth_type) }}' | ||||
| set wireless.{{ section }}.iw_domain_name={{ s(join(",", ssid.pass_point.domain_name)) }} | ||||
| {%       for (let realm in ssid.pass_point.nai_realm): %} | ||||
| add_list wireless.{{ section }}.iw_nai_realm='{{ realm }}' | ||||
| {%       endfor %} | ||||
| set wireless.{{ section }}.osen={{ b(ssid.pass_point.osen) }} | ||||
| set wireless.{{ section }}.anqp_domain_id='{{ ssid.pass_point.anqp_domain }}' | ||||
| {%       for (let cell_net in ssid.pass_point.anqp_3gpp_cell_net): %} | ||||
| add_list wireless.{{ section }}.iw_anqp_3gpp_cell_net='{{ s(cell_net) }}' | ||||
| {%       endfor %} | ||||
| {%       for (let name in ssid.pass_point.friendly_name): %} | ||||
| add_list wireless.{{ section }}.hs20_oper_friendly_name={{ s(name) }} | ||||
| {%       endfor %} | ||||
| set wireless.{{ section }}.iw_access_network_type='{{ ssid.pass_point.access_network_type }}' | ||||
| set wireless.{{ section }}.iw_internet={{ b(ssid.pass_point.internet) }} | ||||
| set wireless.{{ section }}.iw_asra={{ b(ssid.pass_point.asra) }} | ||||
| set wireless.{{ section }}.iw_esr={{ b(ssid.pass_point.esr) }} | ||||
| set wireless.{{ section }}.iw_uesa={{ b(ssid.pass_point.uesa) }} | ||||
| set wireless.{{ section }}.iw_hessid={{ s(ssid.pass_point.hessid) }} | ||||
| {%       for (let name in ssid.pass_point.roaming_consortium): %} | ||||
| add_list wireless.{{ section }}.iw_roaming_consortium={{ s(name) }} | ||||
| {%       endfor %} | ||||
| set wireless.{{ section }}.disable_dgaf={{ b(ssid.pass_point.disable_dgaf) }} | ||||
| set wireless.{{ section }}.hs20_release='3' | ||||
| set wireless.{{ section }}.iw_ipaddr_type_availability={{ s(sprintf("%02x", ssid.pass_point.ipaddr_type_availability)) }} | ||||
| {%       for (let name in ssid.pass_point.connection_capability): %} | ||||
| add_list wireless.{{ section }}.hs20_conn_capab={{ s(name) }} | ||||
| {%       endfor %} | ||||
| set wireless.{{ section }}.hs20_wan_metrics={{ s(get_hs20_wan_metrics()) }} | ||||
| {%     endif %} | ||||
|  | ||||
| {% include("wmm.uc", { section }); %} | ||||
|  | ||||
| {% if (length(ssid.multi_psk)): %} | ||||
| set wireless.{{ section }}.reassociation_deadline=3000 | ||||
| {% endif %} | ||||
|  | ||||
|  | ||||
| {%     if (ssid.pass_point): %} | ||||
| {%       for (let id, icon in ssid.pass_point.icons): %} | ||||
| add wireless hs20-icon | ||||
| set wireless.@hs20-icon[-1].width={{ s(icon.width) }} | ||||
| set wireless.@hs20-icon[-1].height={{ s(icon.height) }} | ||||
| set wireless.@hs20-icon[-1].type={{ s(icon.type) }} | ||||
| set wireless.@hs20-icon[-1].lang={{ s(icon.language) }} | ||||
| set wireless.@hs20-icon[-1].path={{ s(files.add_anonymous(location, 'hs20_icon_' + id, b64dec(icon.icon))) }} | ||||
| {%       endfor %} | ||||
|  | ||||
|  | ||||
|  | ||||
| {%     endif %} | ||||
|  | ||||
| add wireless wifi-vlan | ||||
| set wireless.@wifi-vlan[-1].iface={{ section }} | ||||
| set wireless.@wifi-vlan[-1].name='v#' | ||||
| set wireless.@wifi-vlan[-1].vid='*' | ||||
| {%     if (ssid.rate_limit && (ssid.rate_limit.ingress_rate || ssid.rate_limit.egress_rate)): %} | ||||
|  | ||||
| add ratelimit rate | ||||
| set ratelimit.@rate[-1].ssid={{ s(ssid.name) }} | ||||
| set ratelimit.@rate[-1].ingress={{ ssid.rate_limit.ingress_rate }} | ||||
| set ratelimit.@rate[-1].egress={{ ssid.rate_limit.egress_rate }} | ||||
| {%     endif %} | ||||
| {%     for (let i = length(ssid.multi_psk); i > 0; i--): %} | ||||
| {%       let psk = ssid.multi_psk[i - 1]; %} | ||||
| {%       if (!psk.key) continue %} | ||||
|  | ||||
| add wireless wifi-station | ||||
| set wireless.@wifi-station[-1].iface={{ s(section) }} | ||||
| set wireless.@wifi-station[-1].mac={{ psk.mac }} | ||||
| set wireless.@wifi-station[-1].key={{ psk.key }} | ||||
| set wireless.@wifi-station[-1].vid={{ psk.vlan_id }} | ||||
| {%     endfor %} | ||||
| {%   else %} | ||||
|  | ||||
| # STA specific settings | ||||
| {%   endif %} | ||||
| {% endfor %} | ||||
| @@ -1,35 +0,0 @@ | ||||
| {% | ||||
| if (!interface.ipv4 || !interface.ipv4.subnet || interface.ipv4.addressing != 'static' ) { | ||||
|         warn("A VXLAN tunnel can only be created with a valid and static ivp4 address"); | ||||
|         return; | ||||
| } | ||||
| if (!ethernet.has_vlan(interface)) { | ||||
|         warn("A VXLAN tunnel can only be created with a valid and static ivp4 address"); | ||||
|         return; | ||||
| } | ||||
| if (!interface.tunnel.peer_address) { | ||||
|         warn("A VXLAN tunnel requires a valid peer-address"); | ||||
|         return; | ||||
| } | ||||
| %} | ||||
|  | ||||
| # VXLAN Configuration | ||||
| set network.{{ name }}_vx=interface | ||||
| set network.{{ name }}_vx.proto=vxlan | ||||
| set network.{{ name }}_vx.peeraddr={{ s(interface.tunnel.peer_address) }} | ||||
| set network.{{ name }}_vx.port={{ interface.tunnel.peer_port }} | ||||
| set network.{{ name }}_vx.vid={{ interface.vlan.id }} | ||||
|  | ||||
| set network.{{ name }}=interface | ||||
| set network.{{ name }}.proto='static' | ||||
| set network.{{ name }}.ifname='@{{ name }}_vx' | ||||
| set network.{{ name }}.ipaddr={{ ipcalc.generate_prefix(state, interface.ipv4.subnet) }} | ||||
| set network.{{ name }}.layer=2 | ||||
| set network.{{ name }}.type='bridge' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-VXLAN' | ||||
| set firewall.@rule[-1].src='{{ s(ethernet.find_interface("upstream", 0)) }}' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].dest_port={{ interface.tunnel.peer_port }} | ||||
| @@ -1,125 +0,0 @@ | ||||
| {% | ||||
|  | ||||
| let wmm = state?.globals?.wireless_multimedia; | ||||
|  | ||||
| if (!length(wmm)) | ||||
| 	return; | ||||
|  | ||||
| let class = { | ||||
| 	"CS0": 0, | ||||
| 	"CS1": 8, | ||||
| 	"CS2": 16, | ||||
| 	"CS3": 24, | ||||
| 	"CS4": 32, | ||||
| 	"CS5": 40, | ||||
| 	"CS6": 48, | ||||
| 	"CS7": 56, | ||||
| 	"AF11": 10, | ||||
| 	"AF12": 12, | ||||
| 	"AF13": 14, | ||||
| 	"AF21": 18, | ||||
| 	"AF22": 20, | ||||
| 	"AF23": 22, | ||||
| 	"AF31": 26, | ||||
| 	"AF32": 28, | ||||
| 	"AF33": 30, | ||||
| 	"AF41": 34, | ||||
| 	"AF42": 36, | ||||
| 	"AF43": 38, | ||||
| 	"EF": 46, | ||||
| 	"VA": 44, | ||||
| 	"LE": 1, | ||||
| 	"DF": 0, | ||||
|  | ||||
| 	/* fake entry used by rfc8325 */ | ||||
| 	"MIN": 2 | ||||
| }; | ||||
|  | ||||
| let profiles = { | ||||
| 	"rfc8325": { | ||||
| 		"defaults": { | ||||
| 			"UP0": [ "MIN", "CS2" ], | ||||
| 			"UP1": [ "LE" ], | ||||
| 			"UP3": [ "AF21", "AF23" ], | ||||
| 			"UP4": [ "CS3", "AF43" ], | ||||
| 			"UP5": [ "CS5" ], | ||||
| 			"UP6": [ "VA", "EF" ], | ||||
| 			"UP7": [ "CS6", "CS7" ] | ||||
| 		} | ||||
| 	}, | ||||
| 	"3gpp": { | ||||
| 		"defaults": { | ||||
| 			"UP0": [ "DF" ], | ||||
| 			"UP1": [ "CS1" ], | ||||
| 			"UP2": [ "AF11", "AF13" ], | ||||
| 			"UP3": [ "AF21", "AF23" ], | ||||
| 			"UP4": [ "CS3", "AF33" ], | ||||
| 			"UP5": [ "CS5", "AF43" ], | ||||
| 			"UP6": [ "CS4" ], | ||||
| 			"UP7": [ "CS6" ] | ||||
| 		}, | ||||
| 		"exceptions": { | ||||
| 			"UP6": [ "EF" ] | ||||
| 		} | ||||
| 	}, | ||||
| 	"enterprise": { | ||||
| 		"defaults": { | ||||
| 			"UP0": [ "DF" ], | ||||
| 			"UP1": [ "CS1" ], | ||||
| 			"UP2": [ "AF11", "AF13" ], | ||||
| 			"UP3": [ "CS2", "AF23" ], | ||||
| 			"UP4": [ "CS3", "AF33" ], | ||||
| 			"UP5": [ "CS5" ], | ||||
| 			"UP6": [ "CS4" ], | ||||
| 			"UP7": [ "CS6" ] | ||||
| 		}, | ||||
| 		"exceptions": { | ||||
| 			"UP5": [ "AF41", "AF42", "AF43" ], | ||||
| 			"UP6": [ "EF" ], | ||||
| 			"UP7": [ "CS6" ] | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| function qos_map() { | ||||
| 	let up_map = []; | ||||
|  | ||||
| 	if (wmm.profile) | ||||
| 		wmm = profiles[wmm.profile]; | ||||
|  | ||||
| 	if (!length(wmm.defaults)) | ||||
| 		wmm.defaults = { }; | ||||
|  | ||||
| 	if (!length(wmm.exceptions)) | ||||
| 		wmm.exceptions = { }; | ||||
|  | ||||
| 	for (let prio = 0; prio < 8; prio++) { | ||||
| 		let up = wmm.exceptions["UP" + prio] || []; | ||||
| 		let len = length(up); | ||||
|  | ||||
| 		if (!length(up)) | ||||
| 			continue; | ||||
|  | ||||
| 		for (let idx = 0; idx < len; idx++) { | ||||
| 			push(up_map, class[up[idx]]); | ||||
| 			push(up_map, prio); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (let prio = 0; prio < 8; prio++) { | ||||
| 		let up = wmm.defaults["UP" + prio]; | ||||
|  | ||||
| 		if (length(up)) { | ||||
| 			push(up_map, class[up[0]]); | ||||
| 			push(up_map, class[up[1] || up[0]]); | ||||
| 		} else { | ||||
| 			push(up_map, 255); | ||||
| 			push(up_map, 255); | ||||
| 		} | ||||
| 	} | ||||
| 	let qos_map = join(",", up_map); | ||||
|  | ||||
| 	return qos_map; | ||||
| } | ||||
| %} | ||||
| set wireless.{{ section }}.iw_qos_map_set={{ s(qos_map()) }} | ||||
| @@ -1,21 +0,0 @@ | ||||
| {% let interfaces = services.lookup_interfaces("dhcp-snooping") %} | ||||
| {% let enable = length(interfaces) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| # DHCP Snooping configuration | ||||
|  | ||||
| set event.dhcp=event | ||||
| set event.dhcp.type=dhcp | ||||
| set event.dhcp.filter='*' | ||||
| {% for (let n, filter in dhcp_snooping.filters): %} | ||||
| {{ n ? 'add_list' : 'set' }} event.dhcp.filter={{ filter }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% for (let interface in interfaces): %} | ||||
| {%    if (interface.role != "downstream") continue %} | ||||
| {%	let name = ethernet.calculate_name(interface) %} | ||||
| add dhcpsnoop device | ||||
| set dhcpsnoop.@device[-1].name={{ s(name) }} | ||||
| set dhcpsnoop.@device[-1].ingress=1 | ||||
| set dhcpsnoop.@device[-1].egress=1 | ||||
| {% endfor %} | ||||
| @@ -1,11 +0,0 @@ | ||||
| {% | ||||
| 	if (!health) | ||||
| 		return; | ||||
| %} | ||||
|  | ||||
| # Health configuration | ||||
| set state.health.interval={{ health.interval }} | ||||
| set state.health.dhcp_local={{ b(health.dhcp_local) }} | ||||
| set state.health.dhcp_remote={{ b(health.dhcp_remote) }} | ||||
| set state.health.dns_local={{ b(health.dns_local) }} | ||||
| set state.health.dns_remote={{ b(health.dns_remote) }} | ||||
| @@ -1,7 +0,0 @@ | ||||
| {% if (!realtime) return %} | ||||
|  | ||||
| # Realtime event configuration | ||||
| {% for (let real in realtime.types): %} | ||||
| {%   if (!(real in events)) continue; %} | ||||
| add_list event.realtime.filter={{ real }} | ||||
| {% endfor %} | ||||
| @@ -1,7 +0,0 @@ | ||||
| {% if (!statistics) return %} | ||||
|  | ||||
| # Statistics configuration | ||||
| set state.stats.interval={{ statistics.interval }} | ||||
| {% for (let statistic in statistics.types): %} | ||||
| add_list state.stats.types={{ statistic  }} | ||||
| {% endfor %} | ||||
| @@ -1,8 +0,0 @@ | ||||
| {% if (!telemetry) return %} | ||||
|  | ||||
| # Telemetry streaming configuration | ||||
| set event.bulk.interval={{ telemetry.interval }} | ||||
| {% for (let type in telemetry.types): %} | ||||
| {%   if (!(type in events)) continue; %} | ||||
| add_list event.bulk.filter={{ type }} | ||||
| {% endfor %} | ||||
| @@ -1,9 +0,0 @@ | ||||
| {% if (!wifi_frames) return %} | ||||
|  | ||||
| # Wifi-frame reporting configuration | ||||
| set event.wifi=event | ||||
| set event.wifi.type=wifi | ||||
| set event.wifi.filter='*' | ||||
| {% for (let n, filter in wifi_frames.filters): %} | ||||
| {{ n ? 'add_list' : 'set' }} event.wifi.filter={{ filter }} | ||||
| {% endfor %} | ||||
| @@ -1,3 +0,0 @@ | ||||
| set event.wifiscan.interval={{ wifi_scan.interval }} | ||||
| set event.wifiscan.verbose={{ b(wifi_scan.verbose) }} | ||||
| set event.wifiscan.information_elements={{ b(wifi_scan.information_elements) }} | ||||
| @@ -1,206 +0,0 @@ | ||||
| {% | ||||
| 	let phys = wiphy.lookup_by_band(radio.band); | ||||
|  | ||||
| 	if (!length(phys)) { | ||||
| 		warn("Can't find any suitable radio phy for band %s radio settings", radio.band); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	function match_htmode(phy, radio) { | ||||
| 		let channel_mode = radio.channel_mode; | ||||
| 		let channel_width = radio.channel_width; | ||||
| 		let fallback_modes = { EHT: /^(EHT|HE|VHT|HT)/, HE: /^(HE|VHT|HT)/, VHT: /^(VHT|HT)/, HT: /^HT/ }; | ||||
| 		let mode_weight = { HT: 1, VHT: 10, HE: 100, EHT: 1000 }; | ||||
| 		let wanted_mode = channel_mode + (channel_width == 8080 ? "80+80" : channel_width); | ||||
|  | ||||
| 		let supported_phy_modes = map(sort(map(phy.htmode, (mode) => { | ||||
| 			let m = match(mode, /^([A-Z]+)(.+)$/); | ||||
| 			return [ mode, mode_weight[m[1]] * (m[2] == "80+80" ? 159 : +m[2]) ]; | ||||
| 		}), (a, b) => (b[1] - a[1])), i => i[0]); | ||||
| 		supported_phy_modes = filter(supported_phy_modes, mode => | ||||
| 			!(index(phy.band, "2G") >= 0 && mode == "VHT80")); | ||||
| 		if (wanted_mode in supported_phy_modes) | ||||
| 			return wanted_mode; | ||||
|  | ||||
| 		for (let supported_mode in supported_phy_modes) { | ||||
| 			if (match(supported_mode, fallback_modes[channel_mode])) { | ||||
| 				warn("Selected radio does not support requested HT mode %s, falling back to %s", | ||||
| 					wanted_mode, supported_mode); | ||||
| 				delete radio.channel; | ||||
| 				return supported_mode; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		warn("Selected radio does not support any HT modes"); | ||||
| 		die("Selected radio does not support any HT modes"); | ||||
| 	} | ||||
|  | ||||
| 	let channel_list = { | ||||
| 		"320": [ 0 ], | ||||
| 		"160": [ 36, 100 ], | ||||
| 		"80": [ 36, 52, 100, 116, 132, 149 ], | ||||
| 		"40": [ 36, 44, 52, 60, 100, 108, | ||||
| 			116, 124, 132, 140, 149, 157, 165, 173, | ||||
| 			184, 192 ] | ||||
| 	}; | ||||
|  | ||||
| 	if (!length(radio.valid_channels) && radio.band == "5G") | ||||
| 		radio.valid_channels = [ 36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157, 165, 173, 184, 192 ]; | ||||
| 	if (!length(radio.valid_channels) && radio.band == "6G") | ||||
| 		radio.valid_channels = [ 1, 2, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, | ||||
| 					 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, | ||||
| 					 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, 189, 193, 197, 201, 205, | ||||
| 					 209, 213, 217, 221, 225, 229, 233 ]; | ||||
|  | ||||
| 	if (capab.country_code && !(radio.country in capab.country_code)) { | ||||
| 		warn("Overriding country code to %s", capab.country_code[0]); | ||||
| 		radio.country = capab.country_code[0]; | ||||
| 	} | ||||
|  | ||||
| 	if (length(restrict.country) && !(radio.country in restrict.country)) { | ||||
| 		warn("Country code is restricted"); | ||||
| 		die("Country code is restricted"); | ||||
| 	} | ||||
|  | ||||
| 	function allowed_channel(phy, radio) { | ||||
| 		if (restrict.dfs && radio.channel in phy.dfs_channels) | ||||
| 			return false; | ||||
| 		if (radio.channel_width == 20) | ||||
| 			return true; | ||||
| 		if (!channel_list[radio.channel_width]) | ||||
| 			return false; | ||||
| 		if (!(radio.channel in channel_list[radio.channel_width])) | ||||
| 			return false; | ||||
| 		if (radio.valid_channels && !(radio.channel in radio.valid_channels)) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	function match_channel(phy, radio) { | ||||
| 		let wanted_channel = radio.channel; | ||||
| 		if (!wanted_channel || wanted_channel == "auto") | ||||
| 			return 0; | ||||
|  | ||||
| 		if (index(phy.band, "5G") >= 0 && !allowed_channel(phy, radio)) { | ||||
| 			warn("Selected radio does not support requested channel %d, falling back to ACS", | ||||
| 				wanted_channel); | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		if (wanted_channel in phy.channels) | ||||
| 			return wanted_channel; | ||||
|  | ||||
| 		let min = (wanted_channel <= 14) ?  1 :  32; | ||||
| 		let max = (wanted_channel <= 14) ? 14 : 196; | ||||
| 		let eligible_channels = filter(phy.channels, (ch) => (ch >= min && ch <= max)); | ||||
|  | ||||
| 		// try to find a channel next to the wanted one | ||||
| 		for (let i = length(eligible_channels); i > 0; i--) { | ||||
| 			let candidate = eligible_channels[i - 1]; | ||||
|  | ||||
| 			if (candidate < wanted_channel || i == 1) { | ||||
| 				warn("Selected radio does not support requested channel %d, falling back to %d", | ||||
| 					wanted_channel, candidate); | ||||
|  | ||||
| 				return candidate; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		warn("Selected radio does not support any channel in the target frequency range, falling back to %d", | ||||
| 			phy.channels[0]); | ||||
|  | ||||
| 		return phy.channels[0]; | ||||
| 	} | ||||
|  | ||||
| 	function match_mimo(available_ant, wanted_mimo) { | ||||
| 		if (!radio.mimo) | ||||
| 			return available_ant; | ||||
|  | ||||
| 		let shift = ((available_ant & 0xf0) == available_ant) ? 4 : 0; | ||||
| 		let m = match(wanted_mimo, /^([0-9]+)x([0-9]+$)/); | ||||
| 		if (!m) { | ||||
| 			warn("Failed to parse MIMO mode, falling back to %d", available_ant); | ||||
|  | ||||
| 			return available_ant; | ||||
| 		} | ||||
|  | ||||
| 		let use_ant = 0; | ||||
| 		for (let i = 0; i < m[1]; i++) | ||||
| 			use_ant += 1 << i; | ||||
|  | ||||
| 		if (shift == 4) | ||||
| 			switch(use_ant) { | ||||
| 			case 0x1: | ||||
| 				use_ant = 0x8; | ||||
| 				break; | ||||
| 			case 0x3: | ||||
| 				use_ant = 0xc; | ||||
| 				break; | ||||
| 			case 0x7: | ||||
| 				use_ant = 0xe; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 		if (!use_ant || (use_ant << shift) > available_ant) { | ||||
| 			warn("Invalid or unsupported MIMO mode %s specified, falling back to %d", | ||||
| 				wanted_mimo || 'none', available_ant); | ||||
|  | ||||
| 			return available_ant; | ||||
| 		} | ||||
|  | ||||
| 		return use_ant << shift; | ||||
| 	} | ||||
|  | ||||
| 	function match_require_mode(require_mode) { | ||||
| 		let modes = { HT: "n", VHT: "ac", HE: "ax" }; | ||||
|  | ||||
| 		return modes[require_mode] || ''; | ||||
| 	} | ||||
|  | ||||
| 	if (restrict.dfs && radio.allow_dfs && radio.band == "5G") { | ||||
| 		warn('DFS is restricted.'); | ||||
| 		radio.allow_dfs = false; | ||||
| 	} | ||||
| %} | ||||
|  | ||||
| # Wireless Configuration | ||||
| {% for (let phy in phys): %} | ||||
| {%  let htmode = match_htmode(phy, radio) %} | ||||
| set wireless.{{ phy.section }}.disabled=0 | ||||
| set wireless.{{ phy.section }}.ucentral_path={{ s(location) }} | ||||
| set wireless.{{ phy.section }}.htmode={{ htmode }} | ||||
| set wireless.{{ phy.section }}.channel={{ match_channel(phy, radio) }} | ||||
| set wireless.{{ phy.section }}.txantenna={{ match_mimo(phy.tx_ant_avail, radio.mimo) }} | ||||
| set wireless.{{ phy.section }}.rxantenna={{ match_mimo(phy.rx_ant_avail, radio.mimo) }} | ||||
| set wireless.{{ phy.section }}.beacon_int={{ radio.beacon_interval }} | ||||
| set wireless.{{ phy.section }}.country={{ s(radio.country) }} | ||||
| set wireless.{{ phy.section }}.require_mode={{ s(match_require_mode(radio.require_mode)) }} | ||||
| set wireless.{{ phy.section }}.txpower={{ radio.tx_power }} | ||||
| set wireless.{{ phy.section }}.legacy_rates={{ b(radio.legacy_rates) }} | ||||
| set wireless.{{ phy.section }}.chan_bw={{ radio.bandwidth }} | ||||
| set wireless.{{ phy.section }}.maxassoc={{ radio.maximum_clients }} | ||||
| set wireless.{{ phy.section }}.maxassoc_ignore_probe={{ b(radio.maximum_clients_ignore_probe) }} | ||||
| set wireless.{{ phy.section }}.noscan=1 | ||||
| set wireless.{{ phy.section }}.reconf=1 | ||||
| set wireless.{{ phy.section }}.acs_exclude_dfs={{ b(!radio.allow_dfs) }} | ||||
| {% for (let channel in radio.valid_channels): %} | ||||
| {%    if (!radio.allow_dfs && channel in phy.dfs_channels) continue %} | ||||
| add_list wireless.{{ phy.section }}.channels={{ channel }} | ||||
| {% endfor %} | ||||
| {%  if (radio.he_settings && phy.he_mac_capa && match(htmode, /HE.*/)): %} | ||||
| set wireless.{{ phy.section }}.he_bss_color={{ radio.he_settings.bss_color }} | ||||
| set wireless.{{ phy.section }}.multiple_bssid={{ b(radio.he_settings.multiple_bssid) }} | ||||
| set wireless.{{ phy.section }}.ema={{ b(radio.he_settings.ema) }} | ||||
| {%  endif %} | ||||
| {%  if (radio.rates): %} | ||||
| set wireless.{{ phy.section }}.basic_rate={{ radio.rates.beacon }} | ||||
| set wireless.{{ phy.section }}.mcast_rate={{ radio.rates.multicast }} | ||||
| {%  endif %} | ||||
| {%  for (let raw in radio.hostapd_iface_raw): %} | ||||
| add_list wireless.{{ phy.section }}.hostapd_options={{ s(raw) }} | ||||
| {%  endfor %} | ||||
| {%  if (radio.band == "6G"): %} | ||||
| set wireless.{{ phy.section }}.he_co_locate={{ b(1) }} | ||||
| {%  endif %} | ||||
| {% endfor %} | ||||
| @@ -1,15 +0,0 @@ | ||||
| {% | ||||
| let enable = length(airtime_fairness); | ||||
| services.set_enabled("atfpolicy", enable); | ||||
| if (!enable) | ||||
| 	return; | ||||
| %} | ||||
|  | ||||
| set atfpolicy.@defaults[0].vo_queue_weight={{ airtime_fairness.voice_weight }} | ||||
| set atfpolicy.@defaults[0].update_pkt_threshold={{ airtime_fairness.packet_threshold }} | ||||
| set atfpolicy.@defaults[0].bulk_percent_thresh={{ airtime_fairness.bulk_threshold }} | ||||
| set atfpolicy.@defaults[0].prio_percent_thresh={{ airtime_fairness.priority_threshold }} | ||||
| set atfpolicy.@defaults[0].weight_normal={{ airtime_fairness.weight_normal }} | ||||
| set atfpolicy.@defaults[0].weight_prio={{ airtime_fairness.weight_priority }} | ||||
| set atfpolicy.@defaults[0].weight_bulk={{ airtime_fairness.weight_bulk }} | ||||
|  | ||||
| @@ -1,84 +0,0 @@ | ||||
| {% | ||||
| if (!services.is_present("spotfilter")) | ||||
| 	return; | ||||
| let interfaces = services.lookup_interfaces_by_ssids("captive"); | ||||
| let enable = length(interfaces); | ||||
| if (enable && enable > 1) { | ||||
| 	warn('captive portal can only run on a single interface'); | ||||
| 	enable = false; | ||||
|  | ||||
| } | ||||
| services.set_enabled("spotfilter", enable); | ||||
| services.set_enabled("uspot", enable); | ||||
| if (!enable) | ||||
| 	return; | ||||
| %} | ||||
|  | ||||
| {% for (let interface in uniq(interfaces)): %} | ||||
| {%   let name = ethernet.calculate_name(interface) %} | ||||
| add firewall redirect | ||||
| set firewall.@redirect[-1].name='Redirect-captive-{{ name }}' | ||||
| set firewall.@redirect[-1].src='{{ name }}' | ||||
| set firewall.@redirect[-1].src_dport='80' | ||||
| set firewall.@redirect[-1].proto='tcp' | ||||
| set firewall.@redirect[-1].target='DNAT' | ||||
| set firewall.@redirect[-1].mark='1/127' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-pre-captive-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='80' | ||||
| set firewall.@rule[-1].proto='tcp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].mark='1/127' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-captive-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='80' | ||||
| set firewall.@rule[-1].proto='tcp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].mark='2/127' | ||||
|  | ||||
| {%   if (interface.role == 'downstream'): %} | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-pre-captive-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest='{{ ethernet.find_interface("upstream", interface.vlan.id) }}' | ||||
| set firewall.@rule[-1].proto='any' | ||||
| set firewall.@rule[-1].target='DROP' | ||||
| set firewall.@rule[-1].mark='1/127' | ||||
|  | ||||
| add firewall include | ||||
| set firewall.@include[-1].type=restore | ||||
| set firewall.@include[-1].family=ipv4 | ||||
| set firewall.@include[-1].path='/usr/share/uspot/firewall.ipt' | ||||
| set firewall.@include[-1].reload=1 | ||||
|  | ||||
| add firewall include | ||||
| set firewall.@include[-1].type=restore | ||||
| set firewall.@include[-1].family=ipv6 | ||||
| set firewall.@include[-1].path='/usr/share/uspot/firewall.ipt' | ||||
| set firewall.@include[-1].reload=1 | ||||
| {%   endif %} | ||||
| {% endfor %} | ||||
|  | ||||
| add uhttpd uhttpd | ||||
| set uhttpd.@uhttpd[-1].redirect_https='0' | ||||
| set uhttpd.@uhttpd[-1].rfc1918_filter='1' | ||||
| set uhttpd.@uhttpd[-1].max_requests='5' | ||||
| set uhttpd.@uhttpd[-1].max_connections='100' | ||||
| set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt' | ||||
| set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key' | ||||
| set uhttpd.@uhttpd[-1].script_timeout='60' | ||||
| set uhttpd.@uhttpd[-1].network_timeout='30' | ||||
| set uhttpd.@uhttpd[-1].http_keepalive='20' | ||||
| set uhttpd.@uhttpd[-1].tcp_keepalive='1' | ||||
| set uhttpd.@uhttpd[-1].no_dirlists='1' | ||||
| add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:80' | ||||
| add_list uhttpd.@uhttpd[-1].listen_http='[::]:80' | ||||
| set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot | ||||
| add_list uhttpd.@uhttpd[-1].ucode_prefix='/hotspot=/usr/share/uspot/handler.uc' | ||||
| add_list uhttpd.@uhttpd[-1].ucode_prefix='/cpd=/usr/share/uspot/handler-cpd.uc' | ||||
| add_list uhttpd.@uhttpd[-1].ucode_prefix='/env=/usr/share/uspot/handler-env.uc' | ||||
| set uhttpd.@uhttpd[-1].error_page='/cpd' | ||||
| @@ -1,29 +0,0 @@ | ||||
| # Data Plane service configuration | ||||
| {% | ||||
| let iface = {}; | ||||
|  | ||||
| function render(dict, type) { | ||||
| 	for (let idx, filter in dict) { | ||||
| %} | ||||
| set dataplane.{{ filter.name }}=program | ||||
| set dataplane.{{ filter.name }}.type={{ type }} | ||||
| set dataplane.{{ filter.name }}.program={{ s(files.add_anonymous(location, 'ingress_' + idx, b64dec(filter.program))) }} | ||||
| {% | ||||
| 		for (let i in services.lookup_interfaces("data-plane:" + filter.name)) { | ||||
| 			let name = ethernet.calculate_name(i); | ||||
| 			if (!length(iface[name])) | ||||
| 				iface[name] = []; | ||||
| 			push(iface[name], filter.name); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| render(data_plane.ingress_filters, "ingress"); | ||||
| %} | ||||
|  | ||||
| {% for (let k, v in iface): %} | ||||
| set dataplane.{{ k }}=interface | ||||
| {%   for (let i, p in v): %} | ||||
| add_list dataplane.{{ k }}.program={{ s(p) }} | ||||
| {%   endfor %} | ||||
| {% endfor %} | ||||
| @@ -1,36 +0,0 @@ | ||||
| {% | ||||
| if (!services.is_present("dhcprelay") || !dhcp_relay) | ||||
| 	return; | ||||
| let interfaces = services.lookup_interfaces("dhcp-relay"); | ||||
| let ports = ethernet.lookup_by_select_ports(dhcp_relay.select_ports); | ||||
| let enable = length(interfaces) && length(ports);  | ||||
| services.set_enabled("dhcprelay", enable); | ||||
| if (!enable) | ||||
| 	return; | ||||
|  | ||||
| %} | ||||
|  | ||||
| # DHCP-relay service configuration | ||||
|  | ||||
| set firewall.dhcp_relay=rule | ||||
| set firewall.dhcp_relay.name='Allow-DHCP-Relay' | ||||
| set firewall.dhcp_relay.src='{{ s(ethernet.find_interface("upstream", 0)) }}' | ||||
| set firewall.dhcp_relay.dest_port='67' | ||||
| set firewall.dhcp_relay.family='ipv4' | ||||
| set firewall.dhcp_relay.proto='udp' | ||||
| set firewall.dhcp_relay.target='ACCEPT' | ||||
|  | ||||
| set dhcprelay.relay=bridge | ||||
| set dhcprelay.relay.name=up | ||||
| {% for (let vlan in dhcp_relay.vlans||[]): %} | ||||
| add_list dhcprelay.relay.vlans={{ vlan.vlan }} | ||||
| {% endfor %} | ||||
| {% for (let port in ports): %} | ||||
| add_list dhcprelay.relay.upstream={{ port }} | ||||
| {% endfor %} | ||||
| {% for (let vlan in dhcp_relay.vlans||[]): %} | ||||
| set dhcprelay.vlan{{vlan.vlan}}=config | ||||
| set dhcprelay.vlan{{vlan.vlan}}.server={{ s(vlan.relay_server) }} | ||||
| set dhcprelay.vlan{{vlan.vlan}}.circuit_id={{ s(vlan?.circuit_id_format) }} | ||||
| set dhcprelay.vlan{{vlan.vlan}}.remote_id={{ s(vlan?.remote_id_format) }} | ||||
| {% endfor %} | ||||
| @@ -1,13 +0,0 @@ | ||||
| {% services.set_enabled("dhcpsnoop", true) %} | ||||
|  | ||||
| # DHCP Snooping configuration | ||||
|  | ||||
| {% for (let interface in state.interfaces): %} | ||||
| {%   if (interface.role != 'upstream') continue %} | ||||
| {%      for (let name in ethernet.lookup_by_interface_vlan(interface)): %} | ||||
| add dhcpsnoop device | ||||
| set dhcpsnoop.@device[-1].name={{ s(name) }} | ||||
| set dhcpsnoop.@device[-1].ingress=1 | ||||
| set dhcpsnoop.@device[-1].egress=1 | ||||
| {%     endfor %} | ||||
| {% endfor %} | ||||
| @@ -1,48 +0,0 @@ | ||||
| {% if (!services.is_present("fbwifi")) return %} | ||||
| {% let interfaces = services.lookup_interfaces("facebook-wifi") %} | ||||
| {% let enable = length(interfaces) %} | ||||
| {% services.set_enabled("fbwifi", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| # Facebook-wifi service configuration | ||||
|  | ||||
| set fbwifi.main.enabled=1 | ||||
| set fbwifi.main.zone={{ s(ethernet.calculate_name(interfaces[0])) }} | ||||
| set fbwifi.main.gateway_token='FBWIFI:GATEWAY|{{ facebook_wifi.vendor_id }}|{{ facebook_wifi.gateway_id }}|{{ facebook_wifi.secret }}' | ||||
|  | ||||
| set uhttpd.main=uhttpd | ||||
| add_list uhttpd.main.listen_http='0.0.0.0:80' | ||||
| add_list uhttpd.main.listen_http='[::]:80' | ||||
| add_list uhttpd.main.listen_https='0.0.0.0:443' | ||||
| add_list uhttpd.main.listen_https='[::]:443' | ||||
| set uhttpd.main.redirect_https='0' | ||||
| set uhttpd.main.home='/www' | ||||
| set uhttpd.main.max_requests='3' | ||||
| set uhttpd.main.max_connections='100' | ||||
| set uhttpd.main.cgi_prefix='/cgi-bin' | ||||
| add_list uhttpd.main.lua_prefix='/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua' | ||||
| set uhttpd.main.script_timeout='60' | ||||
| set uhttpd.main.network_timeout='30' | ||||
| set uhttpd.main.http_keepalive='20' | ||||
| set uhttpd.main.tcp_keepalive='1' | ||||
| set uhttpd.main.cert='/tmp/fbwifi/https_server_cert' | ||||
| set uhttpd.main.key='/tmp/fbwifi/https_server_key' | ||||
| set uhttpd.main.json_script='/usr/share/fbwifi/uhttpd.json' | ||||
| set uhttpd.main.rfc1918_filter='0' | ||||
|  | ||||
| set firewall.fbwifi=include | ||||
| set firewall.fbwifi.enabled=1 | ||||
| set firewall.fbwifi.family=ipv4 | ||||
| set firewall.fbwifi.path=/usr/share/fbwifi/firewall.include | ||||
| set firewall.fbwifi.reload=1 | ||||
| set firewall.fbwifi.type=script | ||||
|  | ||||
| set uhttpd.fbwifi_redirect=uhttpd | ||||
| set uhttpd.fbwifi_redirect.enabled=1 | ||||
| set uhttpd.fbwifi_redirect.listen_http='0.0.0.0:2060' | ||||
| set uhttpd.fbwifi_redirect.listen_https='0.0.0.0:2061' | ||||
| set uhttpd.fbwifi_redirect.cert='/tmp/fbwifi/https_server_cert' | ||||
| set uhttpd.fbwifi_redirect.key='/tmp/fbwifi/https_server_key' | ||||
| set uhttpd.fbwifi_redirect.json_script='/tmp/fbwifi/uhttpd-redirect.json' | ||||
|  | ||||
| add_list dhcp.@dnsmasq[0].rebind_domain=fbwifigateway.net | ||||
| @@ -1,11 +0,0 @@ | ||||
| {%- | ||||
| 	let enable = length(gps); | ||||
| 	services.set_enabled("umdns", enable); | ||||
|         if (!enable) | ||||
|                 return; | ||||
| %} | ||||
|  | ||||
| # Configure GPS | ||||
| set gps.@gps[-1].disabled=0 | ||||
| set gps.@gps[-1].adjust_time={{ b(gps.adjust_time) }} | ||||
| set gps.@gps[-1].baudrate={{ s(gps.baud_rate) }} | ||||
| @@ -1,36 +0,0 @@ | ||||
| {% if (!services.is_present("uhttpd")) return %} | ||||
| {% let interfaces = services.lookup_interfaces("http") %} | ||||
| {% let enable = length(interfaces) %} | ||||
| {% services.set_enabled("uhttpd", enable) %} | ||||
| {% services.set_enabled("rpcd", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| # HTTP service configuration | ||||
|  | ||||
| add uhttpd uhttpd | ||||
| set uhttpd.@uhttpd[-1].redirect_https='0' | ||||
| set uhttpd.@uhttpd[-1].home='/www' | ||||
| set uhttpd.@uhttpd[-1].rfc1918_filter='1' | ||||
| set uhttpd.@uhttpd[-1].max_requests='3' | ||||
| set uhttpd.@uhttpd[-1].max_connections='100' | ||||
| set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt' | ||||
| set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key' | ||||
| set uhttpd.@uhttpd[-1].cgi_prefix='/cgi-bin' | ||||
| set uhttpd.@uhttpd[-1].lua_prefix='/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua' | ||||
| set uhttpd.@uhttpd[-1].script_timeout='60' | ||||
| set uhttpd.@uhttpd[-1].network_timeout='30' | ||||
| set uhttpd.@uhttpd[-1].http_keepalive='20' | ||||
| set uhttpd.@uhttpd[-1].tcp_keepalive='1' | ||||
| set uhttpd.@uhttpd[-1].ubus_prefix='/ubus' | ||||
| add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:{{ http.http_port || 80 }}' | ||||
| {% let interfaces = services.lookup_interfaces("http") %} | ||||
| {% for (let interface in interfaces): %} | ||||
| {%    let name = ethernet.calculate_name(interface) %} | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-http-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].port='{{ http.http_port || 80 }}' | ||||
| set firewall.@rule[-1].proto='tcp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| {% endfor %} | ||||
| @@ -1,53 +0,0 @@ | ||||
| {% | ||||
| 	if (!services.is_present("ieee8021x")) | ||||
| 		return; | ||||
| 	let interfaces = services.lookup_interfaces("ieee8021x"); | ||||
| 	let enable = length(interfaces); | ||||
| 	if (ieee8021x.mode == "radius") { | ||||
| 		if (!ieee8021x.radius.auth_server_addr || | ||||
| 		    !ieee8021x.radius.auth_server_port || | ||||
| 		    !ieee8021x.radius.auth_server_secret) { | ||||
| 			warn(invalid radius configuration); | ||||
| 			enable = false; | ||||
| 		} | ||||
| 	} | ||||
| 	services.set_enabled("ieee8021x", enable); | ||||
| 	if (!enable) | ||||
| 		return; | ||||
|  | ||||
| 	let ports = []; | ||||
| 	for (let p in ieee8021x.port_filter) | ||||
| 		if (ethernet.ports[p]) | ||||
| 			push(ports, ethernet.ports[p].netdev); | ||||
| 	cursor.load("system") | ||||
| 	let certs = cursor.get_all("system", "@certificates[-1]") | ||||
| %} | ||||
| # IEEE8021x service configuration | ||||
|  | ||||
| add ieee8021x config  | ||||
| {%	if (ieee8021x.mode == "radius"): %} | ||||
| add ieee8021x config  | ||||
| set ieee8021x.@config[-1].nas_identifier={{ s(ieee8021x.radius.nas_identifier) }} | ||||
| set ieee8021x.@config[-1].auth_server_addr={{ s(ieee8021x.radius.auth_server_addr) }} | ||||
| set ieee8021x.@config[-1].auth_server_port={{ s(ieee8021x.radius.auth_server_port) }} | ||||
| set ieee8021x.@config[-1].auth_server_secret={{ s(ieee8021x.radius.auth_server_secret) }} | ||||
| set ieee8021x.@config[-1].acct_server_addr={{ s(ieee8021x.radius.acct_server_addr) }} | ||||
| set ieee8021x.@config[-1].acct_server_port={{ s(ieee8021x.radius.acct_server_port) }} | ||||
| set ieee8021x.@config[-1].acct_server_secret={{ s(ieee8021x.radius.acct_server_secret) }} | ||||
| set ieee8021x.@config[-1].coa_server_addr={{ s(ieee8021x.radius.coa_server_addr) }} | ||||
| set ieee8021x.@config[-1].coa_server_port={{ s(ieee8021x.radius.coa_server_port) }} | ||||
| set ieee8021x.@config[-1].coa_server_secret={{ s(ieee8021x.radius.coa_server_secret) }} | ||||
| {%	else | ||||
| 		files.add_named("/var/run/hostapd-ieee8021x.eap_user", render("../eap_users.uc", { users: ieee8021x.users })) %} | ||||
| 	endif | ||||
| %} | ||||
| set ieee8021x.@config[-1].ca={{ s(certs.ca) }} | ||||
| set ieee8021x.@config[-1].cert={{ s(certs.cert) }} | ||||
| set ieee8021x.@config[-1].key={{ s(certs.key) }} | ||||
|  | ||||
| {%	for (let port in ports): %} | ||||
| add_list ieee8021x.@config[-1].ports={{ s(port) }} | ||||
| set network.{{ replace(port, '.', '_') }}=device | ||||
| set network.@device[-1].name={{ s(port) }} | ||||
| set network.@device[-1].auth='1' | ||||
| {%	endfor %} | ||||
| @@ -1,20 +0,0 @@ | ||||
| {% if (!services.is_present("igmpproxy")) return %} | ||||
| {% let interfaces = services.lookup_interfaces("igmp") %} | ||||
| {% let enable = length(interfaces) %} | ||||
| {% services.set_enabled("igmpproxy", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| # IGMP service configuration | ||||
|  | ||||
| {% let interfaces = services.lookup_interfaces("igmp") %} | ||||
| {% for (let interface in interfaces): %} | ||||
| {%   if (!interface.ipv4) continue; %} | ||||
| {%   let name = ethernet.calculate_name(interface) %} | ||||
| add igmpproxy phyint | ||||
| set igmpproxy.@phyint[-1].network={{ name }} | ||||
| set igmpproxy.@phyint[-1].zone={{ s((interface.role == "usptream") ? "wan" : name) }} | ||||
| set igmpproxy.@phyint[-1].direction={{ s(interface.role) }} | ||||
| {%   if (interface.role == "upstream"):  %} | ||||
| set igmpproxy.@phyint[-1].altnet='0.0.0.0/0' | ||||
| {%   endif %} | ||||
| {% endfor %} | ||||
| @@ -1,16 +0,0 @@ | ||||
| {% if (!services.is_present("lldpd")) return %} | ||||
| {% let interfaces = services.lookup_interfaces("lldp") %} | ||||
| {% let enable = length(interfaces) %} | ||||
| {% services.set_enabled("lldpd", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| # LLDP service configuration | ||||
|  | ||||
| set lldpd.config.enable=1 | ||||
| set lldpd.config.lldp_description={{ s(lldp.describe) }} | ||||
| set lldpd.config.lldp_location={{ s(lldp.location) }} | ||||
| {% for (let interface in interfaces): %} | ||||
| {%  for (let port in ethernet.lookup_by_interface_spec(interface)): %} | ||||
| add_list lldpd.config.interface={{ s(port) }} | ||||
| {%  endfor %} | ||||
| {% endfor %} | ||||
| @@ -1,18 +0,0 @@ | ||||
| {% | ||||
| if (!length(log)) return; | ||||
| let hostname = state.unit?.hostname; | ||||
| if (!hostname) { | ||||
| 	cursor.load("system"); | ||||
| 	let system = cursor.get_all("system", "@system[-1]"); | ||||
| 	hostname = system?.hostname || OpenWifi; | ||||
| } | ||||
| %} | ||||
|  | ||||
| # Syslog service configuration | ||||
|  | ||||
| set system.@system[-1].log_ip={{ s(log.host) }} | ||||
| set system.@system[-1].log_port={{ s(log.port) }} | ||||
| set system.@system[-1].log_proto={{ s(log.proto) }} | ||||
| set system.@system[-1].log_size={{ s(log.size) }} | ||||
| set system.@system[-1].log_priority={{ s(log.priority) }} | ||||
| set system.@system[-1].log_hostname={{ s(hostname) }} | ||||
| @@ -1,25 +0,0 @@ | ||||
| {% if (!services.is_present("umdns")) return %} | ||||
| {% let interfaces = services.lookup_interfaces("mdns") %} | ||||
| {% let enable = length(interfaces) %} | ||||
| {% services.set_enabled("umdns", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
|  | ||||
| # MDNS service configuration | ||||
|  | ||||
| add umdns umdns | ||||
| set umdns.@umdns[-1].enable=1 | ||||
| {% for (let interface in interfaces): %} | ||||
| add_list umdns.@umdns[-1].network={{ s(ethernet.calculate_name(interface)) }} | ||||
| {% endfor %} | ||||
|  | ||||
| {% for (let interface in interfaces): %} | ||||
| {%   let name = ethernet.calculate_name(interface) %} | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-mdns-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='5353' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| {% endfor %} | ||||
|  | ||||
| @@ -1,23 +0,0 @@ | ||||
| {% | ||||
| 	if (!length(ntp)) | ||||
| 		return; | ||||
| 	let interfaces = services.lookup_interfaces("ntp"); | ||||
| %} | ||||
| delete system.ntp.server | ||||
| set system.ntp.enabled={{ b(ntp.local_server) }} | ||||
| set system.ntp.enable_server={{ b(ntp.servers) }} | ||||
| {%	for (let server in ntp.servers): %} | ||||
| add_list system.ntp.server={{ s(server) }} | ||||
| {%	endfor | ||||
|  | ||||
| 	/* open the port on all interfaces that select ssh */ | ||||
| 	for (let interface in interfaces): | ||||
| 		let name = ethernet.calculate_name(interface); | ||||
| %} | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-ntp-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='123' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| {%	endfor %} | ||||
| @@ -1,27 +0,0 @@ | ||||
| {% | ||||
| let enable = true; | ||||
| if (!services.is_present("onlinecheck") || | ||||
|     !length(online_check) || | ||||
|     (!length(online_check.ping_hosts) && | ||||
|      !length(online_check.download_hosts))) | ||||
| 	enable = false; | ||||
|  | ||||
| services.set_enabled("onlinecheck", enable); | ||||
| if (!enable) | ||||
| 	return; | ||||
| %} | ||||
|  | ||||
|  | ||||
| # Online check service configuration | ||||
| add onlinecheck config | ||||
| set onlinecheck.@config[-1].check_interval={{ s(online_check.check_interval) }} | ||||
| set onlinecheck.@config[-1].check_threshold={{ s(online_check.check_threshold) }} | ||||
| {% for (let action in online_check.action): %} | ||||
| add_list onlinecheck.@config[-1].action={{ s(action) }} | ||||
| {% endfor %} | ||||
| {% for (let host in online_check.ping_hosts): %} | ||||
| add_list onlinecheck.@config[-1].ping_hosts={{ s(host) }} | ||||
| {% endfor %} | ||||
| {% for (let host in online_check.download_hosts): %} | ||||
| add_list onlinecheck.@config[-1].download_hosts={{ s(host) }} | ||||
| {% endfor %} | ||||
| @@ -1,87 +0,0 @@ | ||||
| {% | ||||
| if (!quality_of_service) | ||||
| 	quality_of_service = {}; | ||||
| let egress = ethernet.lookup_by_select_ports(quality_of_service.select_ports); | ||||
| let enable = length(egress); | ||||
| services.set_enabled("qosify", enable); | ||||
| if (!enable) | ||||
| 	return; | ||||
|  | ||||
| function get_speed(dev, speed) { | ||||
| 	if (!speed) | ||||
| 		speed = ethernet.get_speed(dev); | ||||
| 	return speed; | ||||
| } | ||||
|  | ||||
| function get_proto(proto) { | ||||
| 	if (proto == "any") | ||||
| 		return [ "udp", "tcp" ]; | ||||
| 	return [ proto ]; | ||||
| } | ||||
|  | ||||
| function get_range(port) { | ||||
| 	if (port.range_end) | ||||
| 		return sprintf("-%d", port.range_end) | ||||
| } | ||||
|  | ||||
| let fs = require("fs"); | ||||
| let file = fs.open("/tmp/qosify.conf", "w"); | ||||
| for (let class in quality_of_service.classifier) { | ||||
| 	for (let port in class.ports) | ||||
| 		for (let proto in get_proto(port.protocol)) | ||||
| 			file.write(sprintf("%s:%d%s %s%s\n", proto, port.port, | ||||
| 					   port.range_end ? sprintf("-%d", port.range_end) : "", | ||||
| 					   port.reclassify ? "" : "+", class.dscp)); | ||||
| 	for (let fqdn in class.dns) | ||||
| 		file.write(sprintf("dns:%s%s %s%s\n", | ||||
| 				   fqdn.suffix_matching ? "*." : "", fqdn.fqdn, | ||||
| 				   fqdn.reclassify ? "" : "+", class.dscp)); | ||||
| } | ||||
|  | ||||
| if (quality_of_service.services) { | ||||
| 	let inputfile = fs.open('/usr/share/ucentral/qos.json', "r"); | ||||
| 	let db = json(inputfile.read("all")); | ||||
|  | ||||
| 	for (let k, v in db.classes) { | ||||
| %} | ||||
| set qosify.{{ k }}=class | ||||
| set qosify.{{ k }}.ingress={{ s(v.ingress) }} | ||||
| set qosify.{{ k }}.egress={{ s(v.egress) }} | ||||
| set qosify.{{ k }}.bulk_trigger_pps={{ s(v.bulk_pps) }} | ||||
| set qosify.{{ k }}.bulk_trigger_timeout={{ s(v.bulk_timeout) }} | ||||
| set qosify.{{ k }}.dscp_bulk={{ s(v.bulk_dscp) }} | ||||
| {% | ||||
| 	} | ||||
|  | ||||
| 	let rules = []; | ||||
| 	let all = 'all' in quality_of_service.services; | ||||
| 	for (let k, v in db.services) | ||||
| 		if (all || (k in quality_of_service.services)) | ||||
| 			for (let uses in v.uses) | ||||
| 				push(quality_of_service.services, uses); | ||||
| 	for (let k, v in db.services) | ||||
| 		if (all || (k in quality_of_service.services)) { | ||||
| 			for (let port in v.tcp) | ||||
| 				push(rules, 'tcp:' + port + ' ' + v.classifier); | ||||
| 			for (let port in v.udp) | ||||
| 				push(rules, 'udp:' + port + ' ' + v.classifier); | ||||
| 			for (let dns in v.fqdn) | ||||
| 				push(rules, 'dns:' + dns + ' ' + v.classifier); | ||||
| 		} | ||||
|  | ||||
| 	for (let rule in uniq(rules)) | ||||
| 		file.write(rule + '\n'); | ||||
| } | ||||
|  | ||||
| file.close(); | ||||
| %} | ||||
|  | ||||
| set qosify.@defaults[0].bulk_trigger_pps={{ quality_of_service?.bulk_detection?.packets_per_second || 0}} | ||||
| set qosify.@defaults[0].dscp_bulk={{ quality_of_service?.bulk_detection?.dscp }} | ||||
|  | ||||
| {% for (let dev in egress): %} | ||||
| set qosify.{{ dev }}=device | ||||
| set qosify.{{ dev }}.name={{ s(dev) }} | ||||
| set qosify.{{ dev }}.bandwidth_up='{{ get_speed(dev, quality_of_service.bandwidth_up) }}mbit' | ||||
| set qosify.{{ dev }}.bandwidth_down='{{ get_speed(dev, quality_of_service.bandwidth_down) }}mbit' | ||||
| {% endfor %} | ||||
| @@ -1,7 +0,0 @@ | ||||
| {% | ||||
| if (!services.is_present("radius-gw-proxy")) | ||||
| 	return; | ||||
| let ssids = services.lookup_ssids("radius-gw-proxy"); | ||||
| let enable = length(ssids); | ||||
| services.set_enabled("radius-gw-proxy", enable); | ||||
| %} | ||||
| @@ -1,93 +0,0 @@ | ||||
| {% if (!services.is_present("radsecproxy")) return %} | ||||
| {% let enable = (length(radius_proxy) && length(radius_proxy.realms)) %} | ||||
| {% services.set_enabled("radsecproxy", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| add radsecproxy options | ||||
| add_list radsecproxy.@options[-1].ListenUDP='localhost:1812' | ||||
| add_list radsecproxy.@options[-1].ListenUDP='localhost:1813' | ||||
|  | ||||
| add radsecproxy client | ||||
| set radsecproxy.@client[-1].name='client' | ||||
| set radsecproxy.@client[-1].host='localhost' | ||||
| set radsecproxy.@client[-1].type='udp' | ||||
| set radsecproxy.@client[-1].secret={{ s(radius_proxy.proxy_secret) }} | ||||
|  | ||||
| {% for (idx, realm in radius_proxy.realms): %} | ||||
|  | ||||
| {%   let certs = {}; | ||||
|      if (realm.use_local_certificates) { | ||||
| 	cursor.load("system"); | ||||
| 	certs = cursor.get_all("system", "@certificates[-1]"); | ||||
|      } else if (realm.ca_certificate && realm.certificate && realm.private_key) { | ||||
| 	certs.ca = files.add_anonymous(location, 'ca' + idx, b64dec(realm.ca_certificate)); | ||||
| 	certs.cert = files.add_anonymous(location, 'cert' + idx, b64dec(realm.certificate)); | ||||
| 	certs.key = files.add_anonymous(location, 'key' + idx, b64dec(realm.private_key)); | ||||
|      } else if (realm.protocol == "radsec") { | ||||
| 	warn("invalid certificate settings"); | ||||
| 	continue; | ||||
|      } | ||||
| %} | ||||
|  | ||||
| {%   if (realm.protocol == "radsec"): %} | ||||
| set radsecproxy.tls{{ idx }}=tls | ||||
| set radsecproxy.@tls[-1].name='tls{{ idx }}' | ||||
| set radsecproxy.@tls[-1].CACertificateFile={{ s(certs.ca) }} | ||||
| set radsecproxy.@tls[-1].certificateFile={{ s(certs.cert) }} | ||||
| set radsecproxy.@tls[-1].certificateKeyFile={{ s(certs.key) }} | ||||
| set radsecproxy.@tls[-1].certificateKeyPassword='' | ||||
|  | ||||
| set radsecproxy.server{{ idx }}=server | ||||
| set radsecproxy.@server[-1].name='server{{ idx }}' | ||||
| {%     if (realm.auto_discover): %} | ||||
| set radsecproxy.@server[-1].dynamicLookupCommand='/usr/libexec/naptr_lookup.sh' | ||||
| {%     else %} | ||||
| set radsecproxy.@server[-1].host={{ s(realm.host) }} | ||||
| set radsecproxy.@server[-1].port={{ s(realm.port) }} | ||||
| set radsecproxy.@server[-1].secret={{ s(realm.secret) }} | ||||
| {%     endif %} | ||||
| set radsecproxy.@server[-1].type='tls' | ||||
| set radsecproxy.@server[-1].tls='tls{{ idx }}' | ||||
| set radsecproxy.@server[-1].statusServer='0' | ||||
| set radsecproxy.@server[-1].certificateNameCheck='0' | ||||
| {%     for (name in realm.realm): %} | ||||
| add radsecproxy realm | ||||
| set radsecproxy.@realm[-1].name='{{ name }}' | ||||
| set radsecproxy.@realm[-1].server='server{{ idx }}' | ||||
| set radsecproxy.@realm[-1].accountingServer='server{{ idx }}' | ||||
| {%     endfor %} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (realm.protocol == "radius"): %} | ||||
| set radsecproxy.server{{ idx + "auth" }}=server | ||||
| set radsecproxy.@server[-1].name='server{{ idx }}auth' | ||||
| set radsecproxy.@server[-1].host={{ s(realm.auth_server) }} | ||||
| set radsecproxy.@server[-1].port={{ s(realm.auth_port) }} | ||||
| set radsecproxy.@server[-1].secret={{ s(realm.auth_secret) }} | ||||
| set radsecproxy.@server[-1].type='udp' | ||||
| {%     if (realm.acct_server): %} | ||||
| set radsecproxy.server{{ idx + "acct" }}=server | ||||
| set radsecproxy.@server[-1].name='server{{ idx }}acct' | ||||
| set radsecproxy.@server[-1].host={{ s(realm.acct_server) }} | ||||
| set radsecproxy.@server[-1].port={{ s(realm.acct_port) }} | ||||
| set radsecproxy.@server[-1].secret={{ s(realm.acct_secret) }} | ||||
| set radsecproxy.@server[-1].type='udp' | ||||
| {%     endif %} | ||||
| {%     for (name in realm.realm): %} | ||||
| add radsecproxy realm | ||||
| set radsecproxy.@realm[-1].name='{{ name }}' | ||||
| set radsecproxy.@realm[-1].server='server{{ idx }}auth' | ||||
| {%       if (realm.acct_server): %} | ||||
| set radsecproxy.@realm[-1].accountingServer='server{{ idx }}acct' | ||||
| {%       endif %} | ||||
| {%     endfor %} | ||||
| {%   endif %} | ||||
|  | ||||
| {%   if (realm.protocol == "block"): %} | ||||
| {%     for (name in realm.realm): %} | ||||
| add radsecproxy realm | ||||
| set radsecproxy.@realm[-1].name='{{ name }}' | ||||
| set radsecproxy.@realm[-1].replyMessage={{ s(realm.message) }} | ||||
| {%     endfor %} | ||||
| {%   endif %} | ||||
| {% endfor %} | ||||
| @@ -1,12 +0,0 @@ | ||||
| {% | ||||
| if (!services.is_present("rrmd")) | ||||
| 	return; | ||||
| let interfaces = services.lookup_interfaces("mdns"); | ||||
| let enable = length(rrm); | ||||
| services.set_enabled("rrmd", enable); | ||||
| if (!enable) | ||||
| 	return ; | ||||
| %} | ||||
|  | ||||
| set rrmd.@base[0].beacon_request_assoc={{ rrm.beacon_request_assoc || 0 }} | ||||
| set rrmd.@base[0].station_stats_interval={{ rrm.station_stats_interval || 0 }} | ||||
| @@ -1,12 +0,0 @@ | ||||
| {% if (!services.is_present("rtty")) return %} | ||||
| {% let enable = length(rtty) %} | ||||
| {% services.set_enabled("rtty", enable) %} | ||||
| {% if (!enable) return %} | ||||
|  | ||||
| # RTTY service configuration | ||||
|  | ||||
| set rtty.@rtty[-1].enable={{ b((rtty.token && rtty.host && rtty.port)) }} | ||||
| set rtty.@rtty[-1].token={{ s(rtty.token) }} | ||||
| set rtty.@rtty[-1].host={{ s(rtty.host) }} | ||||
| set rtty.@rtty[-1].port={{ s(rtty.port) }} | ||||
| set rtty.@rtty[-1].ssl={{ b(rtty.mutual_tls) }} | ||||
| @@ -1,31 +0,0 @@ | ||||
| {% | ||||
| let interfaces = services.lookup_interfaces("ssh"); | ||||
| let enable = length(interfaces); | ||||
|  | ||||
| if (restrict.ssh && enable) { | ||||
| 	warn('SSH is restricted'); | ||||
| 	enable = false; | ||||
| } | ||||
|  | ||||
| services.set_enabled("dropbear", enable); | ||||
| if (!enable) | ||||
| 	return; | ||||
| files.add_named("/etc/dropbear/authorized_keys", join("\n", ssh.authorized_keys || []) + "\n"); | ||||
| %} | ||||
|  | ||||
| # SSH service configuration | ||||
|  | ||||
| set dropbear.@dropbear[-1].enable={{ b(enable) }} | ||||
| set dropbear.@dropbear[-1].Port={{ s(ssh.port) }} | ||||
| set dropbear.@dropbear[-1].PasswordAuth={{ b(ssh.password_authentication) }} | ||||
|  | ||||
| {% for (let interface in interfaces): %} | ||||
| {%    let name = ethernet.calculate_name(interface) %} | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-ssh-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='{{ ssh.port }}' | ||||
| set firewall.@rule[-1].proto='tcp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| {% endfor %} | ||||
| @@ -1,29 +0,0 @@ | ||||
| {% if (!services.is_present("usteer")) return %} | ||||
| {% let ssids = services.lookup_ssids("wifi-steering") %} | ||||
| {% let enable = length(ssids) %} | ||||
| {% services.set_enabled("usteer", enable) %} | ||||
| {% if (!enable) return %} | ||||
| {% let name = wifi_steering.mode == 'local' ? ethernet.find_interface("upstream", 0) : '' %} | ||||
| # Wifi-Steering service configuration | ||||
|  | ||||
| add usteer usteer | ||||
| set usteer.@usteer[-1].network={{ s(name) }} | ||||
| set usteer.@usteer[-1].ipv6={{ b(wifi_steering.ipv6) }} | ||||
| set usteer.@usteer[-1].key={{ s(wifi_steering.key) }} | ||||
| set usteer.@usteer[-1].assoc_steering={{ b(wifi_steering.assoc_steering) }} | ||||
| set usteer.@usteer[-1].min_snr={{ wifi_steering.required_snr }} | ||||
| set usteer.@usteer[-1].min_connect_snr={{ wifi_steering.required_probe_snr }} | ||||
| set usteer.@usteer[-1].roam_scan_snr={{ wifi_steering.required_roam_snr }} | ||||
| set usteer.@usteer[-1].load_kick_enabled={{ b(wifi_steering.load_kick_threshold) }} | ||||
| set usteer.@usteer[-1].load_kick_threshold={{ wifi_steering.load_kick_threshold }} | ||||
| set usteer.@usteer[-1].autochannel={{ b(wifi_steering.auto_channel) }} | ||||
| {% for (let ssid in ssids): %} | ||||
| add_list usteer.@usteer[-1].ssid_list={{ s(ssid.name) }} | ||||
| {% endfor %} | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-usteer-{{ name }}' | ||||
| set firewall.@rule[-1].src='{{ name }}' | ||||
| set firewall.@rule[-1].dest_port='16720' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| @@ -1,116 +0,0 @@ | ||||
| {% | ||||
| let wireguard = length(services.lookup_interfaces("wireguard-overlay")); | ||||
| let vxlan = length(services.lookup_interfaces("vxlan-overlay")); | ||||
|  | ||||
| if (!wireguard && !vxlan) { | ||||
| 	services.set_enabled("unetd", false); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| if (wireguard + vlxan > 1) { | ||||
| 	warn('only a single wireguard/vxlan-overlay is allowed\n'); | ||||
| 	services.set_enabled("unetd", false); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| if (!wireguard_overlay.root_node.key || | ||||
|     !wireguard_overlay.root_node.endpoint || | ||||
|     !wireguard_overlay.root_node.ipaddr) { | ||||
| 	warn('root node is not configured correctly\n'); | ||||
| 	services.set_enabled("unetd", false); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| services.set_enabled("unetd", true); | ||||
|  | ||||
| let ips = []; | ||||
|  | ||||
| wireguard_overlay.root_node.name = "gateway"; | ||||
| wireguard_overlay.root_node.groups = [ "gateway" ]; | ||||
|  | ||||
| for (let ip in wireguard_overlay.root_node.ipaddr) | ||||
| 	push(ips, ip); | ||||
|  | ||||
| if (wireguard) | ||||
| 	wireguard_overlay.root_node.subnet = [ '0.0.0.0/0' ]; | ||||
|  | ||||
| latency.add(wireguard_overlay.root_node.endpoint, 4); | ||||
|  | ||||
| let cfg = { | ||||
| 	'config': { | ||||
| 		'port': wireguard_overlay.peer_port, | ||||
| 		'peer-exchange-port': wireguard_overlay.peer_exchange_port, | ||||
| 		'keepalive': 10 | ||||
| 	}, | ||||
| 	'hosts': { | ||||
| 		gateway: wireguard_overlay.root_node, | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| let pipe = require('fs').popen(sprintf('echo "%s" | wg pubkey', wireguard_overlay.private_key)); | ||||
| let pubkey = replace(pipe.read("all"), '\n', ''); | ||||
| pipe.close(); | ||||
| for (let host in wireguard_overlay.hosts) | ||||
| 	if (host.name) { | ||||
| 		if (!host.name || !host.key) { | ||||
| 			warn('host is not configured correctly\n'); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		cfg.hosts[host.name] = host; | ||||
| 		cfg.hosts[host.name].groups = [ 'ap' ]; | ||||
| 		if (host.key == pubkey) | ||||
| 			continue; | ||||
| 		for (let ip in host.ipaddr) | ||||
| 			push(ips, ip); | ||||
| 	} | ||||
| if (vxlan) { | ||||
| 	cfg.services = { | ||||
| 		"l2-tunnel": { | ||||
| 			"type": "vxlan", | ||||
| 			"config": { | ||||
| 				port: wireguard_overlay?.vxlan?.port || 4789, | ||||
| 			}, | ||||
| 			"members": [ "gateway", "@ap" ] | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	if (wireguard_overlay?.vxlan?.isolate ?? true) | ||||
| 		cfg.services['l2-tunnel'].config.forward_ports = [ "gateway" ]; | ||||
| } | ||||
|  | ||||
| system('rm /tmp/unet.*.json'); | ||||
| let filename = '/tmp/unet.' + time() + '.json'; | ||||
|  | ||||
| files.add_named(filename, cfg); | ||||
|  | ||||
| include('../interface/firewall.uc', { name: 'unet', ipv4_mode: true, ipv6_mode: true, interface: { role: 'upstream' }, networks: [ 'unet' ] }); | ||||
| %} | ||||
|  | ||||
|  | ||||
| # Wireguard Overlay Configuration | ||||
| set network.unet=interface | ||||
| set network.unet.proto=unet | ||||
| set network.unet.device=unet | ||||
| set network.unet.file={{ s(filename) }} | ||||
| set network.unet.key={{ s(wireguard_overlay.private_key) }} | ||||
| set network.unet.domain=unet | ||||
| set network.unet.ip4table='{{ routing_table.get('wireguard_overlay') }}' | ||||
| {% if (vxlan): %} | ||||
| set network.unet.tunnels='vx-unet=l2-tunnel' | ||||
|  | ||||
| add firewall rule | ||||
| set firewall.@rule[-1].name='Allow-VXLAN-unet' | ||||
| set firewall.@rule[-1].src='unet' | ||||
| set firewall.@rule[-1].proto='udp' | ||||
| set firewall.@rule[-1].target='ACCEPT' | ||||
| set firewall.@rule[-1].dest_port={{ wireguard_overlay?.vxlan?.port || 3457 }} | ||||
| {% endif %} | ||||
|  | ||||
| {% for (let ip in ips): %} | ||||
| add network route | ||||
| set network.@route[-1].interface='unet' | ||||
| set network.@route[-1].target={{ s(ip) }} | ||||
| set network.@route[-1].table='local' | ||||
| {% latency.add(ip, 4) %} | ||||
| {% endfor %} | ||||
| @@ -1,52 +0,0 @@ | ||||
| {% | ||||
| let interfaces = services.lookup_interfaces_by_ssids("captive"); | ||||
| let enable = length(interfaces); | ||||
| if (enable != 1) | ||||
| 	return; | ||||
|  | ||||
| for (let name, data in captive.interfaces) { | ||||
| 	let config = { | ||||
| 		name, | ||||
| 		devices: [], | ||||
| 		config: { | ||||
| 			default_class: 0, | ||||
| 			default_dns_class: 1, | ||||
| 			client_autoremove: false, | ||||
| 			class: [ | ||||
| 				{ | ||||
| 					index: 0, | ||||
| 					device_macaddr: split(name, '_')[0], | ||||
| 					fwmark: 1, | ||||
| 					fwmark_mask: 127 | ||||
| 				}, { | ||||
| 					index: 1, | ||||
| 					fwmark: 2, | ||||
| 					fwmark_mask: 127 | ||||
| 				} | ||||
| 			], | ||||
| 			whitelist: [ | ||||
| 	                        { | ||||
| 	                                "class": 1, | ||||
| 	                                "hosts": [ ], | ||||
| 					"address": [], | ||||
| 	                        } | ||||
| 	                ] | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	for (let iface in data.iface) | ||||
| 		push(config.devices, 'wlanc' + iface); | ||||
|  | ||||
| 	for (let fqdn in data.walled_garden_fqdn) | ||||
| 		push(config.config.whitelist[0].hosts, fqdn); | ||||
|  | ||||
| 	for (let ipaddr in data.walled_garden_ipaddr) | ||||
| 		push(config.config.whitelist[0].address, ipaddr); | ||||
|  | ||||
| 	let fs = require('fs'); | ||||
| 	let file = fs.open('/tmp/spotfilter-' + name + '.json', 'w'); | ||||
| 	file.write(config); | ||||
| 	file.close(); | ||||
| 	services.set_enabled("uhttpd", true) | ||||
| } | ||||
| %} | ||||
| @@ -1,17 +0,0 @@ | ||||
| {% if (state.switch.port_mirror && state.switch.port_mirror.monitor_ports && state.switch.port_mirror.analysis_port): %} | ||||
|  | ||||
| {% | ||||
|     let analysis = ethernet.lookup_by_select_ports([state.switch.port_mirror.analysis_port]); | ||||
|     ethernet.reserve_port(state.switch.port_mirror.analysis_port); | ||||
|     let mirrors = ethernet.lookup_by_select_ports(state.switch.port_mirror.monitor_ports); | ||||
| %} | ||||
|  | ||||
| # Switch  port-mirror configuration | ||||
|  | ||||
| set switch.mirror=port-mirror | ||||
| {%   for (let mirror in mirrors): %} | ||||
| add_list switch.mirror.monitor={{ s(mirror) }} | ||||
| {%   endfor %} | ||||
| set switch.mirror.analysis={{ s(analysis[0]) }} | ||||
|  | ||||
| {% endif %} | ||||
| @@ -1,149 +0,0 @@ | ||||
| {% | ||||
| 	let fs = require('fs'); | ||||
|  | ||||
| 	// reject the config if there is no valid upstream configuration | ||||
| 	if (!state.uuid) { | ||||
| 		warn('Configuration must contain a valid UUID. Rejecting whole file'); | ||||
| 		die('Configuration must contain a valid UUID. Rejecting whole file'); | ||||
| 	} | ||||
|  | ||||
| 	include('admin_ui.uc'); | ||||
|  | ||||
| 	// reject the config if there is no valid upstream configuration | ||||
| 	let upstream; | ||||
| 	for (let i, interface in state.interfaces) { | ||||
| 		if (interface.role != 'upstream') | ||||
| 			continue; | ||||
| 		upstream = interface; | ||||
| 	} | ||||
|  | ||||
| 	if (!upstream) { | ||||
| 		warn('Configuration must contain at least one valid upstream interface. Rejecting whole file'); | ||||
| 		die('Configuration must contain at least one valid upstream interface. Rejecting whole file'); | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	for (let i, interface in state.interfaces) | ||||
| 		interface.index = i; | ||||
|  | ||||
| 	/* find out which vlans are used and which should be assigned dynamically */ | ||||
| 	let vlans = []; | ||||
| 	for (let i, interface in state.interfaces) | ||||
| 		if (ethernet.has_vlan(interface)) | ||||
| 			push(vlans, interface.vlan.id); | ||||
| 		else | ||||
| 			interface.vlan = { id: 0}; | ||||
|  | ||||
| 	// populate the broad-band profile if present. This needs to happen after the default vlans  | ||||
| 	// and before the dynamic vlan are assigned | ||||
| 	let profile = local_profile.get(); | ||||
| 	if (profile && profile.broadband) | ||||
| 		include('broadband.uc', { broadband: profile.broadband }); | ||||
|  | ||||
| 	let vid = 4090; | ||||
| 	function next_free_vid() { | ||||
| 		while (index(vlans, vid) >= 0) | ||||
| 			vid--; | ||||
| 		return vid--; | ||||
| 	} | ||||
|  | ||||
| 	/* dynamically assign vlan ids to all interfaces that have none yet */ | ||||
| 	for (let i, interface in state.interfaces) | ||||
| 		if (!interface.vlan.id) | ||||
| 			interface.vlan.dyn_id = next_free_vid(); | ||||
|  | ||||
| 	/* dynamically assign vlans to all swconfig ports */ | ||||
| 	let swconfig = false; | ||||
| 	for (let k, port in ethernet.ports) { | ||||
| 		if (port.swconfig == null) | ||||
| 			continue; | ||||
| 		port.vlan = next_free_vid(); | ||||
| 		port.switch = capab.switch_ports[port.netdev]; | ||||
| 		port.netdev += '.' + port.vlan; | ||||
| 		swconfig = true; | ||||
| 	} | ||||
|  | ||||
| 	if (swconfig) { | ||||
| 		ethernet.swconfig = {}; | ||||
| 		for (let k, port in ethernet.ports) { | ||||
| 			if (!port.switch) | ||||
| 				continue; | ||||
| 			ethernet.swconfig[port.netdev] = port; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	include('base.uc'); | ||||
|  | ||||
| 	if (state.unit) | ||||
| 		include('unit.uc', { location: '/unit', unit: state.unit }); | ||||
|  | ||||
| 	if (!state.services) | ||||
|                 state.services = {}; | ||||
|  | ||||
| 	for (let service in services.lookup_services()) | ||||
| 		tryinclude('services/' + service + '.uc', { | ||||
| 			location: '/services/' + service, | ||||
| 			[service]: state.services[service] || {} | ||||
| 		}); | ||||
|  | ||||
| 	if (!state.metrics) | ||||
| 		state.metrics = {}; | ||||
|  | ||||
| 	let file = fs.open('/etc/events.json', 'r'); | ||||
| 	let events = []; | ||||
| 	if (file) { | ||||
| 		try { | ||||
| 			events = json(file.read('all')); | ||||
| 		} catch(e) { | ||||
|  | ||||
| 		} | ||||
| 		file.close(); | ||||
| 	} | ||||
| 	for (let metric in services.lookup_metrics()) | ||||
| 		tryinclude('metric/' + metric + '.uc', { | ||||
| 			location: '/metric/' + metric, | ||||
| 			[metric]: state.metrics[metric] || {}, | ||||
| 			events | ||||
| 		}); | ||||
|  | ||||
| 	if (state.switch) | ||||
| 		tryinclude('switch.uc', { | ||||
| 			location: '/switch/' | ||||
| 		}); | ||||
|  | ||||
| 	for (let i, ports in state.ethernet) | ||||
| 		include('ethernet.uc', { location: '/ethernet/' + i, ports }); | ||||
|  | ||||
| 	for (let i, radio in state.radios) | ||||
| 		include('radio.uc', { location: '/radios/' + i, radio }); | ||||
|  | ||||
| 	function iterate_interfaces(role) { | ||||
| 		for (let i, interface in state.interfaces) { | ||||
| 			if (interface.role != role) | ||||
| 				continue; | ||||
| 			include('interface.uc', { location: '/interfaces/' + i, interface, vlans }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	iterate_interfaces("upstream"); | ||||
| 	iterate_interfaces("downstream"); | ||||
|  | ||||
| 	let fs = require('fs'); | ||||
| 	for (let name in fs.glob('/usr/share/ucentral/templates/third-party/*.uc')) { | ||||
| 		name = split(fs.basename(name), '.')[0]; | ||||
| 		let config = state.third_party ? state.third_party[name] : {}; | ||||
| 		tryinclude('third-party/' + name + '.uc', { | ||||
| 			location: '/third-party/' + name, | ||||
| 			[replace(name, '-', '_')]: config | ||||
| 		}); | ||||
| 	} | ||||
| 	services.set_enabled("usteer2", true); | ||||
|  | ||||
| 	include('spotfilter.uc'); | ||||
|  | ||||
| 	if (state.config_raw) | ||||
| 		include("config_raw.uc", { location: '/config_raw', config_raw: state.config_raw }); | ||||
|  | ||||
| 	latency.write(); | ||||
| 	services.set_enabled("bridger", false); | ||||
| %} | ||||
| @@ -1,19 +0,0 @@ | ||||
|  | ||||
| # Basic unit configuration | ||||
| {% if (unit.name): %} | ||||
| set system.@system[-1].description={{ s(unit.name) }} | ||||
| {% endif %} | ||||
| {% if (unit.hostname): %} | ||||
| set system.@system[-1].hostname={{ s(unit.hostname) }} | ||||
| {% endif %} | ||||
| {% if (unit.location): %} | ||||
| set system.@system[-1].notes={{ s(unit.location) }} | ||||
| {% endif %} | ||||
| {% if (unit.timezone): %} | ||||
| set system.@system[-1].timezone={{ s(unit.timezone) }} | ||||
| {% endif %} | ||||
| set system.@system[-1].leds_off={{ b(!unit.leds_active) }} | ||||
| {% | ||||
| shell.password(unit.random_password); | ||||
| services.set_enabled("led", true); | ||||
| %} | ||||
| @@ -1,118 +0,0 @@ | ||||
| #!/usr/bin/ucode | ||||
| push(REQUIRE_SEARCH_PATH, | ||||
| 	"/usr/lib/ucode/*.so", | ||||
| 	"/usr/share/ucentral/*.uc"); | ||||
|  | ||||
| let schemareader = require("schemareader"); | ||||
| let renderer = require("renderer"); | ||||
| let fs = require("fs"); | ||||
| let ubus = require("ubus").connect(); | ||||
|  | ||||
| let inputfile = fs.open(ARGV[0], "r"); | ||||
| let inputjson = json(inputfile.read("all")); | ||||
| let custom_config = (split(ARGV[0], ".")[0] != "/etc/ucentral/ucentral"); | ||||
|  | ||||
| let error = 0; | ||||
|  | ||||
| inputfile.close(); | ||||
| let logs = []; | ||||
|  | ||||
| function set_service_state(state) { | ||||
| 	for (let service, enable in renderer.services_state()) { | ||||
| 		if (enable != state) | ||||
| 			continue; | ||||
| 		printf("%s %s\n", service, enable ? "starting" : "stopping"); | ||||
| 		system(sprintf("/etc/init.d/%s %s", service, (enable || enable == 'early') ? "restart" : "stop")); | ||||
| 	} | ||||
| 	system("/etc/init.d/dnsmasq restart"); | ||||
| } | ||||
|  | ||||
| try { | ||||
| 	for (let cmd in [ 'rm -rf /tmp/ucentral', | ||||
| 			  'mkdir /tmp/ucentral', | ||||
| 			  'rm /tmp/dnsmasq.conf', | ||||
| 			  '/etc/init.d/spotfilter stop', | ||||
| 			  'touch /tmp/dnsmasq.conf' ]) | ||||
| 		system(cmd); | ||||
|  | ||||
| 	let state = schemareader.validate(inputjson, logs); | ||||
|  | ||||
| 	let batch = state ? renderer.render(state, logs) : ""; | ||||
|  | ||||
| 	if (state.strict && length(logs)) { | ||||
| 		push(logs, 'Rejecting config due to strict-mode validation'); | ||||
| 		state = null; | ||||
| 	} | ||||
|  | ||||
| 	fs.stdout.write("Log messages:\n" + join("\n", logs) + "\n\n"); | ||||
|  | ||||
| 	if (state) { | ||||
| 		fs.stdout.write("UCI batch output:\n" + batch + "\n"); | ||||
|  | ||||
| 		let outputjson = fs.open("/tmp/ucentral.uci", "w"); | ||||
| 		outputjson.write(batch); | ||||
| 		outputjson.close(); | ||||
|  | ||||
| 		for (let cmd in [ 'rm -rf /tmp/config-shadow', | ||||
| 				  'cp -r /etc/config-shadow /tmp' ]) | ||||
| 			system(cmd); | ||||
|  | ||||
| 		let apply = fs.popen("/sbin/uci -c /tmp/config-shadow batch", "w"); | ||||
| 		apply.write(batch); | ||||
| 		apply.close(); | ||||
|  | ||||
| 		renderer.write_files(logs); | ||||
|  | ||||
| 		set_service_state(false); | ||||
|  | ||||
| 		for (let cmd in [ 'uci -c /tmp/config-shadow commit', | ||||
| 				  'cp /tmp/config-shadow/* /etc/config/', | ||||
| 				  'rm -rf /tmp/config-shadow']) | ||||
| 			system(cmd); | ||||
|  | ||||
| 		set_service_state('early'); | ||||
|  | ||||
| 		ubus.call('state', 'reload'); | ||||
|  | ||||
| 		for (let cmd in [ 'reload_config', | ||||
| 				  '/etc/init.d/ratelimit restart', | ||||
| 				  '/etc/init.d/dnsmasq restart', | ||||
| 				  'ubus call state reload']) | ||||
| 			system(cmd); | ||||
|  | ||||
| 		if (!custom_config) { | ||||
| 			fs.unlink('/etc/ucentral/ucentral.active'); | ||||
| 			fs.symlink(ARGV[0], '/etc/ucentral/ucentral.active'); | ||||
| 		} | ||||
|  | ||||
| 		set_service_state(true); | ||||
| 	} else { | ||||
| 		error = 1; | ||||
| 	} | ||||
| 	if (!length(batch) || !state) | ||||
| 		error = 2; | ||||
| 	else if (length(logs)) | ||||
| 		error = 1; | ||||
| } | ||||
| catch (e) { | ||||
| 	error = 2; | ||||
| 	warn("Fatal error while generating UCI: ", e, "\n", e.stacktrace[0].context, "\n"); | ||||
| } | ||||
|  | ||||
| if (inputjson.uuid && inputjson.uuid > 1 && !custom_config) { | ||||
| 	let text = [ 'Success', 'Rejects', 'Failed' ]; | ||||
| 	let status = { | ||||
| 		error, | ||||
| 		text: text[error] || "Failed", | ||||
| 	}; | ||||
| 	if (length(logs)) | ||||
| 		status.rejected = logs; | ||||
|  | ||||
| 	ubus.call("ucentral", "result", { | ||||
| 		uuid: inputjson.uuid || 0, | ||||
| 		id: +ARGV[1] || 0, | ||||
| 		status, | ||||
| 	}); | ||||
| 	if (error > 1) | ||||
| 		exit(1); | ||||
| } | ||||
| @@ -1,79 +0,0 @@ | ||||
| let nl = require("nl80211"); | ||||
| let def = nl.const; | ||||
|  | ||||
| const NL80211_IFTYPE_STATION = 2; | ||||
| const NL80211_IFTYPE_AP = 3; | ||||
| const NL80211_IFTYPE_MESH_POINT = 7; | ||||
| let iftypes = { | ||||
| 	[NL80211_IFTYPE_STATION]: "station", | ||||
| 	[NL80211_IFTYPE_AP]: "ap", | ||||
| 	[NL80211_IFTYPE_MESH_POINT]: "mesh", | ||||
| }; | ||||
|  | ||||
| const NL80211_CHAN_WIDTH_20 = 1; | ||||
| const NL80211_CHAN_WIDTH_40 = 2; | ||||
| const NL80211_CHAN_WIDTH_80 = 3; | ||||
| const NL80211_CHAN_WIDTH_80P80 = 4; | ||||
| const NL80211_CHAN_WIDTH_160 = 5; | ||||
| const NL80211_CHAN_WIDTH_5 = 6; | ||||
| const NL80211_CHAN_WIDTH_10 = 7; | ||||
| let chwidth = { | ||||
| 	[NL80211_CHAN_WIDTH_20]: "20", | ||||
| 	[NL80211_CHAN_WIDTH_40]: "40", | ||||
| 	[NL80211_CHAN_WIDTH_80]: "80", | ||||
| 	[NL80211_CHAN_WIDTH_80P80]: "80p80", | ||||
| 	[NL80211_CHAN_WIDTH_160]: "160", | ||||
| 	[NL80211_CHAN_WIDTH_5]: "5", | ||||
| 	[NL80211_CHAN_WIDTH_10]: "10", | ||||
| }; | ||||
|  | ||||
| function freq2channel(freq) { | ||||
| 	if (freq == 2484) | ||||
| 		return 14; | ||||
| 	else if (freq < 2484) | ||||
| 		return (freq - 2407) / 5; | ||||
| 	else if (freq >= 4910 && freq <= 4980) | ||||
| 		return (freq - 4000) / 5; | ||||
| 	else if(freq >= 56160 + 2160 * 1 && freq <= 56160 + 2160 * 6) | ||||
| 		return (freq - 56160) / 2160; | ||||
| 	else if (freq >= 5955 && freq <= 7115) | ||||
| 		return (freq - 5950) / 5; | ||||
| 	else | ||||
| 		return (freq - 5000) / 5; | ||||
| } | ||||
|  | ||||
| function wif_get(wdev) { | ||||
|         let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP); | ||||
|  | ||||
|         if (res === false) | ||||
|                 warn("Unable to lookup interfaces: " + nl.error() + "\n"); | ||||
|  | ||||
|         return res; | ||||
| } | ||||
|  | ||||
| function lookup_wifs() { | ||||
| 	let wifs = wif_get(); | ||||
| 	let rv = {}; | ||||
| 	for (let wif in wifs) { | ||||
| 		if (!wif.wiphy_freq || !iftypes[wif.iftype]) | ||||
| 			continue; | ||||
| 		let w = {}; | ||||
| 		w.ssid = wif.ssid; | ||||
| 		w.bssid = wif.mac; | ||||
| 		w.mode = iftypes[wif.iftype]; | ||||
| 		w.channel = []; | ||||
| 		w.frequency = []; | ||||
| 		w.tx_power = (wif.wiphy_tx_power_level / 100) || 0; | ||||
| 		for (let f in [ wif.wiphy_freq, wif.center_freq1, wif.center_freq2 ]) | ||||
| 			if (f) { | ||||
| 				push(w.channel, freq2channel(f)); | ||||
| 				push(w.frequency, f); | ||||
| 			} | ||||
| 		if (chwidth[wif.channel_width]) | ||||
| 			w.ch_width = chwidth[wif.channel_width]; | ||||
| 		rv[wif.ifname] = w; | ||||
| 	} | ||||
| 	return rv; | ||||
| } | ||||
|  | ||||
| return lookup_wifs(); | ||||
| @@ -1,40 +0,0 @@ | ||||
| let nl = require("nl80211"); | ||||
| let def = nl.const; | ||||
|  | ||||
| const NL80211_IFTYPE_MESH_POINT = 7; | ||||
|  | ||||
| function wif_get(wdev) { | ||||
|         let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP); | ||||
|  | ||||
|         if (res === false) | ||||
|                 warn("Unable to lookup interfaces: " + nl.error() + "\n"); | ||||
|  | ||||
|         return res; | ||||
| } | ||||
|  | ||||
| function lookup_mesh() { | ||||
| 	let wifs = wif_get(); | ||||
| 	let rv = {}; | ||||
| 	for (let wif in wifs) { | ||||
| 		if (!wif.wiphy_freq || wif.iftype != NL80211_IFTYPE_MESH_POINT) | ||||
| 			continue; | ||||
| 		let w = []; | ||||
| 		let params = { dev: wif.ifname }; | ||||
| 		let mpath = nl.request(def.NL80211_CMD_GET_MPATH, def.NLM_F_DUMP, params); | ||||
| 		for (let path in mpath) { | ||||
| 			push(w, { | ||||
| 				destinantion: path.mac, | ||||
| 				next_hop: path.mpath_next_hop, | ||||
| 				metric: path.mpath_info.metric, | ||||
| 				expire: path.mpath_info.expire, | ||||
| 				discovery_timeout: path.mpath_info.discovery_timeout, | ||||
| 				discovery_retries: path.mpath_info.discovery_retries, | ||||
| 				hop_count: path.mpath_info.hop_count, | ||||
| 			}); | ||||
| 		} | ||||
| 		rv[wif.ifname] = w; | ||||
| 	} | ||||
| 	return rv; | ||||
| } | ||||
|  | ||||
| return lookup_mesh(); | ||||
| @@ -1,170 +0,0 @@ | ||||
| let fs = require('fs'); | ||||
| let uci = require("uci"); | ||||
| let cursor = uci ? uci.cursor() : null; | ||||
| let nl = require("nl80211"); | ||||
| let def = nl.const; | ||||
|  | ||||
| function freq2channel(freq) { | ||||
| 	if (freq == 2484) | ||||
| 		return 14; | ||||
| 	else if (freq < 2484) | ||||
| 		return (freq - 2407) / 5; | ||||
| 	else if (freq >= 4910 && freq <= 4980) | ||||
| 		return (freq - 4000) / 5; | ||||
| 	else if(freq >= 56160 + 2160 * 1 && freq <= 56160 + 2160 * 6) | ||||
| 		return (freq - 56160) / 2160; | ||||
| 	else if (freq >= 5955 && freq <= 7115) | ||||
| 		return (freq - 5950) / 5; | ||||
| 	else | ||||
| 		return (freq - 5000) / 5; | ||||
| } | ||||
|  | ||||
| function phy_get(wdev) { | ||||
|         let res = nl.request(def.NL80211_CMD_GET_WIPHY, def.NLM_F_DUMP, { split_wiphy_dump: true }); | ||||
|  | ||||
|         if (res === false) | ||||
|                 warn("Unable to lookup phys: " + nl.error() + "\n"); | ||||
|  | ||||
|         return res; | ||||
| } | ||||
|  | ||||
| let paths = {}; | ||||
|  | ||||
| function add_path(path, phy, index) { | ||||
| 	if (!phy) | ||||
| 		return; | ||||
| 	phy = fs.basename(phy); | ||||
| 	paths[phy] = path; | ||||
| 	if (index) | ||||
| 		paths[phy] += '+' + index; | ||||
| } | ||||
|  | ||||
| function lookup_paths() { | ||||
| 	let wireless = cursor.get_all('wireless'); | ||||
| 	for (let k, section in wireless) { | ||||
| 		if (section['.type'] != 'wifi-device' || !section.path) | ||||
| 			continue; | ||||
| 		let phys = fs.glob(sprintf('/sys/devices/%s/ieee80211/phy*', section.path)); | ||||
| 		if (!length(phys)) | ||||
| 			phys = fs.glob(sprintf('/sys/devices/platform/%s/ieee80211/phy*', section.path)); | ||||
| 		if (!length(phys)) | ||||
| 			continue; | ||||
| 		sort(phys); | ||||
| 		let index = 0; | ||||
| 		for (let phy in phys) | ||||
| 			add_path(section.path, phy, index++); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function get_hwmon(phy) { | ||||
| 	let hwmon = fs.glob(sprintf('/sys/class/ieee80211/%s/hwmon*/temp*_input', phy)); | ||||
| 	if (!hwmon) | ||||
| 		return 0; | ||||
| 	let file = fs.open(hwmon[0], 'r'); | ||||
| 	if (!file) | ||||
| 		return 0; | ||||
| 	let temp = +file.read('all'); | ||||
| 	file.close(); | ||||
| 	return temp; | ||||
| } | ||||
|  | ||||
| function lookup_phys() { | ||||
| 	lookup_paths(); | ||||
|  | ||||
| 	let phys = phy_get(); | ||||
| 	let ret = {}; | ||||
| 	for (let phy in phys) { | ||||
| 		let phyname = 'phy' + phy.wiphy; | ||||
| 		let path = paths[phyname]; | ||||
| 		if (!path) | ||||
| 			continue; | ||||
|  | ||||
| 		let p = {}; | ||||
| 		let temp = get_hwmon('phy' + phy.wiphy); | ||||
| 		if (temp) | ||||
| 			p.temperature = temp / 1000; | ||||
|  | ||||
| 		p.tx_ant = phy.wiphy_antenna_tx; | ||||
| 		p.rx_ant = phy.wiphy_antenna_rx; | ||||
| 		p.tx_ant_avail = phy.wiphy_antenna_avail_tx; | ||||
| 		p.rx_ant_avail = phy.wiphy_antenna_avail_rx; | ||||
| 		p.frequencies = []; | ||||
| 		p.channels = []; | ||||
| 		p.dfs_channels = []; | ||||
| 		p.htmode = []; | ||||
| 		p.band = []; | ||||
| 		for (let band in phy.wiphy_bands) { | ||||
| 			for (let freq in band?.freqs) { | ||||
| 				if (freq.disabled) | ||||
| 					continue; | ||||
| 				push(p.frequencies, freq.freq); | ||||
| 				push(p.channels, freq2channel(freq.freq)); | ||||
| 				if (freq.radar) | ||||
| 					push(p.dfs_channels, freq2channel(freq.freq)); | ||||
| 				if (freq.freq >= 6000) | ||||
| 					push(p.band, '6G'); | ||||
| 				else if (freq.freq <= 2484) | ||||
| 					push(p.band, '2G'); | ||||
| 				else if (freq.freq >= 5160 && freq.freq <= 5885) | ||||
| 					push(p.band, '5G'); | ||||
| 			} | ||||
| 			if (band?.ht_capa) { | ||||
| 				p.ht_capa = band.ht_capa; | ||||
| 				push(p.htmode, 'HT20'); | ||||
| 				if (band.ht_capa & 0x2) | ||||
| 					push(p.htmode, 'HT40'); | ||||
| 			} | ||||
| 			if (band?.vht_capa) { | ||||
| 				p.vht_capa = band.vht_capa; | ||||
| 				push(p.htmode, 'VHT20', 'VHT40', 'VHT80'); | ||||
| 				let chwidth = (band?.vht_capa >> 2) & 0x3; | ||||
| 				switch(chwidth) { | ||||
| 				case 2: | ||||
| 					push(p.htmode, 'VHT80+80'); | ||||
| 					/* fall through */ | ||||
| 				case 1: | ||||
| 					push(p.htmode, 'VHT160'); | ||||
| 				} | ||||
| 			} | ||||
| 			for (let iftype in band?.iftype_data) { | ||||
| 				if (iftype.iftypes?.ap) { | ||||
| 					p.he_phy_capa = iftype?.he_cap_phy; | ||||
| 					p.he_mac_capa = iftype?.he_cap_mac; | ||||
| 					push(p.htmode, 'HE20'); | ||||
| 					let chwidth = (iftype?.he_cap_phy[0] || 0) & 0xff; | ||||
| 					if (chwidth & 0x2 || chwidth & 0x4) | ||||
| 						push(p.htmode, 'HE40'); | ||||
| 					if (chwidth & 0x4) | ||||
| 						push(p.htmode, 'HE80'); | ||||
| 					if (chwidth & 0x8 || chwidth & 0x10) | ||||
| 						push(p.htmode, 'HE160'); | ||||
| 					if (chwidth & 0x10) | ||||
| 						push(p.htmode, 'HE80+80'); | ||||
| 					if (iftype.eht_cap_phy) { | ||||
| 						p.eht_phy_capa = iftype?.eht_cap_phy; | ||||
| 						p.eht_mac_capa = iftype?.eht_cap_mac; | ||||
| 						push(p.htmode, 'EHT20'); | ||||
| 						if (chwidth & 0x2 || chwidth & 0x4) | ||||
| 							push(p.htmode, 'EHT40'); | ||||
| 						if (chwidth & 0x4) | ||||
| 							push(p.htmode, 'EHT80'); | ||||
| 						if (chwidth & 0x8 || chwidth & 0x10) | ||||
| 							push(p.htmode, 'EHT160'); | ||||
| 						if (chwidth & 0x10) | ||||
| 							push(p.htmode, 'EHT80+80'); | ||||
|                                                 if ('6G' in p.band) | ||||
|                                                         push(p.htmode, 'EHT320'); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		p.band = uniq(p.band); | ||||
| 		if (!length(p.dfs_channels)) | ||||
| 			delete p.dfs_channels; | ||||
| 		ret[path] = p; | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| return lookup_phys(); | ||||
| @@ -1,103 +0,0 @@ | ||||
| let nl = require("nl80211"); | ||||
| let def = nl.const; | ||||
|  | ||||
|  | ||||
| function parse_bitrate(info) { | ||||
| 	let rate = { | ||||
| 		bitrate: (info.bitrate32 || info.bitrate) * 100 | ||||
| 	}; | ||||
|  | ||||
| 	if (info.short_gi) | ||||
| 		rate.sgi = true; | ||||
|  | ||||
| 	if (info.mcs) { | ||||
| 		rate.ht = true; | ||||
| 		rate.mcs = info.mcs; | ||||
| 	} | ||||
| 	else if (info.vht_mcs) { | ||||
| 		rate.vht = true; | ||||
| 		rate.mcs = info.vht_mcs; | ||||
| 		rate.nss = info.vht_nss; | ||||
| 	} | ||||
| 	else if (info.he_mcs) { | ||||
| 		rate.he = true; | ||||
| 		rate.mcs = info.he_mcs; | ||||
| 		rate.nss = info.he_nss; | ||||
| 		rate.he_gi = info.he_gi; | ||||
| 		rate.he_dcm = info.he_dcm; | ||||
| 	} | ||||
|  | ||||
| 	if (info.width_40) | ||||
| 		rate.chwidth = 40; | ||||
| 	else if (info.width_80) | ||||
| 		rate.chwidth = 80; | ||||
| 	else if (info.width_80p80) | ||||
| 		rate.chwidth = 8080; | ||||
| 	else if (info.width_160) | ||||
| 		rate.chwidth = 160; | ||||
| 	else if (info.width_10) | ||||
| 		rate.chwidth = 10; | ||||
| 	else if (info.width_5) | ||||
| 		rate.chwidth = 5; | ||||
| 	else | ||||
| 		rate.chwidth = 20; | ||||
| 	return rate; | ||||
| } | ||||
|  | ||||
| function iface_assoclist(wif) { | ||||
| 	let params = { dev: wif.ifname }; | ||||
| 	let res = nl.request(def.NL80211_CMD_GET_STATION, def.NLM_F_DUMP, params); | ||||
|  | ||||
| 	if (res === false) { | ||||
| 		warn("Unable to lookup associations: " + nl.error() + "\n"); | ||||
| 		return []; | ||||
| 	} | ||||
| 	let assocdev = []; | ||||
| 	for (let sta in res) { | ||||
| 		let assoc = { | ||||
| 			bssid: wif.mac, | ||||
| 			station: sta.mac, | ||||
| 			connected: +sta.sta_info?.connected_time, | ||||
| 			inactive: +sta.sta_info?.inactive_time / 1000, | ||||
| 			tx_duration: +sta.sta_info?.tx_duration, | ||||
| 			rx_duration: +sta.sta_info?.rx_duration, | ||||
| 			rssi: +sta.sta_info?.signal, | ||||
| 			ack_signal: +sta.sta_info?.ack_signal, | ||||
| 			ack_signal_avg: +sta.sta_info?.ack_signal_avg, | ||||
| 			rx_packets: +sta.sta_info?.rx_packets, | ||||
| 			tx_packets: +sta.sta_info?.tx_packets, | ||||
| 			rx_bytes: +sta.sta_info?.rx_bytes64, | ||||
| 			tx_bytes: +sta.sta_info?.tx_bytes64, | ||||
| 			tx_retries: +sta.sta_info?.tx_retries, | ||||
| 			tx_failed: +sta.sta_info?.tx_failed, | ||||
| 			rx_rate: parse_bitrate(sta.sta_info?.rx_bitrate || {}), | ||||
| 			tx_rate: parse_bitrate(sta.sta_info?.tx_bitrate || {}), | ||||
| 		}; | ||||
| 		if (global.tid_stats) | ||||
| 			assoc.tid_stats = sta.sta_info?.tid_stats || []; | ||||
| 		push(assocdev, assoc); | ||||
| 	}; | ||||
| 	return assocdev; | ||||
| } | ||||
|  | ||||
| function wif_get(wdev) { | ||||
|         let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP); | ||||
|  | ||||
|         if (res === false) | ||||
|                 warn("Unable to lookup interfaces: " + nl.error() + "\n"); | ||||
|  | ||||
|         return res; | ||||
| } | ||||
|  | ||||
| function lookup_stations() { | ||||
| 	let rv = {}; | ||||
| 	let wifs = wif_get(); | ||||
| 	for (let wif in wifs) { | ||||
| 		let assoc = iface_assoclist(wif); | ||||
| 		if (length(assoc)) | ||||
| 			rv[wif.ifname] = assoc; | ||||
| 	} | ||||
| 	return rv; | ||||
| } | ||||
|  | ||||
| return lookup_stations(); | ||||
| @@ -1,36 +0,0 @@ | ||||
| let nl = require("nl80211"); | ||||
| let def = nl.const; | ||||
| let rv = { survey: [] }; | ||||
| //let frequency = 5660; | ||||
|  | ||||
| function survey_get(dev) { | ||||
|         let res = nl.request(def.NL80211_CMD_GET_SURVEY, def.NLM_F_DUMP, { dev }); | ||||
|  | ||||
|         if (res === false) | ||||
|                 warn("Unable to lookup survey: " + nl.error() + "\n"); | ||||
|  | ||||
|         return res; | ||||
| } | ||||
|  | ||||
| function wif_get(wdev) { | ||||
| 	let res = nl.request(def.NL80211_CMD_GET_INTERFACE, def.NLM_F_DUMP); | ||||
|  | ||||
| 	if (res === false) | ||||
| 		warn("Unable to lookup interfaces: " + nl.error() + "\n"); | ||||
|  | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| function lookup_survey() { | ||||
| 	let wifs = wif_get(); | ||||
|  | ||||
| 	for (let wif in wifs) | ||||
| 		for (let survey in survey_get(wif.ifname)) | ||||
| 			if (!frequency || survey.survey_info.frequency == frequency) | ||||
| 				if (survey.survey_info?.time) | ||||
| 					push(rv.survey, survey.survey_info); | ||||
| } | ||||
|  | ||||
| lookup_survey(); | ||||
| //printf('%.J\n', rv); | ||||
| return rv; | ||||
							
								
								
									
										5
									
								
								schema.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								schema.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| { | ||||
| 	"major": 4, | ||||
| 	"minor": 1, | ||||
| 	"patch": 0  | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| description: | ||||
|   This section is used to define templates that can be referenced by a | ||||
|   configuration. This avoids duplication of data. A RADIUS server can be | ||||
|   defined here for example and then referenced by several SSIDs. | ||||
| type: object | ||||
| properties: | ||||
|   wireless-encryption: | ||||
|     type: object | ||||
|     description: | ||||
|       A dictionary of wireless encryption templates which can be referenced | ||||
|       by the corresponding property name. | ||||
|     patternProperties: | ||||
|       ".+": | ||||
|         $ref: "https://ucentral.io/schema/v1/interface/ssid/encryption/" | ||||
|         additionalProperties: false | ||||
| @@ -18,6 +18,13 @@ properties: | ||||
|       - LAN* | ||||
|       - WAN* | ||||
|       - "*" | ||||
|   name: | ||||
|     description: | ||||
|       This is a free text field, stating the administrative name of the | ||||
|       port. It may contain spaces and special characters, not exceeding 64 characters. | ||||
|     type: string | ||||
|     examples: | ||||
|     - cloud_uplink_port | ||||
|   speed: | ||||
|     description: | ||||
|       The link speed that shall be forced. | ||||
| @@ -30,7 +37,10 @@ properties: | ||||
|     - 5000 | ||||
|     - 10000 | ||||
|     - 25000 | ||||
|     - 40000 | ||||
|     - 50000 | ||||
|     - 100000 | ||||
|     - 200000 | ||||
|     default: 1000 | ||||
|   duplex: | ||||
|     description: | ||||
| @@ -161,42 +171,369 @@ properties: | ||||
|         type: integer | ||||
|         minimum: 1 | ||||
|         maximum: 4094 | ||||
|   port-isolation: | ||||
|     description: | ||||
|       This section describes the per-port specific port-isolation matrix (to which ports selected port can forward traffic to) configuration. | ||||
|       Omitting this configuration completely fully disables any port-isolation configuration on this given port. | ||||
|     type: object | ||||
|     properties: | ||||
|       sessions: | ||||
|         description: | ||||
|           Allow selected port to forward traffic in the provided session-based format. | ||||
|         type: array | ||||
|         items: | ||||
|           type: object | ||||
|           properties: | ||||
|             id: | ||||
|               description: | ||||
|                 Session id to configure. | ||||
|       mac-address-bypass: | ||||
|         description: Enables bypass when a device does not support 802.1X authentication (e.g., printers, IP phones) | ||||
|         type: boolean | ||||
|       mac-address-bypass-timeout-minutes: | ||||
|         description: Defines the time period (in minutes) for which a MAC address is allowed access to the network without requiring reauthentication, after being authenticated or allowed via MAC Authentication Bypass (MAB). | ||||
|         type: integer | ||||
|             uplink: | ||||
|   trunk-group: | ||||
|     description: Associates this port to a trunk or a port-channel. | ||||
|     type: integer | ||||
|     minimum: 1 | ||||
|     maximum: 64 | ||||
|   lacp-config: | ||||
|     description: | ||||
|                 Configuration object for uplink interface(s) | ||||
|       This section describes the 802.3ad Link Aggregation Control Protocol (LACP) configuration for the current interface. | ||||
|     type: object | ||||
|     properties: | ||||
|                 interface-list: | ||||
|       lacp-enable: | ||||
|         description: | ||||
|                     List of interfaces (either physical or trunk ports) | ||||
|           Enables 802.3ad Link Aggregation Control Protocol (LACP) for the current interface. | ||||
|         type: boolean | ||||
|         default: false | ||||
|       lacp-role: | ||||
|         description: | ||||
|           Configures the port LACP role as actor or partner. | ||||
|         type: string | ||||
|         enum: | ||||
|         - actor | ||||
|         - partner | ||||
|         default: actor | ||||
|       lacp-mode: | ||||
|         description: | ||||
|           Configures the LACP negotiation activity mode as active or passive. | ||||
|         type: string | ||||
|         enum: | ||||
|         - active | ||||
|         - passive | ||||
|         default: passive | ||||
|       lacp-port-admin-key: | ||||
|         description: | ||||
|           Configures the port's LACP administration key. | ||||
|         type: integer | ||||
|         minimum: 1 | ||||
|         maximum: 65535 | ||||
|         default: 1 | ||||
|       lacp-port-priority: | ||||
|         description: | ||||
|           Configures the LACP port priority. | ||||
|         type: integer | ||||
|         minimum: 1 | ||||
|         maximum: 65535 | ||||
|         default: 32768 | ||||
|       lacp-system-priority: | ||||
|         description: | ||||
|           Configures the LACP System priority. | ||||
|         type: integer | ||||
|         minimum: 1 | ||||
|         maximum: 65535 | ||||
|         default: 32768 | ||||
|       lacp-pchan-admin-key: | ||||
|         description: | ||||
|           Configures the port channel's LACP administration key (optional). | ||||
|         type: integer | ||||
|         minimum: 1 | ||||
|         maximum: 65535 | ||||
|       lacp-timeout: | ||||
|         description: | ||||
|           Configures the timeout to wait for the next LACP data unit. | ||||
|         type: string | ||||
|         enum: | ||||
|         - short | ||||
|         - long | ||||
|         default: long | ||||
|   lldp-interface-config: | ||||
|     type: object | ||||
|     description: Configurations of LLDP on a specified interface. | ||||
|     properties: | ||||
|       lldp-admin-status: | ||||
|         type: string | ||||
|         description: Enables LLDP transmit, receive, or transmit and receive mode on the specified port. | ||||
|         enum: | ||||
|           - rx | ||||
|           - tx | ||||
|           - rx-tx | ||||
|       lldp-basic-tlv-mgmt-ip-v4: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise the management address for this device. | ||||
|         default: true | ||||
|       lldp-basic-tlv-mgmt-ip-v6: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise the management IPv6 address for this device, if available. | ||||
|         default: false | ||||
|       lldp-basic-tlv-port-descr: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its port description. | ||||
|         default: true | ||||
|       lldp-basic-tlv-sys-capab: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its system capabilities. | ||||
|         default: true | ||||
|       lldp-basic-tlv-sys-descr: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise the system description. | ||||
|         default: true | ||||
|       lldp-basic-tlv-sys-name: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its system name. | ||||
|         default: true | ||||
|       lldp-dot1-tlv-proto-ident: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise the supported protocols. | ||||
|         default: true | ||||
|       lldp-dot1-tlv-proto-vid: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise port-based protocol-related VLAN information. | ||||
|         default: true | ||||
|       lldp-dot1-tlv-pvid: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its default Native VLAN ID (PVID). | ||||
|         default: true | ||||
|       lldp-dot1-tlv-vlan-name: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its VLAN name. | ||||
|         default: true | ||||
|       lldp-dot3-tlv-link-agg: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its link aggregation capabilities. | ||||
|         default: true | ||||
|       lldp-dot3-tlv-mac-phy: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its MAC and physical layer specifications. | ||||
|         default: true | ||||
|       lldp-dot3-tlv-max-frame: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its maximum frame size. | ||||
|         default: true | ||||
|       lldp-dot3-tlv-poe: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-enabled port to advertise its Power-over-Ethernet capabilities. | ||||
|         default: true | ||||
|       lldp-med-location-civic-addr: | ||||
|         type: object | ||||
|         description: Configures an LLDP-MED-enabled port to advertise its location identification details. | ||||
|         properties: | ||||
|           lldp-med-location-civic-addr-admin-status: | ||||
|             type: boolean | ||||
|             description: Enables or disables the advertisement of this TLV. | ||||
|             default: false | ||||
|           lldp-med-location-civic-country-code: | ||||
|             type: string | ||||
|             description: Configure the two-letter ISO 3166 country code in capital ASCII letters. | ||||
|           lldp-med-location-civic-device-type: | ||||
|             type: integer | ||||
|             description: The type of device to which the location applies. | ||||
|           lldp-med-location-civic-ca: | ||||
|             description: The list of LLDP MED Location CA Types to advertise the physical location of the device, that is the city, street number, building and room information. | ||||
|             type: array | ||||
|             items: | ||||
|                     type: string | ||||
|             downlink: | ||||
|               description: | ||||
|                 Configuration object for downlink interface(s) | ||||
|                 type: object | ||||
|                 properties: | ||||
|                 interface-list: | ||||
|                   description: | ||||
|                     List of interfaces (either physical or trunk ports) | ||||
|                   lldp-med-location-civic-ca-type: | ||||
|                       type: integer | ||||
|                       description: A one-octet descriptor of the data civic address value. | ||||
|                       maximum: 255 | ||||
|                       minimum: 0 | ||||
|                   lldp-med-location-civic-ca-value: | ||||
|                       type: string | ||||
|                       description: Description of a location. | ||||
|                       maxLength: 32 | ||||
|                       minLength: 1 | ||||
|       lldp-med-notification: | ||||
|         type: boolean | ||||
|         description: Enables the transmission of SNMP trap notifications about LLDP-MED changes. | ||||
|         default: false | ||||
|       lldp-med-tlv-ext-poe: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-MED-enabled port to advertise its extended Power over Ethernet configuration and usage information. | ||||
|         default: true | ||||
|       lldp-med-tlv-inventory: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-MED-enabled port to advertise its inventory identification details. | ||||
|         default: true | ||||
|       lldp-med-tlv-location: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-MED-enabled port to advertise its location identification details. | ||||
|         default: true | ||||
|       lldp-med-tlv-med-cap: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-MED-enabled port to advertise its Media Endpoint Device capabilities. | ||||
|         default: true | ||||
|       lldp-med-tlv-network-policy: | ||||
|         type: boolean | ||||
|         description: Configures an LLDP-MED-enabled port to advertise its network policy configuration. | ||||
|         default: true | ||||
|       lldp-notification: | ||||
|         type: boolean | ||||
|         description: Enables the transmission of SNMP trap notifications about LLDP changes. | ||||
|         default: false | ||||
|   ip-arp-inspect-port: | ||||
|     type: object | ||||
|     description: Configuration for ARP Inspection on specific interfaces or ports in the switch. | ||||
|     properties: | ||||
|       rate-limit-pps: | ||||
|         type: integer | ||||
|         description: Sets a rate limit (packets per second) for the ARP packets received on a port. Ensures that the port does not process ARP packets beyond the configured limit. | ||||
|         minimum: 0 | ||||
|         maximum: 65535 | ||||
|       trusted: | ||||
|         type: boolean | ||||
|         description: Configures the port as trusted, exempting it from ARP Inspection. Trusted ports bypass ARP validation checks. | ||||
|   rate-limit-port: | ||||
|     type: object | ||||
|     description: Configuration for ingress and egress rate limiting on a specific port (in kbps) | ||||
|     properties: | ||||
|       ingress-kbps: | ||||
|         type: integer | ||||
|         description: Sets the maximum allowed ingress (input) traffic rate for the port, in kilobits per second (kbps). | ||||
|         minimum: 64 | ||||
|         maximum: 1000000000 | ||||
|       egress-kbps: | ||||
|         type: integer | ||||
|         description: Sets the maximum allowed egress (output) traffic rate for the port, in kilobits per second (kbps). | ||||
|         minimum: 64 | ||||
|         maximum: 1000000000 | ||||
|   ip-source-guard-port: | ||||
|     type: object | ||||
|     description: Configuration of IP Source Guard (IPSG) on a physical interface in a Layer 2 switch. | ||||
|     properties: | ||||
|       rule: | ||||
|         type: string | ||||
|         description: Configures the switch to filter inbound traffic based on source IP address only,  | ||||
|           or source IP address and corresponding MAC address combined. | ||||
|         enum: | ||||
|           - sip | ||||
|           - sip-mac | ||||
|       mode: | ||||
|         type: string | ||||
|         description: Specifies the learning mode to use for validation, either MAC address table or ACL table.  | ||||
|           The system searches for source addresses in the specified table. | ||||
|         enum: | ||||
|           - mac | ||||
|           - acl | ||||
|       max-binding: | ||||
|         type: integer | ||||
|         description: Sets the maximum number of address entries that can be mapped to an interface  | ||||
|           in the binding table. Includes both static entries and dynamically learned entries  | ||||
|           via DHCP Snooping. | ||||
|         minimum: 1 | ||||
|         maximum: 65535 | ||||
|   acl: | ||||
|     description: A collection of access control entries that define the rules for filtering traffic through a network port. | ||||
|     type: array | ||||
|     items: | ||||
|       type: object | ||||
|       properties: | ||||
|         acl-inf-policy-preference: | ||||
|           description: Determines the priority of multiple ACL policies when more than one is applied to an interface, if any. | ||||
|           type: integer | ||||
|           minimum: 1 | ||||
|           maximum: 64 | ||||
|           default: 1 | ||||
|         acl-inf-policy-ingress: | ||||
|           description: Specifies the ACL policy that is applied to incoming traffic on an interface. | ||||
|           type: string | ||||
|           maxLength: 32 | ||||
|           minLength: 1 | ||||
|           examples: | ||||
|             - blacklisted-macs | ||||
|         acl-inf-counters-ingress: | ||||
|           description: Tracks the number and type of packets that match the ingress ACL rules on an interface. | ||||
|           type: boolean | ||||
|           default: false | ||||
|         acl-inf-policy-egress: | ||||
|           description: Specifies the ACL policy that is applied to outgoing traffic from an interface. | ||||
|           type: string | ||||
|           maxLength: 32 | ||||
|           minLength: 1 | ||||
|           examples: | ||||
|             - blacklisted-macs | ||||
|         acl-inf-counters-egress: | ||||
|           description: Tracks the number and type of packets that match the egress ACL rules on an interface. | ||||
|           type: boolean | ||||
|           default: false | ||||
|   voice-vlan-intf-config: | ||||
|     description: Configure the Voice VLAN feature at the interface level, allowing for VoIP traffic to be prioritized on this specific port. | ||||
|     type: object | ||||
|     properties: | ||||
|       voice-vlan-intf-mode: | ||||
|         description: Specify the mode of placing this port on the voice VLAN. | ||||
|         type: string | ||||
|         default: "auto" | ||||
|         enum: | ||||
|           - none | ||||
|           - manual | ||||
|           - auto | ||||
|       voice-vlan-intf-priority: | ||||
|         description: Define the Class of Service (CoS) priority for VoIP traffic passing through this port, ensuring higher priority over other traffic types. | ||||
|         type: integer | ||||
|         default: 6 | ||||
|         minimum: 0 | ||||
|         maximum: 6 | ||||
|       voice-vlan-intf-detect-voice: | ||||
|         description: Select the detection method for identifying VoIP traffic on this port, such as OUI-based detection or traffic pattern recognition. | ||||
|         type: string | ||||
|         default: "oui" | ||||
|         enum: | ||||
|           - oui | ||||
|           - lldp | ||||
|       voice-vlan-intf-security: | ||||
|         description: Enable or configure security filtering for VoIP traffic on the interface to protect against unauthorized devices. | ||||
|         type: boolean | ||||
|         default: false | ||||
|   dhcp-snoop-port: | ||||
|     description: Configuration for DHCP Snooping on a port level on a switch | ||||
|     type: object | ||||
|     properties: | ||||
|       dhcp-snoop-port-trust: | ||||
|         description: This parameter designates a switch port as ‘trusted’ for DHCP messages, meaning it can forward DHCP offers and acknowledgments, which is essential for connecting to legitimate DHCP servers | ||||
|         type: boolean | ||||
|         default: false | ||||
|       dhcp-snoop-port-client-limit: | ||||
|         description: It sets a limit on the number of DHCP clients that can be associated with a single port, helping to prevent a single port from exhausting the network’s IP address pool | ||||
|         type: integer | ||||
|         minimum: 1 | ||||
|       dhcp-snoop-port-circuit-id: | ||||
|         description: Specifies DHCP Option 82 circuit ID suboption information. Often including information like the interface number and VLAN ID, this can be useful for network management and troubleshooting | ||||
|         type: string | ||||
|         minLength: 1 | ||||
|         maxLength: 32 | ||||
|   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. | ||||
|   | ||||
| @@ -1,28 +0,0 @@ | ||||
| type: array | ||||
| items: | ||||
|   type: string | ||||
|   enum: | ||||
|   - CS0 | ||||
|   - CS1 | ||||
|   - CS2 | ||||
|   - CS3 | ||||
|   - CS4 | ||||
|   - CS5 | ||||
|   - CS6 | ||||
|   - CS7 | ||||
|   - AF11 | ||||
|   - AF12 | ||||
|   - AF13 | ||||
|   - AF21 | ||||
|   - AF22 | ||||
|   - AF23 | ||||
|   - AF31 | ||||
|   - AF32 | ||||
|   - AF33 | ||||
|   - AF41 | ||||
|   - AF42 | ||||
|   - AF43 | ||||
|   - DF | ||||
|   - EF | ||||
|   - VA | ||||
|   - LE | ||||
| @@ -1,12 +0,0 @@ | ||||
| type: object | ||||
| additionalProperties: false | ||||
| properties: | ||||
|   profile: | ||||
|     description: | ||||
|       Define a default profile that shall be used for the WMM behaviour of all SSIDs on | ||||
|       the device. | ||||
|     type: string | ||||
|     enum: | ||||
|     - enterprise | ||||
|     - rfc8325 | ||||
|     - 3gpp | ||||
| @@ -1,23 +0,0 @@ | ||||
| description: | ||||
|   Define the default WMM behaviour of all SSIDs on the device. Each access | ||||
|   category can be assigned a default class selector that gets used for packet | ||||
|   matching. | ||||
| type: object | ||||
| additionalProperties: false | ||||
| properties: | ||||
|   UP0: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP1: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP2: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP3: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP4: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP5: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP6: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
|   UP7: | ||||
|     $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/' | ||||
| @@ -19,10 +19,6 @@ properties: | ||||
|     format: uc-cidr6 | ||||
|     examples: | ||||
|     - fdca:1234:4567::/48 | ||||
|   wireless-multimedia: | ||||
|     anyOf: | ||||
|      - $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/table/' | ||||
|      - $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/profile/' | ||||
|   ipv4-blackhole: | ||||
|     description: | ||||
|       Define a list of non-interface specific BLACKHOLE (to-nowhere) routes. | ||||
| @@ -40,7 +36,7 @@ properties: | ||||
|         vrf: | ||||
|           description: | ||||
|             VRF id. | ||||
|           type: number | ||||
|           type: integer | ||||
|   ipv4-unreachable: | ||||
|     description: | ||||
|       Define a list of non-interface specific UNREACHABLE routes. | ||||
| @@ -58,4 +54,4 @@ properties: | ||||
|         vrf: | ||||
|           description: | ||||
|             VRF id. | ||||
|           type: number | ||||
|           type: integer | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user