mirror of
				https://github.com/Telecominfraproject/ols-ucentral-schema.git
				synced 2025-11-04 03:57:51 +00:00 
			
		
		
		
	Compare commits
	
		
			92 Commits
		
	
	
		
			fix_loop_d
			...
			v4.0.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,2 @@
 | 
				
			|||||||
/jsdoc.conf.json
 | 
					/jsdoc.conf.json
 | 
				
			||||||
docs/
 | 
					 | 
				
			||||||
node_modules
 | 
					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.
 | 
				
			||||||
							
								
								
									
										309
									
								
								capabilities/connect.capabilities.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								capabilities/connect.capabilities.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,309 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					      # 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
 | 
				
			||||||
 | 
					      # 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.pretty.json 0 1
 | 
				
			||||||
./merge-schema.py schema schema ucentral.yml ucentral.schema.full.json 0 0
 | 
					./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
 | 
					./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
 | 
					#./generate-example.uc > input.json
 | 
				
			||||||
mkdir -p docs
 | 
					 | 
				
			||||||
which generate-schema-doc > /dev/null
 | 
					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.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.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*
 | 
					      - LAN*
 | 
				
			||||||
      - WAN*
 | 
					      - 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:
 | 
					  speed:
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      The link speed that shall be forced.
 | 
					      The link speed that shall be forced.
 | 
				
			||||||
@@ -30,7 +37,10 @@ properties:
 | 
				
			|||||||
    - 5000
 | 
					    - 5000
 | 
				
			||||||
    - 10000
 | 
					    - 10000
 | 
				
			||||||
    - 25000
 | 
					    - 25000
 | 
				
			||||||
 | 
					    - 40000
 | 
				
			||||||
 | 
					    - 50000
 | 
				
			||||||
    - 100000
 | 
					    - 100000
 | 
				
			||||||
 | 
					    - 200000
 | 
				
			||||||
    default: 1000
 | 
					    default: 1000
 | 
				
			||||||
  duplex:
 | 
					  duplex:
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -161,42 +171,331 @@ properties:
 | 
				
			|||||||
        type: integer
 | 
					        type: integer
 | 
				
			||||||
        minimum: 1
 | 
					        minimum: 1
 | 
				
			||||||
        maximum: 4094
 | 
					        maximum: 4094
 | 
				
			||||||
  port-isolation:
 | 
					      mac-address-bypass:
 | 
				
			||||||
    description:
 | 
					        description: Enables bypass when a device does not support 802.1X authentication (e.g., printers, IP phones)
 | 
				
			||||||
      This section describes the per-port specific port-isolation matrix (to which ports selected port can forward traffic to) configuration.
 | 
					        type: boolean
 | 
				
			||||||
      Omitting this configuration completely fully disables any port-isolation configuration on this given port.
 | 
					      mac-address-bypass-timeout-minutes:
 | 
				
			||||||
    type: object
 | 
					        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).
 | 
				
			||||||
    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.
 | 
					 | 
				
			||||||
        type: integer
 | 
					        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:
 | 
					    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
 | 
					    type: object
 | 
				
			||||||
    properties:
 | 
					    properties:
 | 
				
			||||||
                interface-list:
 | 
					      lacp-enable:
 | 
				
			||||||
        description:
 | 
					        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
 | 
					            type: array
 | 
				
			||||||
            items:
 | 
					            items:
 | 
				
			||||||
                    type: string
 | 
					 | 
				
			||||||
            downlink:
 | 
					 | 
				
			||||||
              description:
 | 
					 | 
				
			||||||
                Configuration object for downlink interface(s)
 | 
					 | 
				
			||||||
                type: object
 | 
					                type: object
 | 
				
			||||||
                properties:
 | 
					                properties:
 | 
				
			||||||
                interface-list:
 | 
					                  lldp-med-location-civic-ca-type:
 | 
				
			||||||
                  description:
 | 
					                      type: integer
 | 
				
			||||||
                    List of interfaces (either physical or trunk ports)
 | 
					                      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
 | 
					    type: array
 | 
				
			||||||
    items:
 | 
					    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
 | 
					          type: string
 | 
				
			||||||
 | 
					          maxLength: 32
 | 
				
			||||||
 | 
					          minLength: 1
 | 
				
			||||||
 | 
					          examples:
 | 
				
			||||||
 | 
					            - blacklisted-macs
 | 
				
			||||||
 | 
					        acl-inf-counters-ingress:
 | 
				
			||||||
 | 
					          description: Tracks the number and type of packets that match the ingress ACL rules on an interface.
 | 
				
			||||||
 | 
					          type: boolean
 | 
				
			||||||
 | 
					          default: false
 | 
				
			||||||
 | 
					        acl-inf-policy-egress:
 | 
				
			||||||
 | 
					          description: Specifies the ACL policy that is applied to outgoing traffic from an interface.
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					          maxLength: 32
 | 
				
			||||||
 | 
					          minLength: 1
 | 
				
			||||||
 | 
					          examples:
 | 
				
			||||||
 | 
					            - blacklisted-macs
 | 
				
			||||||
 | 
					        acl-inf-counters-egress:
 | 
				
			||||||
 | 
					          description: Tracks the number and type of packets that match the egress ACL rules on an interface.
 | 
				
			||||||
 | 
					          type: boolean
 | 
				
			||||||
 | 
					          default: false
 | 
				
			||||||
 | 
					  voice-vlan-intf-config:
 | 
				
			||||||
 | 
					    description: Configure the Voice VLAN feature at the interface level, allowing for VoIP traffic to be prioritized on this specific port.
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      voice-vlan-intf-mode:
 | 
				
			||||||
 | 
					        description: Specify the mode of placing this port on the voice VLAN.
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "auto"
 | 
				
			||||||
 | 
					        enum:
 | 
				
			||||||
 | 
					          - none
 | 
				
			||||||
 | 
					          - manual
 | 
				
			||||||
 | 
					          - auto
 | 
				
			||||||
 | 
					      voice-vlan-intf-priority:
 | 
				
			||||||
 | 
					        description: Define the Class of Service (CoS) priority for VoIP traffic passing through this port, ensuring higher priority over other traffic types.
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
 | 
					        default: 6
 | 
				
			||||||
 | 
					        minimum: 0
 | 
				
			||||||
 | 
					        maximum: 6
 | 
				
			||||||
 | 
					      voice-vlan-intf-detect-voice:
 | 
				
			||||||
 | 
					        description: Select the detection method for identifying VoIP traffic on this port, such as OUI-based detection or traffic pattern recognition.
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: "oui"
 | 
				
			||||||
 | 
					        enum:
 | 
				
			||||||
 | 
					          - oui
 | 
				
			||||||
 | 
					          - lldp
 | 
				
			||||||
 | 
					      voice-vlan-intf-security:
 | 
				
			||||||
 | 
					        description: Enable or configure security filtering for VoIP traffic on the interface to protect against unauthorized devices.
 | 
				
			||||||
 | 
					        type: boolean
 | 
				
			||||||
 | 
					        default: false
 | 
				
			||||||
 | 
					  dhcp-snoop-port:
 | 
				
			||||||
 | 
					    description: Configuration for DHCP Snooping on a port level on a switch
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      dhcp-snoop-port-trust:
 | 
				
			||||||
 | 
					        description: This parameter designates a switch port as ‘trusted’ for DHCP messages, meaning it can forward DHCP offers and acknowledgments, which is essential for connecting to legitimate DHCP servers
 | 
				
			||||||
 | 
					        type: boolean
 | 
				
			||||||
 | 
					        default: false
 | 
				
			||||||
 | 
					      dhcp-snoop-port-client-limit:
 | 
				
			||||||
 | 
					        description: It sets a limit on the number of DHCP clients that can be associated with a single port, helping to prevent a single port from exhausting the network’s IP address pool
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
 | 
					        minimum: 1
 | 
				
			||||||
 | 
					      dhcp-snoop-port-circuit-id:
 | 
				
			||||||
 | 
					        description: Specifies DHCP Option 82 circuit ID suboption information. Often including information like the interface number and VLAN ID, this can be useful for network management and troubleshooting
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        minLength: 1
 | 
				
			||||||
 | 
					        maxLength: 32
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
type: array
 | 
					 | 
				
			||||||
items:
 | 
					 | 
				
			||||||
  type: string
 | 
					 | 
				
			||||||
  enum:
 | 
					 | 
				
			||||||
  - CS0
 | 
					 | 
				
			||||||
  - CS1
 | 
					 | 
				
			||||||
  - CS2
 | 
					 | 
				
			||||||
  - CS3
 | 
					 | 
				
			||||||
  - CS4
 | 
					 | 
				
			||||||
  - CS5
 | 
					 | 
				
			||||||
  - CS6
 | 
					 | 
				
			||||||
  - CS7
 | 
					 | 
				
			||||||
  - AF11
 | 
					 | 
				
			||||||
  - AF12
 | 
					 | 
				
			||||||
  - AF13
 | 
					 | 
				
			||||||
  - AF21
 | 
					 | 
				
			||||||
  - AF22
 | 
					 | 
				
			||||||
  - AF23
 | 
					 | 
				
			||||||
  - AF31
 | 
					 | 
				
			||||||
  - AF32
 | 
					 | 
				
			||||||
  - AF33
 | 
					 | 
				
			||||||
  - AF41
 | 
					 | 
				
			||||||
  - AF42
 | 
					 | 
				
			||||||
  - AF43
 | 
					 | 
				
			||||||
  - DF
 | 
					 | 
				
			||||||
  - EF
 | 
					 | 
				
			||||||
  - VA
 | 
					 | 
				
			||||||
  - LE
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
type: object
 | 
					 | 
				
			||||||
additionalProperties: false
 | 
					 | 
				
			||||||
properties:
 | 
					 | 
				
			||||||
  profile:
 | 
					 | 
				
			||||||
    description:
 | 
					 | 
				
			||||||
      Define a default profile that shall be used for the WMM behaviour of all SSIDs on
 | 
					 | 
				
			||||||
      the device.
 | 
					 | 
				
			||||||
    type: string
 | 
					 | 
				
			||||||
    enum:
 | 
					 | 
				
			||||||
    - enterprise
 | 
					 | 
				
			||||||
    - rfc8325
 | 
					 | 
				
			||||||
    - 3gpp
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
description:
 | 
					 | 
				
			||||||
  Define the default WMM behaviour of all SSIDs on the device. Each access
 | 
					 | 
				
			||||||
  category can be assigned a default class selector that gets used for packet
 | 
					 | 
				
			||||||
  matching.
 | 
					 | 
				
			||||||
type: object
 | 
					 | 
				
			||||||
additionalProperties: false
 | 
					 | 
				
			||||||
properties:
 | 
					 | 
				
			||||||
  UP0:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP1:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP2:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP3:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP4:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP5:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP6:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
  UP7:
 | 
					 | 
				
			||||||
    $ref: 'https://ucentral.io/schema/v1/globals/wireless-multimedia/class-selector/'
 | 
					 | 
				
			||||||
@@ -19,10 +19,6 @@ properties:
 | 
				
			|||||||
    format: uc-cidr6
 | 
					    format: uc-cidr6
 | 
				
			||||||
    examples:
 | 
					    examples:
 | 
				
			||||||
    - fdca:1234:4567::/48
 | 
					    - 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:
 | 
					  ipv4-blackhole:
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      Define a list of non-interface specific BLACKHOLE (to-nowhere) routes.
 | 
					      Define a list of non-interface specific BLACKHOLE (to-nowhere) routes.
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user