mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Refactor WiFI implementation
* Wi-Fi station still work (changed YANG model) * Wi-Fi access point supported Model is flexible to support even more modes
This commit is contained in:
+55
-4
@@ -17,7 +17,25 @@
|
||||
"state": {
|
||||
"admin-state": "unlocked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "radio0",
|
||||
"class": "infix-hardware:wifi",
|
||||
"infix-hardware:wifi-radio": {
|
||||
"country-code": "DE",
|
||||
"band": "2.4GHz",
|
||||
"channel": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "radio1",
|
||||
"class": "infix-hardware:wifi",
|
||||
"infix-hardware:wifi-radio": {
|
||||
"country-code": "DE",
|
||||
"band": "5GHz",
|
||||
"channel": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ietf-interfaces:interfaces": {
|
||||
@@ -150,12 +168,36 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wifi0",
|
||||
"type": "infix-if-type:wifi"
|
||||
"name": "wifi0-ap",
|
||||
"type": "infix-if-type:wifi",
|
||||
"infix-interfaces:wifi": {
|
||||
"radio": "radio0",
|
||||
"access-point": {
|
||||
"ssid": "Infix",
|
||||
"security": {
|
||||
"secret": "wifi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wifi1",
|
||||
"type": "infix-if-type:wifi"
|
||||
"name": "wifi1-ap",
|
||||
"type": "infix-if-type:wifi",
|
||||
"infix-interfaces:wifi": {
|
||||
"radio": "radio1",
|
||||
"access-point": {
|
||||
"ssid": "Infix5Ghz",
|
||||
"security": {
|
||||
"secret": "wifi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -171,6 +213,15 @@
|
||||
"certificates": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"symmetric-keys": {
|
||||
"symmetric-key": [
|
||||
{
|
||||
"name": "wifi",
|
||||
"infix-keystore:cleartext-symmetric-key": "infixinfix",
|
||||
"infix-keystore:key-format": "infix-crypto-types:wifi-preshared-key-format"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-netconf-acm:nacm": {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
service <!> name:hostapd :%i \
|
||||
[2345] hostapd -P/var/run/hostapd-%i.pid /etc/hostapd-%i.conf \
|
||||
-- Wi-Fi Access Point @%i
|
||||
@@ -1,5 +1,5 @@
|
||||
service name:wpa_supplicant :%i \
|
||||
[2345] wpa_supplicant -s -i %i -c /etc/wpa_supplicant-%i.conf -P/var/run/wpa_supplicant-%i.pid \
|
||||
-- WPA supplicant @%i
|
||||
-- Wi-Fi Station @%i
|
||||
|
||||
task name:wifi-scanner :%i [2345] <pid/wpa_supplicant:%i> /usr/libexec/infix/wifi-scanner %i -- Start scanning for SSID @ %i
|
||||
task name:wifi-scanner :%i [2345] <pid/wpa_supplicant:%i> /usr/libexec/infix/wifi-scanner %i -- Start scanning for SSID @%i
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Rename WiFi PHY devices from phy0 to radio0 to avoid name clashes
|
||||
SUBSYSTEM=="ieee80211", ACTION=="add", KERNEL=="phy*", \
|
||||
RUN+="/bin/sh -c '/usr/sbin/iw phy %k set name radio%n'"
|
||||
@@ -0,0 +1,7 @@
|
||||
# Remove kernel-created WiFi interfaces
|
||||
# All WiFi interfaces are now virtual interfaces created by confd
|
||||
SUBSYSTEM=="net", ACTION=="add", KERNEL=="wlan*", \
|
||||
TEST=="/sys/class/net/$name/phy80211/name", \
|
||||
PROGRAM="/bin/cat /sys/class/net/%k/phy80211/name", \
|
||||
TEST!="/run/wifi-cleaned-%c", \
|
||||
RUN+="/bin/sh -c '/usr/sbin/iw dev %k del && touch /run/wifi-cleaned-%c'"
|
||||
@@ -1 +0,0 @@
|
||||
SUBSYSTEM=="net", ACTION=="add", TEST=="/sys/class/net/$name/wireless", NAME="wifi%n"
|
||||
+367
-84
@@ -1,14 +1,29 @@
|
||||
# Wi-Fi (Wireless LAN)
|
||||
|
||||
Infix includes built-in Wi-Fi client support for connecting to
|
||||
wireless networks. When a compatible Wi-Fi adapter is detected, the
|
||||
system automatically begins scanning for available networks.
|
||||
Infix includes comprehensive Wi-Fi support for both client (Station) and
|
||||
Access Point modes. When a compatible Wi-Fi adapter is detected, the system
|
||||
automatically creates a WiFi radio (PHY) in factory-config, that can
|
||||
host virtual interfaces.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Infix uses a two-layer WiFi architecture:
|
||||
|
||||
1. **WiFi Radio (PHY layer)**: Represents the physical wireless hardware
|
||||
- Configured via `ietf-hardware` module
|
||||
- Controls channel, transmit power, regulatory domain
|
||||
- One radio can host multiple virtual interfaces
|
||||
|
||||
2. **WiFi Interface (Network layer)**: Virtual interface on a radio
|
||||
- Configured via `infix-interfaces` module
|
||||
- Can operate in Station (client) or Access Point mode
|
||||
- Each interface references a parent radio
|
||||
|
||||
## Current Limitations
|
||||
|
||||
- Only client mode is supported (no access point functionality)
|
||||
- USB hotplug is not supported - adapters must be present at boot
|
||||
- Interface naming may be inconsistent with multiple USB Wi-Fi adapters
|
||||
- AP and Station modes cannot be mixed on the same radio
|
||||
|
||||
## Supported Wi-Fi Adapters
|
||||
|
||||
@@ -16,6 +31,8 @@ Wi-Fi support is primarily tested with Realtek chipset-based adapters.
|
||||
|
||||
### Known Working Chipsets
|
||||
|
||||
- Built-in Wi-Fi on Banana Pi r3
|
||||
- Built-in Wi-Fi on Raspberry Pi 4/CM4
|
||||
- RTL8821CU
|
||||
- Other Realtek chipsets may work but are not guaranteed
|
||||
|
||||
@@ -24,35 +41,128 @@ Wi-Fi support is primarily tested with Realtek chipset-based adapters.
|
||||
> Firmware requirements vary by chipset
|
||||
> Check kernel logs if your adapter is not detected
|
||||
|
||||
## Configuration
|
||||
## Radio Configuration
|
||||
|
||||
Add a supported Wi-Fi network device. To verify that it has been
|
||||
detected, look for `wifi0` in `show interface`
|
||||
Before configuring WiFi interfaces, you must first configure the WiFi radio.
|
||||
Radios are automatically discovered and named `radio0`, `radio1`, etc.
|
||||
|
||||
### Country Code and Regulatory Compliance
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The `country-code` setting is **legally required** and determines which WiFi channels and power levels are permitted in your location. Using an incorrect country code may violate local wireless regulations.
|
||||
|
||||
**Factory default**: Systems may ship with a default country code (typically "DE" for Germany in European builds or "00" for World domain). **You must configure the correct country code for your deployment location.**
|
||||
|
||||
**Common country codes**:
|
||||
- Europe: DE (Germany), SE (Sweden), GB (UK), FR (France), ES (Spain)
|
||||
- Americas: US (United States), CA (Canada), BR (Brazil)
|
||||
- Asia-Pacific: JP (Japan), AU (Australia), CN (China)
|
||||
|
||||
See [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) for the complete list.
|
||||
|
||||
### Basic Radio Setup
|
||||
|
||||
Configure the radio with channel, power, and regulatory domain.
|
||||
|
||||
**For Station (client) mode:**
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit hardware component radio0 wifi-radio
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
```
|
||||
|
||||
**For Access Point mode:**
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit hardware component radio0 wifi-radio
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
```
|
||||
|
||||
**Key radio parameters:**
|
||||
- `country-code`: Two-letter ISO 3166-1 code - determines allowed channels and maximum power. Examples: US, DE, GB, SE, FR, JP. **Must match your physical location for legal compliance.**
|
||||
- `band`: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Band selection automatically enables appropriate WiFi standards (2.4GHz: 802.11n, 5GHz: 802.11n/ac, 6GHz: 802.11n/ac/ax)
|
||||
- `channel`: Channel number (1-196) or "auto" (required for AP mode). When set to "auto", defaults to channel 6 for 2.4GHz, channel 36 for 5GHz, or channel 109 for 6GHz
|
||||
- `enable-wifi6`: Boolean (default: false). Opt-in to enable WiFi 6 (802.11ax) on 2.4GHz and 5GHz bands. The 6GHz band always uses WiFi 6 regardless of this setting
|
||||
|
||||
> [!NOTE]
|
||||
> TX power and channel width are automatically determined by the driver based on regulatory constraints, PHY mode, and hardware capabilities.
|
||||
|
||||
### WiFi 6 (802.11ax) Support
|
||||
|
||||
WiFi 6 (802.11ax) provides improved performance in congested environments through
|
||||
features like OFDMA, Target Wake Time, and BSS Coloring. By default, WiFi 6 is
|
||||
only enabled on the 6GHz band (WiFi 6E requirement).
|
||||
|
||||
To enable WiFi 6 on 2.4GHz or 5GHz bands:
|
||||
|
||||
```
|
||||
admin@example:/> show interface
|
||||
INTERFACE PROTOCOL STATE DATA
|
||||
lo loopback UP
|
||||
ipv4 127.0.0.1/8 (static)
|
||||
ipv6 ::1/128 (static)
|
||||
e1 ethernet UP 02:00:00:00:00:01
|
||||
ipv6 fe80::ff:fe00:1/64 (link-layer)
|
||||
ipv6 fec0::ff:fe00:1/64 (link-layer)
|
||||
wifi0 ethernet DOWN f0:09:0d:36:5f:86
|
||||
wifi ssid: ------, signal: ------
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit hardware component radio0 wifi-radio
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set enable-wifi6 true
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
```
|
||||
|
||||
**WiFi 6 Benefits:**
|
||||
- **OFDMA**: Better multi-user efficiency in dense environments
|
||||
- **Target Wake Time**: Improved battery life for client devices
|
||||
- **1024-QAM**: Higher throughput with strong signal conditions
|
||||
- **BSS Coloring**: Reduced interference from neighboring networks
|
||||
|
||||
**Requirements:**
|
||||
- Hardware must support 802.11ax
|
||||
- Client devices must support WiFi 6 for full benefits
|
||||
- Older WiFi 5/4 clients can still connect but won't use WiFi 6 features
|
||||
|
||||
> [!NOTE]
|
||||
> The 6GHz band always uses WiFi 6 (802.11ax) regardless of the `enable-wifi6`
|
||||
> setting, as WiFi 6E requires 802.11ax support.
|
||||
|
||||
## Discovering Available Networks (Scanning)
|
||||
|
||||
Before connecting to a WiFi network, you need to discover which networks
|
||||
are available. Infix automatically scans for networks when a WiFi interface
|
||||
is created with a radio reference.
|
||||
|
||||
### Enable Background Scanning
|
||||
|
||||
To enable scanning without connecting, configure the radio and create a WiFi
|
||||
interface referencing it:
|
||||
|
||||
**Step 1: Configure the radio**
|
||||
|
||||
```
|
||||
Add the new Wi-Fi interface to the configuration to start scanning.
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit hardware component radio0 wifi-radio
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
```
|
||||
admin@example:/config/> set interface wifi0
|
||||
admin@example:/config/> leave
|
||||
```
|
||||
Now the system will now start scanning in the background. To
|
||||
see the result read the operational datastore for interface `wifi0` or
|
||||
use the CLI
|
||||
|
||||
**Step 2: Create WiFi interface with radio reference only**
|
||||
|
||||
```
|
||||
admin@infix-00-00-00:/> show interface wifi0
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit interface wifi0
|
||||
admin@example:/config/interface/wifi0/> set wifi radio radio0
|
||||
admin@example:/config/interface/wifi0/> leave
|
||||
```
|
||||
|
||||
The system will now start scanning in the background. The interface will
|
||||
operate in scan-only mode until you configure a specific mode (station or
|
||||
access-point).
|
||||
|
||||
### View Available Networks
|
||||
|
||||
Use `show interface` to see discovered networks and their signal strength:
|
||||
|
||||
```
|
||||
admin@example:/> show interface wifi0
|
||||
name : wifi0
|
||||
type : wifi
|
||||
index : 3
|
||||
@@ -64,89 +174,262 @@ ipv6 addresses :
|
||||
SSID : ----
|
||||
Signal : ----
|
||||
|
||||
SSID ENCRYPTION SIGNAL
|
||||
ssid1 WPA2-Personal excellent
|
||||
ssid2 WPA2-Personal excellent
|
||||
ssid3 WPA2-Personal excellent
|
||||
ssid4 WPA2-Personal good
|
||||
ssid5 WPA2-Personal good
|
||||
ssid6 WPA2-Personal good
|
||||
SSID SECURITY SIGNAL
|
||||
MyNetwork WPA2-Personal excellent
|
||||
GuestWiFi WPA2-WPA3-Personal good
|
||||
CoffeeShop Open fair
|
||||
IoT-Devices WPA2-Personal good
|
||||
```
|
||||
|
||||
In the CLI, signal strength is reported as: excellent, good, poor or
|
||||
bad. For precise values, use NETCONF or RESTCONF, where the RSSI (in
|
||||
dBm) is available in the operational datastore.
|
||||
In the CLI, signal strength is reported as: excellent, good, fair or bad.
|
||||
For precise RSSI values in dBm, use NETCONF or RESTCONF to access the
|
||||
operational datastore directly.
|
||||
|
||||
Configure your Wi-Fi secret in the keystore, it should be between 8
|
||||
and 63 characters
|
||||
### Connect to a Network
|
||||
|
||||
Once you've identified the desired network from the scan results, configure
|
||||
station mode with the SSID and credentials. First, store your WiFi password
|
||||
in the keystore:
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit keystore symmetric-key example
|
||||
admin@example:/config/keystore/…/example/> set key-format wifi-preshared-key-format
|
||||
admin@example:/config/keystore/…/example/> set cleartext-symmetric-key mysecret
|
||||
admin@example:/config/keystore/…/example/> leave
|
||||
admin@example:/>
|
||||
admin@example:/config/> edit keystore symmetric-key my-wifi-key
|
||||
admin@example:/config/keystore/…/my-wifi-key/> set key-format wifi-preshared-key-format
|
||||
admin@example:/config/keystore/…/my-wifi-key/> set symmetric-key YourWiFiPassword
|
||||
admin@example:/config/keystore/…/my-wifi-key/> leave
|
||||
```
|
||||
|
||||
Configure the Wi-Fi settings, set secret to the name selected above
|
||||
for the symmetric key, in this case `example`.
|
||||
|
||||
WPA2 or WPA3 encryption will be automatically selected based on what
|
||||
the access point supports. No manual selection is required unless
|
||||
connecting to an open network. No support for certificate based
|
||||
authentication yet.
|
||||
|
||||
Unencrypted network is also supported, to connect to an unencrypted
|
||||
network (generally not recommended):
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi encryption disabled
|
||||
```
|
||||
|
||||
A valid `country-code` is also required for regulatory compliance, the
|
||||
valid codes are documented in the YANG model `infix-wifi-country-codes`
|
||||
|
||||
Then configure the WiFi interface for station mode:
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit interface wifi0
|
||||
admin@example:/config/interface/wifi0/>
|
||||
admin@example:/config/interface/wifi0/> set wifi ssid ssid1
|
||||
admin@example:/config/interface/wifi0/> set wifi secret example
|
||||
admin@example:/config/interface/wifi0/> set wifi country-code SE
|
||||
admin@example:/config/interface/wifi0/> set wifi station ssid MyNetwork
|
||||
admin@example:/config/interface/wifi0/> set wifi station security secret my-wifi-key
|
||||
admin@example:/config/interface/wifi0/> leave
|
||||
```
|
||||
|
||||
The Wi-Fi negotiation should now start immediately, provided that the
|
||||
SSID and pre-shared key are correct. You can verify the connection by
|
||||
running `show interface` again.
|
||||
The interface will transition from scan-only mode to station mode and
|
||||
attempt to connect to the specified network.
|
||||
|
||||
## Station Mode (Client)
|
||||
|
||||
Station mode connects to an existing Wi-Fi network. Before configuring station
|
||||
mode, follow the "Discovering Available Networks (Scanning)" section above to
|
||||
scan for available networks and identify the SSID you want to connect to.
|
||||
|
||||
### Step 1: Configure WiFi Password
|
||||
|
||||
Create a keystore entry for your WiFi password (8-63 characters):
|
||||
|
||||
```
|
||||
admin@example:/> show interface
|
||||
INTERFACE PROTOCOL STATE DATA
|
||||
lo loopback UP
|
||||
ipv4 127.0.0.1/8 (static)
|
||||
ipv6 ::1/128 (static)
|
||||
e1 ethernet UP 02:00:00:00:00:01
|
||||
ipv6 fe80::ff:fe00:1/64 (link-layer)
|
||||
ipv6 fec0::ff:fe00:1/64 (link-layer)
|
||||
wifi0 ethernet UP f0:09:0d:36:5f:86
|
||||
wifi ssid: ssid1, signal: excellent
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit keystore symmetric-key my-wifi-key
|
||||
admin@example:/config/keystore/…/my-wifi-key/> set key-format wifi-preshared-key-format
|
||||
admin@example:/config/keystore/…/my-wifi-key/> set symmetric-key MyPassword123
|
||||
admin@example:/config/keystore/…/my-wifi-key/> leave
|
||||
```
|
||||
|
||||
admin@example:/>
|
||||
### Step 2: Connect to Network
|
||||
|
||||
Configure station mode with the SSID and password to connect:
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit interface wifi0
|
||||
admin@example:/config/interface/wifi0/> set wifi station ssid MyHomeNetwork
|
||||
admin@example:/config/interface/wifi0/> set wifi station security secret my-wifi-key
|
||||
admin@example:/config/interface/wifi0/> leave
|
||||
```
|
||||
|
||||
The connection attempt will start immediately. You can verify the connection status:
|
||||
|
||||
```
|
||||
admin@example:/> show interface wifi0
|
||||
name : wifi0
|
||||
type : wifi
|
||||
operational status : up
|
||||
physical address : f0:09:0d:36:5f:86
|
||||
SSID : MyHomeNetwork
|
||||
Signal : excellent
|
||||
```
|
||||
|
||||
**Station configuration parameters:**
|
||||
- `radio`: Reference to the WiFi radio (mandatory) - already set during scanning
|
||||
- `station ssid`: Network name to connect to (mandatory)
|
||||
- `station security mode`: `auto` (default, WPA2/WPA3 auto-negotiation) or `disabled` (open network)
|
||||
- `station security secret`: Reference to keystore entry (required unless mode is `disabled`)
|
||||
|
||||
> [!NOTE]
|
||||
> The `auto` security mode automatically selects WPA3-SAE or WPA2-PSK based on
|
||||
> what the access point supports, prioritizing WPA3 for better security.
|
||||
> Certificate-based authentication (802.1X/EAP) is not yet supported.
|
||||
|
||||
## Access Point Mode
|
||||
|
||||
Access Point (AP) mode allows your device to create a WiFi network that
|
||||
other devices can connect to. APs are configured as virtual interfaces on
|
||||
a WiFi radio.
|
||||
|
||||
### Basic AP Configuration
|
||||
|
||||
First, ensure the radio is configured (see Radio Configuration above). Then
|
||||
create an AP interface:
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit interface wifi0
|
||||
admin@example:/config/interface/wifi0/> set wifi radio radio0
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point ssid MyNetwork
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point security mode wpa2-personal
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point security secret example
|
||||
admin@example:/config/interface/wifi0/> leave
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Using `wifiN` as the interface name automatically sets the type to WiFi.
|
||||
> Alternatively, you can use any name and explicitly set `type wifi`.
|
||||
|
||||
**Access Point configuration parameters:**
|
||||
- `radio`: Reference to the WiFi radio (mandatory)
|
||||
- `access-point ssid`: Network name (SSID) to broadcast
|
||||
- `access-point hidden`: Set to `true` to hide SSID (optional, default: false)
|
||||
- `access-point security mode`: Security mode (see below)
|
||||
- `access-point security secret`: Reference to keystore entry (for secured networks)
|
||||
|
||||
**Security modes:**
|
||||
- `open`: No encryption (not recommended)
|
||||
- `wpa2-personal`: WPA2-PSK (most compatible)
|
||||
- `wpa3-personal`: WPA3-SAE (more secure, requires WPA3-capable clients)
|
||||
- `wpa2-wpa3-personal`: Mixed mode (maximum compatibility)
|
||||
|
||||
### Hidden Network (SSID Hiding)
|
||||
|
||||
To create a hidden network that doesn't broadcast its SSID:
|
||||
|
||||
```
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point hidden true
|
||||
```
|
||||
|
||||
### Multi-SSID Configuration
|
||||
|
||||
Multiple AP interfaces on the same radio allow broadcasting multiple SSIDs,
|
||||
each with independent security settings. This is useful for guest networks,
|
||||
IoT devices, or segregating traffic into different VLANs.
|
||||
|
||||
**Step 1: Configure the radio** (shared by all APs)
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit hardware component radio0 wifi-radio
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
```
|
||||
|
||||
**Step 2: Configure keystore secrets**
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit keystore symmetric-key main-secret
|
||||
admin@example:/config/keystore/…/main-secret/> set key-format wifi-preshared-key-format
|
||||
admin@example:/config/keystore/…/main-secret/> set cleartext-symmetric-key MyMainPassword
|
||||
admin@example:/config/> edit keystore symmetric-key guest-secret
|
||||
admin@example:/config/keystore/…/guest-secret/> set key-format wifi-preshared-key-format
|
||||
admin@example:/config/keystore/…/guest-secret/> set cleartext-symmetric-key GuestPassword123
|
||||
admin@example:/config/> edit keystore symmetric-key iot-secret
|
||||
admin@example:/config/keystore/…/iot-secret/> set key-format wifi-preshared-key-format
|
||||
admin@example:/config/keystore/…/iot-secret/> set cleartext-symmetric-key IoTDevices2025
|
||||
admin@example:/config/keystore/…/iot-secret/> leave
|
||||
```
|
||||
|
||||
**Step 3: Create multiple AP interfaces** (all on radio0)
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
# Primary AP - Main network (WPA3 for maximum security)
|
||||
admin@example:/config/> edit interface wifi0
|
||||
admin@example:/config/interface/wifi0/> set wifi radio radio0
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point ssid MainNetwork
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point security mode wpa3-personal
|
||||
admin@example:/config/interface/wifi0/> set wifi access-point security secret main-secret
|
||||
|
||||
# Guest AP - Guest network (WPA2/WPA3 mixed for compatibility)
|
||||
admin@example:/config/> edit interface wifi1
|
||||
admin@example:/config/interface/wifi1/> set wifi radio radio0
|
||||
admin@example:/config/interface/wifi1/> set wifi access-point ssid GuestNetwork
|
||||
admin@example:/config/interface/wifi1/> set wifi access-point security mode wpa2-wpa3-personal
|
||||
admin@example:/config/interface/wifi1/> set wifi access-point security secret guest-secret
|
||||
admin@example:/config/interface/wifi1/> set custom-phys-address static 00:0c:43:26:60:01
|
||||
|
||||
# IoT AP - IoT devices (WPA2 for older device compatibility)
|
||||
admin@example:/config/> edit interface wifi2
|
||||
admin@example:/config/interface/wifi2/> set wifi radio radio0
|
||||
admin@example:/config/interface/wifi2/> set wifi access-point ssid IoT-Devices
|
||||
admin@example:/config/interface/wifi2/> set wifi access-point security mode wpa2-personal
|
||||
admin@example:/config/interface/wifi2/> set wifi access-point security secret iot-secret
|
||||
admin@example:/config/interface/wifi2/> set custom-phys-address static 00:0c:43:26:60:02
|
||||
admin@example:/config/interface/wifi2/> leave
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **MAC Address Requirement for Multi-SSID:**
|
||||
> When creating multiple AP interfaces on the same radio, you **must** configure
|
||||
> a unique MAC address for each secondary interface (wifi1, wifi2, etc.) using
|
||||
> `set custom-phys-address static <MAC>`. All interfaces on the same radio inherit
|
||||
> the radio's hardware MAC address by default, which causes network conflicts. Only
|
||||
> the primary interface (alphabetically first, e.g., wifi0) should use the default
|
||||
> hardware MAC address.
|
||||
>
|
||||
> Choose MAC addresses from the same locally-administered range:
|
||||
> - Primary (wifi0): Uses hardware MAC (e.g., `00:0c:43:26:60:00`)
|
||||
> - Secondary (wifi1): `00:0c:43:26:60:01` (increment last octet)
|
||||
> - Tertiary (wifi2): `00:0c:43:26:60:02` (increment last octet)
|
||||
|
||||
**Result:** Three SSIDs broadcasting simultaneously on radio0:
|
||||
- `MainNetwork` (WPA3, most secure)
|
||||
- `GuestNetwork` (WPA2/WPA3 mixed mode)
|
||||
- `IoT-Devices` (WPA2 for compatibility)
|
||||
|
||||
All APs on the same radio share the same channel and physical layer settings
|
||||
(configured at the radio level). Each AP can have its own:
|
||||
- SSID (network name)
|
||||
- Security mode and passphrase
|
||||
- Hidden/visible SSID setting
|
||||
- Bridge membership
|
||||
|
||||
You can verify the configuration with `show hardware component radio0` to see
|
||||
radio settings, and `show interface` to see all active AP interfaces.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> AP and Station modes cannot be mixed on the same radio. All virtual interfaces
|
||||
> on a radio must be the same mode (all APs or all Stations).
|
||||
|
||||
### AP as Bridge Port
|
||||
|
||||
WiFi AP interfaces can be added to bridges to integrate wireless devices
|
||||
into your LAN:
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit interface br0
|
||||
admin@example:/config/interface/br0/> set type bridge
|
||||
|
||||
admin@example:/config/> edit interface wifi0
|
||||
admin@example:/config/interface/wifi0/> set bridge-port bridge br0
|
||||
admin@example:/config/interface/wifi0/> leave
|
||||
```
|
||||
|
||||
## Troubleshooting Connection Issues
|
||||
|
||||
Use `show wifi scan wifi0` and `show interface` to verify signal strength
|
||||
and connection status. If issues arise, try the following
|
||||
troubleshooting steps:
|
||||
Use `show interface wifi0` to verify signal strength and connection status.
|
||||
If issues arise, try the following troubleshooting steps:
|
||||
|
||||
1. **Verify signal strength**: Check that the target network shows "good" or "excellent" signal
|
||||
2. **Check credentials**: Verify the preshared key in `ietf-keystore`
|
||||
1. **Verify signal strength**: Check that the target network shows "good" or "excellent" signal in scan results
|
||||
2. **Check credentials**: Verify the preshared key in the keystore matches the network password
|
||||
3. **Review logs**: Check system logs with `show log` for Wi-Fi related errors
|
||||
4. **Regulatory compliance**: Ensure the country-code matches your location
|
||||
5. **Hardware detection**: Confirm the adapter appears in `show interface`
|
||||
4. **Regulatory compliance**: Ensure the country-code on the radio matches your location
|
||||
5. **Hardware detection**: Confirm the WiFi radio appears in `show hardware`
|
||||
|
||||
If issues persist, check the system log for specific error messages that can help identify the root cause.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#
|
||||
########################################################################
|
||||
# The system factory-config, failure-config and test-config are derived
|
||||
# from default settings snippets, from /usr/share/confd/factory.d, and
|
||||
# some generated snippets, e.g., hostname (based on base MAC address)
|
||||
# from default settings snippets, from /usr/share/confd/factory.d, and
|
||||
# some generated snippets, e.g., hostname (based on base MAC address)
|
||||
# and number of interfaces.
|
||||
#
|
||||
# The resulting factory-config is used to create the syrepo db (below)
|
||||
@@ -147,7 +147,7 @@ gen_test_cfg()
|
||||
|
||||
# Both factory-config and failure-config are generated every boot
|
||||
# regardless if there is a static /etc/factory-config.cfg or not.
|
||||
gen_factory_cfg
|
||||
gen_factory_cfg
|
||||
gen_failure_cfg
|
||||
|
||||
if [ -f "/mnt/aux/test-mode" ]; then
|
||||
|
||||
@@ -6,6 +6,8 @@ if jq -e '.["usb-ports"]' /run/system.json > /dev/null; then
|
||||
else
|
||||
usb_ports=""
|
||||
fi
|
||||
wifi_radios=$(/usr/libexec/infix/iw.py list 2>/dev/null | jq -r '.[]' || echo "")
|
||||
|
||||
|
||||
gen_port()
|
||||
{
|
||||
@@ -20,6 +22,43 @@ gen_port()
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
gen_radio()
|
||||
{
|
||||
radio="$1"
|
||||
|
||||
# Detect supported bands from iw.py info JSON output
|
||||
phy_info=$(/usr/libexec/infix/iw.py info "$radio" 2>/dev/null || echo '{"bands":[]}')
|
||||
# Check if 2.4GHz band exists (band name "2.4GHz")
|
||||
has_2ghz=$(echo "$phy_info" | jq '[.bands[] | select(.name == "2.4GHz")] | length')
|
||||
# Check if 5GHz band exists (band name "5GHz")
|
||||
has_5ghz=$(echo "$phy_info" | jq '[.bands[] | select(.name == "5GHz")] | length')
|
||||
|
||||
# Determine band setting
|
||||
# If both bands supported, prefer 5GHz for better performance
|
||||
if [ "$has_2ghz" -gt 0 ] && [ "$has_5ghz" -gt 0 ]; then
|
||||
band="5GHz"
|
||||
elif [ "$has_5ghz" -gt 0 ]; then
|
||||
band="5GHz"
|
||||
elif [ "$has_2ghz" -gt 0 ]; then
|
||||
band="2.4GHz"
|
||||
else
|
||||
band="2.4GHz" # Fallback to 2.4GHz for maximum compatibility
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"name": "$radio",
|
||||
"class": "infix-hardware:wifi",
|
||||
"infix-hardware:wifi-radio": {
|
||||
"country-code": "00",
|
||||
"band": "$band",
|
||||
"channel": "auto"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
first=1
|
||||
cat <<EOF
|
||||
{
|
||||
@@ -33,6 +72,13 @@ for port in $usb_ports; do
|
||||
first=0;
|
||||
gen_port "$port"
|
||||
done
|
||||
for radio in $wifi_radios; do
|
||||
if [ $first -eq 0 ]; then
|
||||
echo -n ','
|
||||
fi
|
||||
first=0;
|
||||
gen_radio $radio
|
||||
done
|
||||
cat <<EOF
|
||||
]}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Remove interfaces of type infix-if-type:wifi
|
||||
# Wi-Fi support has been refactored and all radio
|
||||
# settings have been moved to ietf-hardware.
|
||||
file=$1
|
||||
temp=${file}.tmp
|
||||
|
||||
jq '
|
||||
if .["ietf-interfaces:interfaces"]?.interface then
|
||||
.["ietf-interfaces:interfaces"].interface |= map(
|
||||
select(.type != "infix-if-type:wifi")
|
||||
)
|
||||
else
|
||||
.
|
||||
end
|
||||
' "$file" > "$temp" && mv "$temp" "$file"
|
||||
@@ -1,3 +1,4 @@
|
||||
migratedir = $(pkgdatadir)/migrate/1.7
|
||||
dist_migrate_DATA = 10-netconf-server-tcp-params.sh \
|
||||
20-keystore-cleartext-key-rename.sh
|
||||
20-keystore-cleartext-key-rename.sh \
|
||||
30-remove-wifi-interfaces.sh
|
||||
|
||||
@@ -138,6 +138,128 @@ static confd_dependency_t handle_dependencies(struct lyd_node **diff, struct lyd
|
||||
}
|
||||
}
|
||||
|
||||
/* When WiFi interfaces change, add their radios to the diff */
|
||||
struct lyd_node *difs = lydx_get_descendant(*diff, "interfaces", "interface", NULL);
|
||||
if (difs) {
|
||||
struct lyd_node *dif;
|
||||
|
||||
LYX_LIST_FOR_EACH(difs, dif, "interface") {
|
||||
struct lyd_node *dwifi, *radio_node;
|
||||
const char *ifname, *radio_name;
|
||||
char xpath[256];
|
||||
|
||||
/* Check if this interface has a wifi container in the diff */
|
||||
dwifi = lydx_get_child(dif, "wifi");
|
||||
if (!dwifi)
|
||||
continue;
|
||||
|
||||
/* Get the interface name */
|
||||
ifname = lydx_get_cattr(dif, "name");
|
||||
if (!ifname)
|
||||
continue;
|
||||
|
||||
/* Get radio reference from config tree using xpath */
|
||||
radio_node = lydx_get_xpathf(config,
|
||||
"/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio",
|
||||
ifname);
|
||||
if (!radio_node)
|
||||
continue;
|
||||
|
||||
radio_name = lyd_get_value(radio_node);
|
||||
if (!radio_name)
|
||||
continue;
|
||||
|
||||
/* Add the radio to the diff */
|
||||
snprintf(xpath, sizeof(xpath), "/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio_name);
|
||||
result = add_dependencies(diff, xpath, radio_name);
|
||||
if (result == CONFD_DEP_ERROR) {
|
||||
ERROR("Failed to add radio %s to diff for WiFi interface %s", radio_name, ifname);
|
||||
return result;
|
||||
}
|
||||
|
||||
DEBUG("Added radio %s to diff for WiFi interface %s", radio_name, ifname);
|
||||
}
|
||||
}
|
||||
|
||||
/* When WiFi radios change, add all interfaces using that radio to the diff */
|
||||
struct ly_set *radios = lydx_find_xpathf(*diff,
|
||||
"/ietf-hardware:hardware/component[infix-hardware:wifi-radio]");
|
||||
if (radios && radios->count > 0) {
|
||||
for (uint32_t i = 0; i < radios->count; i++) {
|
||||
struct lyd_node *hradio = radios->dnodes[i];
|
||||
const char *radio_name = lydx_get_cattr(hradio, "name");
|
||||
struct ly_set *ifaces;
|
||||
uint32_t j;
|
||||
char xpath[256];
|
||||
|
||||
if (!radio_name)
|
||||
continue;
|
||||
|
||||
/* Find all interfaces that reference this radio */
|
||||
ifaces = lydx_find_xpathf(config,
|
||||
"/ietf-interfaces:interfaces/interface[infix-interfaces:wifi/radio='%s']",
|
||||
radio_name);
|
||||
if (ifaces && ifaces->count > 0) {
|
||||
for (j = 0; j < ifaces->count; j++) {
|
||||
const char *ifname = lydx_get_cattr(ifaces->dnodes[j], "name");
|
||||
|
||||
/* Add the wifi container */
|
||||
snprintf(xpath, sizeof(xpath),
|
||||
"/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi",
|
||||
ifname);
|
||||
result = add_dependencies(diff, xpath, ifname);
|
||||
if (result == CONFD_DEP_ERROR) {
|
||||
ERROR("Failed to add interface wifi for %s (radio %s)", ifname, radio_name);
|
||||
ly_set_free(ifaces, NULL);
|
||||
ly_set_free(radios, NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Add the radio leaf */
|
||||
snprintf(xpath, sizeof(xpath),
|
||||
"/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio",
|
||||
ifname);
|
||||
result = add_dependencies(diff, xpath, radio_name);
|
||||
if (result == CONFD_DEP_ERROR) {
|
||||
ERROR("Failed to add radio leaf for interface %s (radio %s)", ifname, radio_name);
|
||||
ly_set_free(ifaces, NULL);
|
||||
ly_set_free(radios, NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Add station or access-point container depending on mode */
|
||||
if (lydx_get_descendant(ifaces->dnodes[j], "interface", "wifi", "station", NULL)) {
|
||||
snprintf(xpath, sizeof(xpath),
|
||||
"/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/station",
|
||||
ifname);
|
||||
result = add_dependencies(diff, xpath, ifname);
|
||||
if (result == CONFD_DEP_ERROR) {
|
||||
ERROR("Failed to add station for interface %s (radio %s)", ifname, radio_name);
|
||||
ly_set_free(ifaces, NULL);
|
||||
ly_set_free(radios, NULL);
|
||||
return result;
|
||||
}
|
||||
} else if (lydx_get_descendant(ifaces->dnodes[j], "interface", "wifi", "access-point", NULL)) {
|
||||
snprintf(xpath, sizeof(xpath),
|
||||
"/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/access-point",
|
||||
ifname);
|
||||
result = add_dependencies(diff, xpath, ifname);
|
||||
if (result == CONFD_DEP_ERROR) {
|
||||
ERROR("Failed to add access-point for interface %s (radio %s)", ifname, radio_name);
|
||||
ly_set_free(ifaces, NULL);
|
||||
ly_set_free(radios, NULL);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG("Added interface %s to diff for radio %s", ifname, radio_name);
|
||||
}
|
||||
ly_set_free(ifaces, NULL);
|
||||
}
|
||||
}
|
||||
ly_set_free(radios, NULL);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+606
-17
@@ -10,8 +10,14 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "interfaces.h"
|
||||
#include "dagger.h"
|
||||
|
||||
#define XPATH_BASE_ "/ietf-hardware:hardware"
|
||||
#define XPATH_BASE_ "/ietf-hardware:hardware"
|
||||
#define HOSTAPD_CONF "/etc/hostapd-%s.conf"
|
||||
#define HOSTAPD_CONF_NEXT HOSTAPD_CONF"+"
|
||||
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
|
||||
#define WPA_SUPPLICANT_CONF_NEXT WPA_SUPPLICANT_CONF"+"
|
||||
|
||||
static int dir_cb(const char *fpath, const struct stat *sb,
|
||||
int typeflag, struct FTW *ftwbuf)
|
||||
@@ -150,6 +156,452 @@ out_free_xpath:
|
||||
free(xpath);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int wifi_find_interfaces_on_radio(struct lyd_node *ifs, const char *radio_name,
|
||||
struct lyd_node ***iface_list, int *count)
|
||||
{
|
||||
struct lyd_node *iface, *wifi;
|
||||
const char *radio;
|
||||
struct lyd_node **list = NULL;
|
||||
int n = 0;
|
||||
|
||||
if (!ifs)
|
||||
return 0;
|
||||
|
||||
LYX_LIST_FOR_EACH(ifs, iface, "interface") {
|
||||
wifi = lydx_get_child(iface, "wifi");
|
||||
if (!wifi)
|
||||
continue;
|
||||
|
||||
radio = lydx_get_cattr(wifi, "radio");
|
||||
if (!radio || strcmp(radio, radio_name))
|
||||
continue;
|
||||
|
||||
if (lydx_get_op(iface) == LYDX_OP_DELETE)
|
||||
continue;
|
||||
list = realloc(list, sizeof(struct lyd_node *) * n + 1);
|
||||
list[n++] = iface;
|
||||
}
|
||||
|
||||
*iface_list = list;
|
||||
*count = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wifi_gen_station(const char *ifname, struct lyd_node *station,
|
||||
const char *radio, struct lyd_node *config)
|
||||
{
|
||||
const char *ssid, *secret_name, *secret, *security_mode;
|
||||
struct lyd_node *security, *secret_node, *radio_node;
|
||||
FILE *wpa_supplicant = NULL;
|
||||
char *security_str = NULL;
|
||||
const char *country;
|
||||
int rc = SR_ERR_OK;
|
||||
|
||||
/* If station is NULL, we're in scan-only mode (no station container) */
|
||||
if (station) {
|
||||
ssid = lydx_get_cattr(station, "ssid");
|
||||
security = lydx_get_child(station, "security");
|
||||
security_mode = lydx_get_cattr(security, "mode");
|
||||
secret_name = lydx_get_cattr(security, "secret");
|
||||
} else {
|
||||
ssid = NULL;
|
||||
security = NULL;
|
||||
security_mode = "disabled";
|
||||
secret_name = NULL;
|
||||
}
|
||||
|
||||
radio_node = lydx_get_xpathf(config,
|
||||
"/hardware/component[name='%s']/wifi-radio", radio);
|
||||
country = radio_node ? lydx_get_cattr(radio_node, "country-code") : NULL;
|
||||
|
||||
if (secret_name && strcmp(security_mode, "disabled") != 0) {
|
||||
secret_node = lydx_get_xpathf(config,
|
||||
"/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key",
|
||||
secret_name);
|
||||
secret = secret_node ? lyd_get_value(secret_node) : NULL;
|
||||
} else {
|
||||
secret = NULL;
|
||||
}
|
||||
|
||||
wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF_NEXT, ifname);
|
||||
if (!wpa_supplicant) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fprintf(wpa_supplicant,
|
||||
"ctrl_interface=/run/wpa_supplicant\n"
|
||||
"autoscan=periodic:10\n"
|
||||
"ap_scan=1\n");
|
||||
|
||||
if (country)
|
||||
fprintf(wpa_supplicant, "country=%s\n", country);
|
||||
|
||||
/* If SSID is present, create network block. Otherwise, scan-only mode */
|
||||
if (ssid) {
|
||||
/* Station mode with network configured */
|
||||
if (!strcmp(security_mode, "disabled")) {
|
||||
asprintf(&security_str, "key_mgmt=NONE");
|
||||
} else if (secret) {
|
||||
asprintf(&security_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret);
|
||||
}
|
||||
fprintf(wpa_supplicant,
|
||||
"network={\n"
|
||||
"bgscan=\"simple: 30:-45:300\"\n"
|
||||
"ssid=\"%s\"\n"
|
||||
"%s\n"
|
||||
"}\n", ssid, security_str);
|
||||
free(security_str);
|
||||
} else {
|
||||
/* Scan-only mode - no station container configured */
|
||||
fprintf(wpa_supplicant, "# Scan-only mode - no network configured\n");
|
||||
}
|
||||
|
||||
out:
|
||||
if (wpa_supplicant)
|
||||
fclose(wpa_supplicant);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Helper: Find all AP interfaces on a specific radio */
|
||||
static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
|
||||
char ***ap_list, int *count)
|
||||
{
|
||||
struct lyd_node *cif, *wifi, *ap;
|
||||
const char *ifname, *radio;
|
||||
char **list = NULL;
|
||||
int n = 0;
|
||||
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "interface") {
|
||||
wifi = lydx_get_child(cif, "wifi");
|
||||
if (!wifi)
|
||||
continue;
|
||||
|
||||
radio = lydx_get_cattr(wifi, "radio");
|
||||
if (!radio || strcmp(radio, radio_name))
|
||||
continue;
|
||||
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
if (!ap)
|
||||
continue;
|
||||
list = realloc(list, sizeof(char *) *n+1);
|
||||
|
||||
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
list[n++] = strdup(ifname);
|
||||
}
|
||||
|
||||
/* Sort alphabetically for consistent primary selection */
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
for (int j = i + 1; j < n; j++) {
|
||||
if (strcmp(list[i], list[j]) > 0) {
|
||||
char *tmp = list[i];
|
||||
list[i] = list[j];
|
||||
list[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ap_list = list;
|
||||
*count = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate BSS section for secondary AP (multi-SSID) */
|
||||
static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char *ifname, struct lyd_node *config)
|
||||
{
|
||||
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
|
||||
struct lyd_node *cif, *wifi, *ap, *security, *secret_node;
|
||||
|
||||
/* Find the interface node for this BSS */
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "interface") {
|
||||
const char *name = lydx_get_cattr(cif, "name");
|
||||
if (strcmp(name, ifname) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cif) {
|
||||
ERROR("Failed to find interface %s for BSS section", ifname);
|
||||
return SR_ERR_INVAL_ARG;
|
||||
}
|
||||
|
||||
wifi = lydx_get_child(cif, "wifi");
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
|
||||
fprintf(hostapd, "\n# BSS %s\n", ifname);
|
||||
fprintf(hostapd, "bss=%s\n", ifname);
|
||||
|
||||
/* SSID configuration */
|
||||
ssid = lydx_get_cattr(ap, "ssid");
|
||||
hidden = lydx_get_cattr(ap, "hidden");
|
||||
|
||||
if (ssid)
|
||||
fprintf(hostapd, "ssid=%s\n", ssid);
|
||||
if (hidden && !strcmp(hidden, "true"))
|
||||
fprintf(hostapd, "ignore_broadcast_ssid=1\n");
|
||||
|
||||
/* Security configuration */
|
||||
security = lydx_get_child(ap, "security");
|
||||
security_mode = lydx_get_cattr(security, "mode");
|
||||
|
||||
if (!security_mode)
|
||||
security_mode = "open";
|
||||
|
||||
/* Get secret from keystore if needed */
|
||||
secret = NULL;
|
||||
if (strcmp(security_mode, "open") != 0) {
|
||||
secret_name = lydx_get_cattr(security, "secret");
|
||||
if (secret_name) {
|
||||
secret_node = lydx_get_xpathf(config,
|
||||
"/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key",
|
||||
secret_name);
|
||||
if (secret_node)
|
||||
secret = lyd_get_value(secret_node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(security_mode, "open")) {
|
||||
fprintf(hostapd, "# Open network\n");
|
||||
fprintf(hostapd, "auth_algs=1\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-personal")) {
|
||||
fprintf(hostapd, "# WPA2-Personal\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
|
||||
fprintf(hostapd, "wpa_pairwise=CCMP\n");
|
||||
if (secret)
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
} else if (!strcmp(security_mode, "wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA3-Personal\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
if (secret)
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
fprintf(hostapd, "ieee80211w=2\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA2/WPA3 Mixed\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
if (secret) {
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
}
|
||||
fprintf(hostapd, "ieee80211w=1\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate hostapd config for all APs on a radio (multi-SSID support) */
|
||||
static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
|
||||
struct lyd_node *radio_node, struct lyd_node *config)
|
||||
{
|
||||
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
|
||||
struct lyd_node *primary_cif, *cif;
|
||||
struct lyd_node *primary_wifi, *primary_ap;
|
||||
struct lyd_node *security, *secret_node;
|
||||
const char *country, *channel, *band;
|
||||
const char *primary_ifname;
|
||||
char hostapd_conf[256];
|
||||
char **ap_list = NULL;
|
||||
FILE *hostapd = NULL;
|
||||
bool wifi6_enabled;
|
||||
int ap_count = 0;
|
||||
int i;
|
||||
|
||||
int rc = SR_ERR_OK;
|
||||
|
||||
wifi_find_radio_aps(cifs, radio_name, &ap_list, &ap_count);
|
||||
|
||||
if (ap_count == 0) {
|
||||
DEBUG("No APs found on radio %s", radio_name);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
DEBUG("Generating hostapd config for radio %s (%d APs)", radio_name, ap_count);
|
||||
|
||||
primary_ifname = ap_list[0];
|
||||
primary_cif = NULL;
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "interface") {
|
||||
if (!strcmp(lydx_get_cattr(cif, "name"), primary_ifname)) {
|
||||
primary_cif = cif;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!primary_cif) {
|
||||
ERROR("Failed to find primary AP interface %s", primary_ifname);
|
||||
rc = SR_ERR_INVAL_ARG;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
primary_wifi = lydx_get_child(primary_cif, "wifi");
|
||||
primary_ap = lydx_get_child(primary_wifi, "access-point");
|
||||
|
||||
/* Get AP configuration */
|
||||
ssid = lydx_get_cattr(primary_ap, "ssid");
|
||||
hidden = lydx_get_cattr(primary_ap, "hidden");
|
||||
security = lydx_get_child(primary_ap, "security");
|
||||
security_mode = lydx_get_cattr(security, "mode");
|
||||
secret_name = lydx_get_cattr(security, "secret");
|
||||
secret = NULL;
|
||||
|
||||
/* Get radio configuration */
|
||||
country = lydx_get_cattr(radio_node, "country-code");
|
||||
band = lydx_get_cattr(radio_node, "band");
|
||||
channel = lydx_get_cattr(radio_node, "channel");
|
||||
wifi6_enabled = lydx_get_bool(radio_node, "enable_wifi6");
|
||||
|
||||
/* Get secret from keystore if not open network */
|
||||
if (secret_name && strcmp(security_mode, "open") != 0) {
|
||||
secret_node = lydx_get_xpathf(config,
|
||||
"/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key",
|
||||
secret_name);
|
||||
if (secret_node) {
|
||||
secret = lyd_get_value(secret_node);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(hostapd_conf, sizeof(hostapd_conf), HOSTAPD_CONF_NEXT, radio_name);
|
||||
|
||||
hostapd = fopen(hostapd_conf, "w");
|
||||
if (!hostapd) {
|
||||
ERROR("Failed to create hostapd config: %s", hostapd_conf);
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fprintf(hostapd, "# Generated by Infix confd - WiFi Radio %s\n", radio_name);
|
||||
fprintf(hostapd, "# Primary BSS: %s", primary_ifname);
|
||||
if (ap_count > 1)
|
||||
fprintf(hostapd, " (%d total APs)\n\n", ap_count);
|
||||
else
|
||||
fprintf(hostapd, "\n\n");
|
||||
|
||||
fprintf(hostapd, "interface=%s\n", primary_ifname);
|
||||
fprintf(hostapd, "driver=nl80211\n");
|
||||
fprintf(hostapd, "ctrl_interface=/run/hostapd\n\n");
|
||||
|
||||
fprintf(hostapd, "ssid=%s\n", ssid);
|
||||
if (hidden && !strcmp(hidden, "true"))
|
||||
fprintf(hostapd, "ignore_broadcast_ssid=1\n");
|
||||
fprintf(hostapd, "\n");
|
||||
|
||||
if (country)
|
||||
fprintf(hostapd, "country_code=%s\n", country);
|
||||
|
||||
/* Enable 802.11d (regulatory domain) and 802.11h (spectrum management/DFS) */
|
||||
fprintf(hostapd, "ieee80211d=1\n");
|
||||
fprintf(hostapd, "ieee80211h=1\n");
|
||||
|
||||
/* Band and channel configuration */
|
||||
if (band) {
|
||||
/* Set hardware mode based on band */
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "hw_mode=g\n");
|
||||
} else if (!strcmp(band, "5GHz") || !strcmp(band, "6GHz")) {
|
||||
fprintf(hostapd, "hw_mode=a\n");
|
||||
}
|
||||
|
||||
/* Set channel */
|
||||
if (channel) {
|
||||
if (strcmp(channel, "auto") == 0) {
|
||||
/*
|
||||
Use default channels: 6 for 2.4GHz, 36 for 5GHz, 109 for 6GHz, this
|
||||
is a temporary hack, replace with logic for finding best free channel.
|
||||
*/
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "channel=6\n");
|
||||
} else if (!strcmp(band, "5GHz")) {
|
||||
fprintf(hostapd, "channel=36\n");
|
||||
} else if (!strcmp(band, "6GHz")) {
|
||||
fprintf(hostapd, "channel=109\n");
|
||||
} else {
|
||||
/* Unknown band - use ACS */
|
||||
fprintf(hostapd, "channel=0\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(hostapd, "channel=%s\n", channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (band) {
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
/* 2.4GHz: Enable 802.11n (HT), optionally WiFi 6 */
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
if (wifi6_enabled) {
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
}
|
||||
} else if (!strcmp(band, "5GHz")) {
|
||||
/* 5GHz: Enable 802.11n and 802.11ac, optionally WiFi 6 */
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
fprintf(hostapd, "ieee80211ac=1\n");
|
||||
if (wifi6_enabled) {
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
}
|
||||
} else if (!strcmp(band, "6GHz")) {
|
||||
/* 6GHz: Enable 802.11ax (WiFi 6E required) */
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
fprintf(hostapd, "ieee80211ac=1\n");
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
}
|
||||
}
|
||||
fprintf(hostapd, "\n");
|
||||
|
||||
/* Security configuration */
|
||||
if (!strcmp(security_mode, "open")) {
|
||||
fprintf(hostapd, "# Open network (no encryption)\n");
|
||||
fprintf(hostapd, "auth_algs=1\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-personal")) {
|
||||
fprintf(hostapd, "# WPA2-Personal\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
|
||||
fprintf(hostapd, "wpa_pairwise=CCMP\n");
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
} else if (!strcmp(security_mode, "wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA3-Personal\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
fprintf(hostapd, "ieee80211w=2\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA2/WPA3 Mixed Mode\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
fprintf(hostapd, "ieee80211w=1\n");
|
||||
}
|
||||
|
||||
/* Add BSS sections for secondary APs (multi-SSID) */
|
||||
for (i = 1; i < ap_count; i++) {
|
||||
DEBUG("Adding BSS section for secondary AP %s", ap_list[i]);
|
||||
rc = wifi_gen_bss_section(hostapd, cifs, ap_list[i], config);
|
||||
if (rc != SR_ERR_OK) {
|
||||
ERROR("Failed to generate BSS section for %s", ap_list[i]);
|
||||
fclose(hostapd);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(hostapd);
|
||||
|
||||
cleanup:
|
||||
for (i = 0; i < ap_count; i++)
|
||||
free(ap_list[i]);
|
||||
free(ap_list);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hardware_cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
|
||||
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
|
||||
{
|
||||
@@ -187,49 +639,186 @@ static int hardware_cand(sr_session_ctx_t *session, uint32_t sub_id, const char
|
||||
|
||||
int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd)
|
||||
{
|
||||
struct lyd_node *difs = NULL, *dif = NULL, *cifs = NULL, *cif = NULL;
|
||||
struct lyd_node *difs = NULL, *dif = NULL;
|
||||
int rc = SR_ERR_OK;
|
||||
|
||||
if (event != SR_EV_DONE || !lydx_find_xpathf(diff, XPATH_BASE_))
|
||||
if (!lydx_find_xpathf(diff, XPATH_BASE_))
|
||||
return SR_ERR_OK;
|
||||
|
||||
cifs = lydx_get_descendant(config, "hardware", "component", NULL);
|
||||
difs = lydx_get_descendant(diff, "hardware", "component", NULL);
|
||||
|
||||
LYX_LIST_FOR_EACH(difs, dif, "component") {
|
||||
enum lydx_op op;
|
||||
struct lyd_node *state;
|
||||
struct lyd_node *state, *cif;
|
||||
const char *admin_state;
|
||||
const char *class, *name;
|
||||
|
||||
op = lydx_get_op(dif);
|
||||
name = lydx_get_cattr(dif, "name");
|
||||
if (op == LYDX_OP_DELETE) {
|
||||
if (usb_authorize(confd->root, name, 0)) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto err;;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "component") {
|
||||
if (strcmp(name, lydx_get_cattr(cif, "name")))
|
||||
/* Get the current config node for this component */
|
||||
cif = lydx_get_xpathf(config, "/hardware/component[name='%s']", name);
|
||||
if (!cif)
|
||||
continue;
|
||||
|
||||
class = lydx_get_cattr(cif, "class");
|
||||
|
||||
/* Handle USB components */
|
||||
if (!strcmp(class, "infix-hardware:usb")) {
|
||||
if (event != SR_EV_DONE)
|
||||
continue;
|
||||
|
||||
class = lydx_get_cattr(cif, "class");
|
||||
if (strcmp(class, "infix-hardware:usb")) {
|
||||
if (op == LYDX_OP_DELETE) {
|
||||
/* Handle USB deletion */
|
||||
if (usb_authorize(confd->root, name, 0)) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto err;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
state = lydx_get_child(dif, "state");
|
||||
admin_state = lydx_get_cattr(state, "admin-state");
|
||||
if (usb_authorize(confd->root, name, !strcmp(admin_state, "unlocked"))) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto err;;
|
||||
goto err;
|
||||
}
|
||||
} else if (!strcmp(class, "infix-hardware:wifi")) {
|
||||
struct lyd_node *interfaces_config, *interfaces_diff;
|
||||
struct lyd_node **wifi_iface_list = NULL;
|
||||
struct lyd_node *station, *ap;
|
||||
struct lyd_node *cwifi_radio;
|
||||
int wifi_iface_count = 0;
|
||||
char src[40], dst[40];
|
||||
int ap_interfaces = 0;
|
||||
|
||||
switch (event) {
|
||||
case SR_EV_ABORT:
|
||||
continue;
|
||||
case SR_EV_CHANGE:
|
||||
break;
|
||||
case SR_EV_DONE:
|
||||
interfaces_diff = lydx_get_descendant(diff, "interfaces", "interface", NULL);
|
||||
|
||||
wifi_find_interfaces_on_radio(interfaces_diff, name,
|
||||
&wifi_iface_list, &wifi_iface_count);
|
||||
if (wifi_iface_count > 0) {
|
||||
bool running, enabled;
|
||||
station = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "station", NULL);
|
||||
ap = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "access-point", NULL);
|
||||
|
||||
if (station || !ap || lydx_get_op(ap) == LYDX_OP_DELETE) {
|
||||
const char *ifname = lydx_get_cattr(wifi_iface_list[0], "name");
|
||||
running = !systemf("initctl -bfq status wpa_supplicant:%s", ifname);
|
||||
if (lydx_get_op(station) == LYDX_OP_DELETE) {
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
erasef(WPA_SUPPLICANT_CONF_NEXT, ifname);
|
||||
systemf("initctl -bfq disable wifi@%s", ifname);
|
||||
} else {
|
||||
snprintf(src, sizeof(src), WPA_SUPPLICANT_CONF_NEXT, ifname);
|
||||
snprintf(dst, sizeof(dst), WPA_SUPPLICANT_CONF, ifname);
|
||||
running = !systemf("initctl -bfq status wpa_supplicant:%s", ifname);
|
||||
enabled = fexistf(WPA_SUPPLICANT_CONF_NEXT, ifname);
|
||||
|
||||
if (enabled) {
|
||||
(void)rename(src, dst);
|
||||
if (running)
|
||||
systemf("initctl -bfq touch wifi@%s", ifname);
|
||||
else
|
||||
systemf("initctl -bfq enable wifi@%s", ifname);
|
||||
}
|
||||
}
|
||||
} else if (wifi_iface_count > 0) {
|
||||
/* AP mode - activate hostapd for radio */
|
||||
snprintf(src, sizeof(src), HOSTAPD_CONF_NEXT, name);
|
||||
snprintf(dst, sizeof(dst), HOSTAPD_CONF, name);
|
||||
|
||||
running = !systemf("initctl -bfq status hostapd:%s", name);
|
||||
enabled = fexistf(HOSTAPD_CONF_NEXT, name);
|
||||
|
||||
if (enabled) {
|
||||
(void)rename(src, dst);
|
||||
ap_interfaces++;
|
||||
|
||||
if (running)
|
||||
systemf("initctl -bfq touch hostapd@%s", name);
|
||||
else
|
||||
systemf("initctl -bfq enable hostapd@%s", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ap_interfaces) {
|
||||
systemf("initctl -bfq disable hostapd@%s", name);
|
||||
erasef(HOSTAPD_CONF, name);
|
||||
erasef(HOSTAPD_CONF_NEXT, name);
|
||||
}
|
||||
free(wifi_iface_list);
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
cwifi_radio = lydx_get_child(cif, "wifi-radio");
|
||||
|
||||
interfaces_config = lydx_get_descendant(config, "interfaces", "interface", NULL);
|
||||
|
||||
wifi_find_interfaces_on_radio(interfaces_config, name,
|
||||
&wifi_iface_list, &wifi_iface_count);
|
||||
|
||||
|
||||
if (!wifi_iface_count)
|
||||
continue;
|
||||
/*
|
||||
* A radio operates in one of three modes:
|
||||
* 1. Station mode: One station interface (client mode)
|
||||
* 2. AP mode: One or more AP interfaces (hostapd multi-SSID)
|
||||
* 3. Scan-only mode: WiFi interface with radio but no mode configured
|
||||
*
|
||||
* Check for station first - there can be at most one per radio.
|
||||
* If no station or AP is configured, default to scan-only mode.
|
||||
*/
|
||||
station = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "station", NULL);
|
||||
ap = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "access-point", NULL);
|
||||
if (wifi_iface_count == 1 && station) {
|
||||
/* Station mode (with or without SSID for scan-only) */
|
||||
struct lyd_node *iface = wifi_iface_list[0];
|
||||
if (lydx_is_enabled(iface, "enabled")) {
|
||||
const char *ifname = lydx_get_cattr(iface, "name");
|
||||
rc = wifi_gen_station(ifname, station, name, config);
|
||||
if (rc != SR_ERR_OK) {
|
||||
ERROR("Failed to generate station config for %s", ifname);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
} else if (!station && !ap) {
|
||||
/* No station/AP configured - default to scan-only mode */
|
||||
struct lyd_node *iface = wifi_iface_list[0];
|
||||
if (lydx_is_enabled(iface, "enabled")) {
|
||||
const char *ifname = lydx_get_cattr(iface, "name");
|
||||
rc = wifi_gen_station(ifname, NULL, name, config);
|
||||
if (rc != SR_ERR_OK) {
|
||||
ERROR("Failed to generate scan-only config for %s", ifname);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Multiple interfaces or APs */
|
||||
rc = wifi_gen_aps_on_radio(name, interfaces_config, cwifi_radio, config);
|
||||
if (rc != SR_ERR_OK) {
|
||||
ERROR("Failed to generate AP config for radio %s", name);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
next:
|
||||
/* Free the interface list */
|
||||
free(wifi_iface_list);
|
||||
wifi_iface_list = NULL;
|
||||
wifi_iface_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
|
||||
return rc;
|
||||
}
|
||||
int hardware_candidate_init(struct confd *confd)
|
||||
|
||||
+97
-84
@@ -3,108 +3,121 @@
|
||||
|
||||
#include "interfaces.h"
|
||||
|
||||
#define WPA_SUPPLICANT_FINIT_CONF "/etc/finit.d/available/wpa_supplicant-%s.conf"
|
||||
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
|
||||
/*
|
||||
* WiFi Interface Management
|
||||
*
|
||||
* This file handles only virtual WiFi interface creation/deletion.
|
||||
* WiFi daemon configuration (hostapd/wpa_supplicant) is handled by
|
||||
* hardware.c when the WiFi radio (phy) is configured.
|
||||
*/
|
||||
|
||||
static int wifi_gen_config(const char *ifname, const char *ssid, const char *country, const char *secret, const char* encryption, struct dagger *net)
|
||||
/*
|
||||
* Determine WiFi mode from YANG configuration
|
||||
*/
|
||||
typedef enum wifi_mode_t {
|
||||
wifi_station,
|
||||
wifi_ap,
|
||||
wifi_unknown
|
||||
} wifi_mode_t;
|
||||
static wifi_mode_t wifi_get_mode(struct lyd_node *wifi)
|
||||
{
|
||||
FILE *wpa_supplicant = NULL, *wpa = NULL;
|
||||
char *encryption_str;
|
||||
if (lydx_get_child(wifi, "access-point"))
|
||||
return wifi_ap;
|
||||
else
|
||||
return wifi_station; /* Need to return station even if "station" also is false, since that is the default scanning mode */
|
||||
}
|
||||
|
||||
int wifi_mode_changed(struct lyd_node *wifi)
|
||||
{
|
||||
struct lyd_node *station, *ap;
|
||||
enum lydx_op station_op, ap_op;
|
||||
|
||||
if (!wifi)
|
||||
return 0;
|
||||
station = lydx_get_child(wifi, "station");
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
if (station)
|
||||
station_op = lydx_get_op(station);
|
||||
if (ap)
|
||||
ap_op = lydx_get_op(ap);
|
||||
|
||||
return ((station && station_op == LYDX_OP_DELETE) || (ap && ap_op == LYDX_OP_DELETE));
|
||||
}
|
||||
/*
|
||||
* Add WiFi virtual interface using iw
|
||||
*/
|
||||
int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
|
||||
{
|
||||
const char *ifname, *radio;
|
||||
struct lyd_node *wifi;
|
||||
wifi_mode_t mode;
|
||||
FILE *iw;
|
||||
int rc = SR_ERR_OK;
|
||||
|
||||
if (!secret && (ssid && country && encryption)) {
|
||||
/* Not an error, updated from two ways, interface cb and keystore cb. */
|
||||
return 0;
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
wifi = lydx_get_child(cif, "wifi");
|
||||
|
||||
if (!wifi) {
|
||||
ERROR("WiFi interface %s: no wifi container", ifname);
|
||||
return SR_ERR_INVAL_ARG;
|
||||
}
|
||||
|
||||
wpa = dagger_fopen_net_init(net, ifname, NETDAG_INIT_POST, "wpa_supplicant.sh");
|
||||
if (!wpa) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
radio = lydx_get_cattr(wifi, "radio");
|
||||
if (!radio) {
|
||||
ERROR("WiFi interface %s: missing radio reference", ifname);
|
||||
return SR_ERR_INVAL_ARG;
|
||||
}
|
||||
|
||||
iw = dagger_fopen_net_init(net, ifname, NETDAG_INIT_PRE, "wifi-iface.sh");
|
||||
if (!iw) {
|
||||
ERROR("Failed to open dagger file for WiFi interface creation");
|
||||
return SR_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
mode = wifi_get_mode(wifi);
|
||||
|
||||
fprintf(iw, "# Generated by Infix confd - WiFi Interface Creation\n");
|
||||
fprintf(iw, "# Create %s interface %s on radio %s\n",
|
||||
mode == wifi_station ? "station" : "access point", ifname, radio);
|
||||
|
||||
switch(mode) {
|
||||
case wifi_station:
|
||||
fprintf(iw, "iw phy %s interface add %s type managed\n", radio, ifname);
|
||||
break;
|
||||
case wifi_ap:
|
||||
fprintf(iw, "iw phy %s interface add %s type __ap\n", radio, ifname);
|
||||
break;
|
||||
default:
|
||||
ERROR("WiFi mode %d unknown", mode);
|
||||
rc = SR_ERR_INVAL_ARG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fprintf(wpa, "# Generated by Infix confd\n");
|
||||
|
||||
fprintf(wpa, "if [ -f '/etc/finit.d/enabled/wifi@%s.conf' ];then\n", ifname);
|
||||
fprintf(wpa, "initctl -bfqn touch wifi@%s\n", ifname);
|
||||
fprintf(wpa, "else\n");
|
||||
fprintf(wpa, "initctl -bfqn enable wifi@%s\n", ifname);
|
||||
fprintf(wpa, "fi\n");
|
||||
fclose(wpa);
|
||||
|
||||
wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF, ifname);
|
||||
if (!wpa_supplicant) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!secret || !ssid || !country || !encryption) {
|
||||
fprintf(wpa_supplicant,
|
||||
"ctrl_interface=/run/wpa_supplicant\n"
|
||||
"autoscan=periodic:10\n"
|
||||
"ap_scan=1\n");
|
||||
} else {
|
||||
if (!strcmp(encryption, "disabled")) {
|
||||
asprintf(&encryption_str, "key_mgmt=NONE");
|
||||
} else {
|
||||
asprintf(&encryption_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret);
|
||||
}
|
||||
fprintf(wpa_supplicant,
|
||||
"country=%s\n"
|
||||
"ctrl_interface=/run/wpa_supplicant\n"
|
||||
"autoscan=periodic:10\n"
|
||||
"ap_scan=1\n"
|
||||
"network={\n"
|
||||
"bgscan=\"simple: 30:-45:300\"\n"
|
||||
"ssid=\"%s\"\n"
|
||||
"%s\n"
|
||||
"}\n", country, ssid, encryption_str);
|
||||
free(encryption_str);
|
||||
}
|
||||
fclose(wpa_supplicant);
|
||||
|
||||
out:
|
||||
fclose(iw);
|
||||
return rc;
|
||||
|
||||
}
|
||||
int wifi_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net)
|
||||
|
||||
/*
|
||||
* Delete WiFi virtual interface using iw
|
||||
*/
|
||||
int wifi_del_iface(struct lyd_node *dif, struct dagger *net)
|
||||
{
|
||||
const char *ssid, *secret_name, *secret, *ifname, *country, *encryption;
|
||||
struct lyd_node *wifi, *secret_node;
|
||||
const char *ifname;
|
||||
FILE *iw;
|
||||
|
||||
bool enabled;
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
ifname = lydx_get_cattr(dif, "name");
|
||||
|
||||
if (cif && !lydx_get_child(cif, "wifi")) {
|
||||
return wifi_gen_config(ifname, NULL, NULL, NULL, NULL, net);
|
||||
iw = dagger_fopen_net_exit(net, ifname, NETDAG_EXIT_POST, "wifi-iface.sh");
|
||||
if (!iw) {
|
||||
ERROR("Failed to open dagger file for WiFi interface deletion");
|
||||
return SR_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
enabled = lydx_get_bool(cif, "enabled");
|
||||
wifi = lydx_get_child(cif, "wifi");
|
||||
|
||||
ssid = lydx_get_cattr(wifi, "ssid");
|
||||
secret_name = lydx_get_cattr(wifi, "secret");
|
||||
country = lydx_get_cattr(wifi, "country-code");
|
||||
encryption = lydx_get_cattr(wifi, "encryption");
|
||||
secret_node = lydx_get_xpathf(cif, "../../keystore/symmetric-keys/symmetric-key[name='%s']", secret_name);
|
||||
secret = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
|
||||
|
||||
if (!enabled)
|
||||
return wifi_gen_del(cif, net);
|
||||
|
||||
return wifi_gen_config(ifname, ssid, country, secret, encryption, net);
|
||||
}
|
||||
|
||||
int wifi_gen_del(struct lyd_node *dif, struct dagger *net)
|
||||
{
|
||||
const char *ifname = lydx_get_cattr(dif, "name");
|
||||
FILE *iw = dagger_fopen_net_exit(net, ifname, NETDAG_EXIT_PRE, "iw.sh");
|
||||
|
||||
fprintf(iw, "# Generated by Infix confd\n");
|
||||
fprintf(iw, "# Generated by Infix confd - WiFi Interface Deletion\n");
|
||||
fprintf(iw, "ip link set %s down\n", ifname); /* Required to change modes. */
|
||||
fprintf(iw, "iw dev %s disconnect\n", ifname);
|
||||
fprintf(iw, "initctl -bfqn disable wifi@%s\n", ifname);
|
||||
fprintf(iw, "iw dev %s del\n", ifname);
|
||||
fclose(iw);
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
+12
-10
@@ -418,7 +418,7 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
|
||||
case IFT_VXLAN:
|
||||
return vxlan_gen(NULL, cif, ip);
|
||||
case IFT_WIFI:
|
||||
return wifi_gen(NULL, cif, net);
|
||||
return wifi_add_iface(cif, net);
|
||||
case IFT_ETH:
|
||||
return netdag_gen_ethtool(net, cif, dif);
|
||||
case IFT_LO:
|
||||
@@ -448,7 +448,10 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
||||
case IFT_ETH:
|
||||
return netdag_gen_ethtool(net, cif, dif);
|
||||
case IFT_WIFI:
|
||||
return wifi_gen(dif, cif, net);
|
||||
/* WiFi daemon config (hostapd/wpa_supplicant) is handled by
|
||||
* hardware.c when the radio (phy) is configured. Interface
|
||||
* creation/deletion is handled in netdag_gen_afspec_add(). */
|
||||
return 0;
|
||||
case IFT_DUMMY:
|
||||
case IFT_GRE:
|
||||
case IFT_GRETAP:
|
||||
@@ -472,9 +475,10 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif)
|
||||
case IFT_BRIDGE:
|
||||
case IFT_DUMMY:
|
||||
case IFT_LO:
|
||||
case IFT_WIFI:
|
||||
return lydx_get_child(dif, "custom-phys-address") || lydx_get_descendant(dif, "wifi", "radio", NULL) || wifi_mode_changed(lydx_get_child(dif, "wifi"));
|
||||
break;
|
||||
|
||||
case IFT_WIFI:
|
||||
case IFT_ETH:
|
||||
return lydx_get_child(dif, "custom-phys-address");
|
||||
|
||||
@@ -562,13 +566,12 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif,
|
||||
case IFT_LO:
|
||||
eth_gen_del(dif, ip);
|
||||
break;
|
||||
case IFT_WIFI:
|
||||
eth_gen_del(dif, ip);
|
||||
wifi_gen_del(dif, net);
|
||||
break;
|
||||
case IFT_VETH:
|
||||
veth_gen_del(dif, ip);
|
||||
break;
|
||||
case IFT_WIFI:
|
||||
wifi_del_iface(dif, net);
|
||||
break;
|
||||
case IFT_BRIDGE:
|
||||
case IFT_DUMMY:
|
||||
case IFT_GRE:
|
||||
@@ -587,7 +590,7 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif,
|
||||
|
||||
static sr_error_t netdag_gen_iface_timeout(struct dagger *net, const char *ifname, const char *iftype)
|
||||
{
|
||||
if (!strcmp(iftype, "infix-if-type:ethernet") || !strcmp(iftype, "infix-if-type:wifi")) {
|
||||
if (!strcmp(iftype, "infix-if-type:ethernet")) {
|
||||
FILE *wait = dagger_fopen_net_init(net, ifname, NETDAG_INIT_TIMEOUT, "wait-interface.sh");
|
||||
if (!wait) {
|
||||
return -EIO;
|
||||
@@ -734,10 +737,9 @@ static int netdag_init_iface(struct lyd_node *cif)
|
||||
return vlan_add_deps(cif);
|
||||
case IFT_VETH:
|
||||
return veth_add_deps(cif);
|
||||
|
||||
case IFT_WIFI:
|
||||
case IFT_DUMMY:
|
||||
case IFT_ETH:
|
||||
case IFT_WIFI:
|
||||
case IFT_GRE:
|
||||
case IFT_GRETAP:
|
||||
case IFT_LO:
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
_map(IFT_BRIDGE, "infix-if-type:bridge") \
|
||||
_map(IFT_DUMMY, "infix-if-type:dummy") \
|
||||
_map(IFT_ETH, "infix-if-type:ethernet") \
|
||||
_map(IFT_WIFI, "infix-if-type:wifi") \
|
||||
_map(IFT_GRE, "infix-if-type:gre") \
|
||||
_map(IFT_GRETAP, "infix-if-type:gretap") \
|
||||
_map(IFT_LAG, "infix-if-type:lag") \
|
||||
_map(IFT_LAG, "infix-if-type:lag") \
|
||||
_map(IFT_LO, "infix-if-type:loopback") \
|
||||
_map(IFT_VETH, "infix-if-type:veth") \
|
||||
_map(IFT_VLAN, "infix-if-type:vlan") \
|
||||
_map(IFT_VXLAN, "infix-if-type:vxlan") \
|
||||
/* */
|
||||
_map(IFT_WIFI, "infix-if-type:wifi") \
|
||||
/* */
|
||||
|
||||
enum iftype {
|
||||
#define ift_enum(_enum, _str) _enum,
|
||||
@@ -122,8 +122,9 @@ int bridge_mcd_gen(struct lyd_node *cifs);
|
||||
int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||
|
||||
/* if-wifi.c */
|
||||
int wifi_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net);
|
||||
int wifi_gen_del(struct lyd_node *dif, struct dagger *net);
|
||||
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
|
||||
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
|
||||
int wifi_mode_changed(struct lyd_node *wifi);
|
||||
|
||||
/* if-gre.c */
|
||||
int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||
|
||||
@@ -23,7 +23,7 @@ MODULES=(
|
||||
"infix-syslog@2025-11-17.yang"
|
||||
"iana-hardware@2018-03-13.yang"
|
||||
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
|
||||
"infix-hardware@2025-10-30.yang"
|
||||
"infix-hardware@2025-12-04.yang"
|
||||
"ieee802-dot1q-types@2022-10-29.yang"
|
||||
"infix-ip@2025-11-02.yang"
|
||||
"infix-if-type@2025-02-12.yang"
|
||||
|
||||
@@ -13,10 +13,18 @@ module infix-hardware {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
import infix-wifi-country-codes {
|
||||
prefix iwcc;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Vital Product Data augmentation of ieee-hardware and deviations.";
|
||||
|
||||
revision 2025-12-04 {
|
||||
description "Add WiFi radio survey container for channel utilization data.";
|
||||
reference "internal";
|
||||
}
|
||||
revision 2025-10-30 {
|
||||
description "Add phys-address leaf for hardware components and enable sensor support.";
|
||||
reference "internal";
|
||||
@@ -38,6 +46,38 @@ module infix-hardware {
|
||||
description "A two-letter country code.";
|
||||
}
|
||||
|
||||
/*
|
||||
* WiFi-specific typedefs
|
||||
*/
|
||||
|
||||
typedef wifi-radio-ref {
|
||||
type leafref {
|
||||
path "/iehw:hardware/iehw:component/iehw:name";
|
||||
}
|
||||
description
|
||||
"Reference to a WiFi radio hardware component.
|
||||
WiFi radios are hardware components with class 'ih:wifi'.";
|
||||
}
|
||||
|
||||
typedef wifi-band {
|
||||
type enumeration {
|
||||
enum "2.4GHz" {
|
||||
description "2.4 GHz band (channels 1-14, maximum compatibility)";
|
||||
}
|
||||
enum "5GHz" {
|
||||
description "5 GHz band (less congestion, higher throughput, recommended)";
|
||||
}
|
||||
enum "6GHz" {
|
||||
description "6 GHz band (WiFi 6E, requires compatible hardware)";
|
||||
}
|
||||
}
|
||||
description "WiFi frequency band selection.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Hardware class identities
|
||||
*/
|
||||
|
||||
identity usb {
|
||||
base iahw:hardware-class;
|
||||
description "This identity is used to describe a USB port";
|
||||
@@ -143,5 +183,335 @@ module infix-hardware {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* WiFi Radio configuration (when class = 'ih:wifi')
|
||||
*/
|
||||
|
||||
container wifi-radio {
|
||||
when "derived-from-or-self(../iehw:class, 'ih:wifi')";
|
||||
presence "WiFi radio configuration";
|
||||
description
|
||||
"WiFi radio/PHY configuration and operational data.
|
||||
|
||||
This container is present when the hardware component represents
|
||||
a WiFi radio (class 'ih:wifi'). WiFi radios are physical devices
|
||||
that can host multiple virtual WiFi interfaces (APs or Stations).";
|
||||
|
||||
leaf country-code {
|
||||
type iwcc:country-code;
|
||||
mandatory true;
|
||||
description
|
||||
"Two-letter ISO 3166-1 country code for regulatory compliance.
|
||||
|
||||
Sets the regulatory domain for this radio, determining:
|
||||
- Allowed channels and frequencies
|
||||
- Maximum transmit power
|
||||
- DFS (Dynamic Frequency Selection) requirements
|
||||
|
||||
Examples: 'US', 'DE', 'JP'.
|
||||
|
||||
WARNING: Incorrect values may violate local laws and regulations.";
|
||||
}
|
||||
|
||||
leaf channel {
|
||||
type union {
|
||||
type uint16 {
|
||||
range "1..196";
|
||||
}
|
||||
type enumeration {
|
||||
enum "auto" {
|
||||
description "Automatic channel selection (ACS)";
|
||||
}
|
||||
}
|
||||
}
|
||||
default "auto";
|
||||
description
|
||||
"Operating channel number.
|
||||
|
||||
Required for Access Point mode.
|
||||
Not used in Station mode (station uses AP's channel).
|
||||
|
||||
Channel availability depends on:
|
||||
- Configured band (2.4/5/6 GHz)
|
||||
- Regulatory domain (country-code)
|
||||
- Hardware capabilities
|
||||
|
||||
Common channels:
|
||||
- 2.4 GHz: 1-14 (channels 12-14 restricted in some countries)
|
||||
- 5 GHz: 36, 40, 44, 48, 149, 153, 157, 161, 165 (varies by region)
|
||||
- 6 GHz: 1-233 (WiFi 6E, where permitted)
|
||||
|
||||
Set to 'auto' for automatic channel selection.";
|
||||
}
|
||||
|
||||
leaf band {
|
||||
type wifi-band;
|
||||
description
|
||||
"Frequency band selection.
|
||||
|
||||
Required for Access Point mode.
|
||||
Not used in Station mode (station uses AP's band).
|
||||
|
||||
Constraints:
|
||||
- Hardware must support the selected band
|
||||
- Regulatory domain affects channel availability
|
||||
- PHY mode must be compatible with selected band
|
||||
|
||||
Recommendation: Use 5GHz for better performance and less
|
||||
congestion in most environments.";
|
||||
}
|
||||
|
||||
leaf enable-wifi6 {
|
||||
type boolean;
|
||||
default false;
|
||||
description
|
||||
"Enable WiFi 6 (802.11ax) on 2.4GHz and 5GHz bands.
|
||||
|
||||
By default, WiFi 6 is enabled only on 6GHz (WiFi 6E).
|
||||
Set to 'true' to enable WiFi 6 on 2.4GHz and 5GHz bands.
|
||||
|
||||
WiFi 6 provides:
|
||||
- OFDMA (better multi-user efficiency)
|
||||
- Target Wake Time (better battery life)
|
||||
- 1024-QAM (higher throughput)
|
||||
- BSS Coloring (reduced interference)
|
||||
|
||||
Requires:
|
||||
- Hardware support for 802.11ax
|
||||
- Compatible clients for full benefits
|
||||
|
||||
Note: 6GHz band always uses WiFi 6 regardless of this setting.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Operational state
|
||||
*/
|
||||
|
||||
leaf frequency {
|
||||
config false;
|
||||
type uint32;
|
||||
units "MHz";
|
||||
description
|
||||
"Current operating frequency in MHz.
|
||||
|
||||
Derived from the configured channel and band.
|
||||
|
||||
Example values:
|
||||
- 2412 MHz (channel 1, 2.4 GHz)
|
||||
- 5180 MHz (channel 36, 5 GHz)
|
||||
- 5955 MHz (channel 1, 6 GHz)";
|
||||
}
|
||||
|
||||
leaf noise {
|
||||
config false;
|
||||
type int16;
|
||||
units "dBm";
|
||||
description
|
||||
"Background noise level on current channel in dBm.
|
||||
|
||||
Lower (more negative) values indicate a cleaner RF environment.
|
||||
|
||||
Typical values:
|
||||
- -95 to -100 dBm: Very low noise (excellent)
|
||||
- -85 to -95 dBm: Low noise (good)
|
||||
- -75 to -85 dBm: Moderate noise
|
||||
- -65 to -75 dBm: High noise (congested)";
|
||||
}
|
||||
|
||||
leaf-list supported-channels {
|
||||
config false;
|
||||
type uint16;
|
||||
description
|
||||
"List of channels supported by this radio in the current
|
||||
regulatory domain.
|
||||
|
||||
Channels depend on:
|
||||
- Hardware capabilities
|
||||
- Configured country-code
|
||||
- Band selection
|
||||
|
||||
This list reflects actual usable channels after applying
|
||||
regulatory constraints.";
|
||||
}
|
||||
|
||||
leaf max-txpower {
|
||||
config false;
|
||||
type uint8;
|
||||
units "dBm";
|
||||
description
|
||||
"Maximum transmit power allowed by the regulatory domain
|
||||
for the current channel.
|
||||
|
||||
This is the regulatory limit for the current band and channel.";
|
||||
}
|
||||
|
||||
leaf num-virtual-interfaces {
|
||||
config false;
|
||||
type uint8;
|
||||
description
|
||||
"Number of virtual interfaces (AP/Station) currently
|
||||
configured on this radio.";
|
||||
}
|
||||
|
||||
leaf driver {
|
||||
config false;
|
||||
type string;
|
||||
description
|
||||
"WiFi driver name (e.g., mt798x-wmac, ath10k).";
|
||||
}
|
||||
|
||||
container max-interfaces {
|
||||
config false;
|
||||
description
|
||||
"Maximum number of virtual interfaces supported by this radio.";
|
||||
|
||||
leaf ap {
|
||||
type uint8;
|
||||
description
|
||||
"Maximum number of AP interfaces.";
|
||||
}
|
||||
|
||||
leaf station {
|
||||
type uint8;
|
||||
description
|
||||
"Maximum number of station interfaces.";
|
||||
}
|
||||
}
|
||||
|
||||
list bands {
|
||||
config false;
|
||||
key "band";
|
||||
description
|
||||
"Supported frequency bands and their capabilities.";
|
||||
|
||||
leaf band {
|
||||
type string;
|
||||
description
|
||||
"Band identifier from iw (e.g., '1' for 2.4GHz, '2' for 5GHz).";
|
||||
}
|
||||
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"Human-readable band name (e.g., '2.4GHz', '5GHz', '6GHz').";
|
||||
}
|
||||
|
||||
leaf ht-capable {
|
||||
type boolean;
|
||||
description
|
||||
"High Throughput (802.11n) support.";
|
||||
}
|
||||
|
||||
leaf vht-capable {
|
||||
type boolean;
|
||||
description
|
||||
"Very High Throughput (802.11ac) support.";
|
||||
}
|
||||
|
||||
leaf he-capable {
|
||||
type boolean;
|
||||
description
|
||||
"High Efficiency (802.11ax/WiFi 6) support.";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Channel survey data (operational state)
|
||||
*/
|
||||
|
||||
container survey {
|
||||
config false;
|
||||
description
|
||||
"WiFi channel survey data providing channel utilization
|
||||
and interference information.
|
||||
|
||||
This data is collected from the WiFi driver and provides
|
||||
insights into channel occupancy, noise levels, and RF activity.";
|
||||
|
||||
list channel {
|
||||
key "frequency";
|
||||
description
|
||||
"Per-channel survey information.
|
||||
|
||||
Includes utilization metrics for all channels scanned by
|
||||
the radio, not just the currently active channel.";
|
||||
|
||||
leaf frequency {
|
||||
type uint32;
|
||||
units "MHz";
|
||||
description
|
||||
"Channel center frequency in MHz.
|
||||
|
||||
Examples:
|
||||
- 2412 MHz (2.4 GHz channel 1)
|
||||
- 5180 MHz (5 GHz channel 36)
|
||||
- 5955 MHz (6 GHz channel 1)";
|
||||
}
|
||||
|
||||
leaf in-use {
|
||||
type boolean;
|
||||
description
|
||||
"Whether this channel is currently in use by the radio.
|
||||
|
||||
Only one channel will have this set to true at a time.";
|
||||
}
|
||||
|
||||
leaf noise {
|
||||
type int16;
|
||||
units "dBm";
|
||||
description
|
||||
"Background noise level on this channel in dBm.
|
||||
|
||||
Lower (more negative) values indicate cleaner RF environment.
|
||||
|
||||
Typical values:
|
||||
- -95 to -100 dBm: Very low noise (excellent)
|
||||
- -85 to -95 dBm: Low noise (good)
|
||||
- -75 to -85 dBm: Moderate noise
|
||||
- -65 to -75 dBm: High noise (congested)";
|
||||
}
|
||||
|
||||
leaf active-time {
|
||||
type uint32;
|
||||
units "milliseconds";
|
||||
description
|
||||
"Total time the radio was active on this channel.
|
||||
|
||||
This is the survey measurement period for this channel.";
|
||||
}
|
||||
|
||||
leaf busy-time {
|
||||
type uint32;
|
||||
units "milliseconds";
|
||||
description
|
||||
"Time the channel was detected as busy.
|
||||
|
||||
Includes time spent receiving frames, transmitting frames,
|
||||
and time the channel was busy due to other sources.
|
||||
|
||||
Channel utilization = (busy-time / active-time) * 100%";
|
||||
}
|
||||
|
||||
leaf receive-time {
|
||||
type uint32;
|
||||
units "milliseconds";
|
||||
description
|
||||
"Time spent receiving frames on this channel.
|
||||
|
||||
Subset of busy-time spent on frame reception.";
|
||||
}
|
||||
|
||||
leaf transmit-time {
|
||||
type uint32;
|
||||
units "milliseconds";
|
||||
description
|
||||
"Time spent transmitting frames on this channel.
|
||||
|
||||
Subset of busy-time spent on frame transmission.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,6 +928,9 @@ submodule infix-if-bridge {
|
||||
must "not(../ip:ipv4/ip:address or ../ip:ipv6/ip:address)" {
|
||||
error-message "Bridge ports cannot have IP addresses configured.";
|
||||
}
|
||||
must "not(derived-from-or-self(../if:type, 'infix-ift:wifi')) or ../infix-if:wifi/infix-if:access-point" {
|
||||
error-message "WiFi interfaces can only be bridge ports when configured as Access Points.";
|
||||
}
|
||||
description "Bridge association and port specific settings.";
|
||||
uses bridge-port-common;
|
||||
uses bridge-port-lower {
|
||||
|
||||
@@ -110,6 +110,6 @@ module infix-if-type {
|
||||
if-feature wifi;
|
||||
base infix-interface-type;
|
||||
base ianaift:ieee80211;
|
||||
description "WiFi interface";
|
||||
description "WiFi (802.11) interface";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,11 @@ submodule infix-if-wifi {
|
||||
import infix-if-type {
|
||||
prefix infixift;
|
||||
}
|
||||
import infix-wifi-country-codes {
|
||||
prefix iwcc;
|
||||
import ietf-hardware {
|
||||
prefix iehw;
|
||||
}
|
||||
import infix-hardware {
|
||||
prefix ih;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
@@ -30,12 +33,30 @@ submodule infix-if-wifi {
|
||||
description
|
||||
"WiFi-specific extensions to the standard IETF interfaces model.
|
||||
|
||||
This submodule defines configuration and operational data relevant to
|
||||
WiFi interfaces, including security settings, network
|
||||
discovery, and regulatory compliance.
|
||||
This submodule defines configuration and operational data for WiFi
|
||||
virtual interfaces, supporting both Access Point (AP) and Station
|
||||
(client) modes.
|
||||
|
||||
It supports WiFi client mode and enables comprehensive management of
|
||||
wireless connections, including encryption, country codes, and scanning.";
|
||||
WiFi virtual interfaces are created on top of WiFi radios (PHYs)
|
||||
defined in the infix-wifi-radio module. The radio provides physical
|
||||
layer configuration (channel, power, PHY mode), while virtual
|
||||
interfaces provide network-layer configuration (SSID, security).
|
||||
|
||||
Key features:
|
||||
- Dual mode support: AP and Station
|
||||
- Multi-SSID: Multiple APs on same radio
|
||||
- Security: WPA2/WPA3 with keystore integration
|
||||
- Operational state: Connection status, RSSI, client lists";
|
||||
|
||||
revision 2025-12-17 {
|
||||
description
|
||||
"Major refactoring for AP mode support (BREAKING CHANGE):
|
||||
- Added radio reference (parent PHY)
|
||||
- Added wifi-mode choice (AP vs Station)
|
||||
- Reorganized configuration hierarchy
|
||||
- Old configurations must be migrated manually";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2025-12-12 {
|
||||
description "Adapt to new revision of model ietf-keystore.";
|
||||
@@ -43,7 +64,7 @@ submodule infix-if-wifi {
|
||||
}
|
||||
|
||||
revision 2025-05-27 {
|
||||
description "Initial revision.";
|
||||
description "Initial revision (Station mode only).";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
@@ -51,29 +72,6 @@ submodule infix-if-wifi {
|
||||
description "WiFi support is an optional build-time feature in Infix.";
|
||||
}
|
||||
|
||||
typedef encryption {
|
||||
type enumeration {
|
||||
enum auto {
|
||||
description
|
||||
"Enables WPA/WPA2/WPA3 encryption with automatic protocol
|
||||
negotiation. The system uses the strongest supported variant supported by Access Point.";
|
||||
}
|
||||
enum disabled {
|
||||
description
|
||||
"Disables encryption for an open network.
|
||||
|
||||
WARNING: Open networks transmit data unencrypted and should only
|
||||
be used in trusted environments.";
|
||||
}
|
||||
}
|
||||
description
|
||||
"Encryption modes available for WiFi connections.
|
||||
|
||||
- auto: Secure connection using WPA3/WPA2/WPA (auto-selected)
|
||||
- disabled: Open network (unencrypted)";
|
||||
}
|
||||
|
||||
|
||||
augment "/if:interfaces/if:interface" {
|
||||
when "derived-from-or-self(if:type, 'infixift:wifi')" {
|
||||
description
|
||||
@@ -82,107 +80,370 @@ submodule infix-if-wifi {
|
||||
|
||||
container wifi {
|
||||
if-feature wifi;
|
||||
presence "Configure Wi-Fi settings";
|
||||
presence "Configure Wi-Fi virtual interface";
|
||||
|
||||
description
|
||||
"WiFi-specific configuration and operational data.";
|
||||
"WiFi virtual interface configuration.
|
||||
|
||||
leaf country-code {
|
||||
type iwcc:country-code;
|
||||
mandatory true;
|
||||
description
|
||||
"Two-letter ISO 3166-1 country code for regulatory compliance.
|
||||
Each WiFi interface represents a virtual interface (VAP - Virtual
|
||||
Access Point, or Station) created on a physical radio.
|
||||
|
||||
Examples: 'US', 'DE', 'JP'.
|
||||
The interface must reference a radio defined in infix-wifi-radio
|
||||
module, which provides the physical layer configuration.";
|
||||
|
||||
WARNING: Incorrect values may violate local laws.";
|
||||
|
||||
|
||||
}
|
||||
|
||||
leaf encryption {
|
||||
default auto;
|
||||
type encryption;
|
||||
|
||||
description
|
||||
"WiFi encryption method.
|
||||
|
||||
- auto (default): Enables WPA2/WPA3 auto-negotiation
|
||||
- disabled: Disables encryption (open network)";
|
||||
}
|
||||
|
||||
leaf ssid {
|
||||
type string {
|
||||
length "1..32";
|
||||
leaf radio {
|
||||
type leafref {
|
||||
path "/iehw:hardware/iehw:component/iehw:name";
|
||||
}
|
||||
mandatory true;
|
||||
must "derived-from-or-self(/iehw:hardware/iehw:component[iehw:name=current()]/iehw:class, 'ih:wifi')" {
|
||||
error-message "Referenced hardware component must be a WiFi radio (class 'ih:wifi')";
|
||||
}
|
||||
|
||||
description
|
||||
"WiFi network name (SSID).
|
||||
"Reference to parent WiFi radio (PHY).
|
||||
|
||||
Case-sensitive, must match the target network.
|
||||
References a hardware component with class 'ih:wifi'.
|
||||
The radio must exist and be configured before creating
|
||||
virtual interfaces.
|
||||
|
||||
Length: 1–32 characters.";
|
||||
Example: 'phy0' for the first WiFi radio.
|
||||
|
||||
All physical layer settings (channel, power, regulatory)
|
||||
are inherited from the radio configuration.";
|
||||
}
|
||||
|
||||
leaf secret {
|
||||
type ks:central-symmetric-key-ref;
|
||||
mandatory true;
|
||||
must "../encryption != 'disabled'" {
|
||||
error-message
|
||||
"Pre-shared key required unless encryption is disabled.";
|
||||
}
|
||||
|
||||
choice wifi-mode {
|
||||
description
|
||||
"Pre-shared key (PSK) for WPA-secured networks.";
|
||||
}
|
||||
"WiFi interface operating mode.
|
||||
|
||||
leaf rssi {
|
||||
config false;
|
||||
type int16;
|
||||
units "dBm";
|
||||
description
|
||||
"Current received signal strength (RSSI) in dBm.
|
||||
When no mode is configured, the interface operates in scan-only
|
||||
mode, allowing discovery of available WiFi networks.
|
||||
|
||||
Lower (more negative) values indicate stronger signals.";
|
||||
}
|
||||
Once you've identified a network, configure either:
|
||||
- Station mode: Connect to an existing WiFi network
|
||||
- Access Point mode: Create a WiFi network for clients
|
||||
|
||||
list scan-results {
|
||||
config false;
|
||||
key ssid;
|
||||
description
|
||||
"List of discovered networks.";
|
||||
Note: A radio can host either:
|
||||
- Multiple AP interfaces (multi-SSID), OR
|
||||
- A single Station interface
|
||||
|
||||
leaf ssid {
|
||||
type string;
|
||||
description
|
||||
"SSID of the discovered network.";
|
||||
Mixing AP and Station on the same radio is not supported.";
|
||||
|
||||
case station {
|
||||
container station {
|
||||
presence "Configure WiFi station (client) mode";
|
||||
|
||||
description
|
||||
"WiFi Station mode configuration.
|
||||
|
||||
In station mode, the interface acts as a WiFi client,
|
||||
connecting to an existing Access Point.
|
||||
|
||||
Only one station interface is allowed per radio.
|
||||
|
||||
Example use case: Connect to upstream WiFi network.";
|
||||
|
||||
leaf ssid {
|
||||
type string {
|
||||
length "1..32";
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"WiFi network name (SSID) to connect to.
|
||||
|
||||
Case-sensitive, must match the target network exactly.
|
||||
|
||||
Length: 1–32 characters.";
|
||||
}
|
||||
|
||||
container security {
|
||||
description
|
||||
"WiFi security configuration.";
|
||||
|
||||
leaf mode {
|
||||
type enumeration {
|
||||
enum auto {
|
||||
description
|
||||
"Automatic security negotiation.
|
||||
Tries WPA3-SAE, then WPA2-PSK, in that order.
|
||||
Recommended for maximum compatibility and security.";
|
||||
}
|
||||
enum disabled {
|
||||
description
|
||||
"Open network (no security).
|
||||
|
||||
WARNING: All traffic is transmitted unencrypted!
|
||||
Only use in trusted environments.";
|
||||
}
|
||||
}
|
||||
default auto;
|
||||
description
|
||||
"Security mode for WiFi connection.
|
||||
|
||||
- auto (default): WPA3/WPA2 auto-negotiation (secure)
|
||||
- disabled: Open network (insecure)";
|
||||
}
|
||||
|
||||
leaf secret {
|
||||
when "../mode != 'disabled'";
|
||||
type ks:central-symmetric-key-ref;
|
||||
mandatory true;
|
||||
description
|
||||
"Pre-shared key (PSK) reference.
|
||||
|
||||
References a symmetric key in the keystore.
|
||||
|
||||
For WPA2/WPA3 networks, this is the WiFi password.";
|
||||
}
|
||||
}
|
||||
|
||||
/* Operational state */
|
||||
|
||||
leaf rssi {
|
||||
config false;
|
||||
type int16;
|
||||
units "dBm";
|
||||
description
|
||||
"Current received signal strength indication (RSSI) in dBm.
|
||||
|
||||
More negative values indicate weaker signal.
|
||||
|
||||
Typical values:
|
||||
- -30 to -50 dBm: Excellent
|
||||
- -50 to -60 dBm: Good
|
||||
- -60 to -70 dBm: Fair
|
||||
- -70 to -80 dBm: Weak
|
||||
- Below -80 dBm: Very weak";
|
||||
}
|
||||
|
||||
list scan-results {
|
||||
config false;
|
||||
key ssid;
|
||||
description
|
||||
"List of discovered WiFi networks.
|
||||
|
||||
Updated periodically by background scanning.";
|
||||
|
||||
leaf ssid {
|
||||
type string;
|
||||
description "SSID of the discovered network.";
|
||||
}
|
||||
|
||||
leaf bssid {
|
||||
type yang:mac-address;
|
||||
description "BSSID (MAC address) of the AP.";
|
||||
}
|
||||
|
||||
leaf rssi {
|
||||
type int16;
|
||||
units "dBm";
|
||||
description "Signal strength of the network.";
|
||||
}
|
||||
|
||||
leaf channel {
|
||||
type uint16;
|
||||
description "Channel on which the network was detected.";
|
||||
}
|
||||
|
||||
leaf-list encryption {
|
||||
ordered-by user;
|
||||
type string;
|
||||
description
|
||||
"Human-readable security information.
|
||||
|
||||
Examples: 'WPA2-Personal', 'WPA3-SAE', 'Open'";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leaf bssid {
|
||||
type string;
|
||||
description
|
||||
"BSSID of the discovered network.";
|
||||
}
|
||||
case access-point {
|
||||
container access-point {
|
||||
presence "Configure WiFi Access Point mode";
|
||||
|
||||
leaf rssi {
|
||||
type int16;
|
||||
units "dBm";
|
||||
description
|
||||
"Signal strength of the network.";
|
||||
}
|
||||
description
|
||||
"WiFi Access Point mode configuration.
|
||||
|
||||
leaf channel {
|
||||
type int16;
|
||||
description
|
||||
"Channel on which the network was detected.";
|
||||
}
|
||||
In AP mode, the interface provides a WiFi network that
|
||||
clients can connect to.
|
||||
|
||||
leaf-list encryption {
|
||||
ordered-by user;
|
||||
type string;
|
||||
description
|
||||
"Human-readable description of the detected security.";
|
||||
Multiple AP interfaces can be created on the same radio
|
||||
for multi-SSID support (Guest network, IoT network, etc.).
|
||||
|
||||
Example use case: Create WiFi hotspot.";
|
||||
|
||||
must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:band" {
|
||||
error-message "Parent radio must have 'band' configured for Access Point mode";
|
||||
}
|
||||
|
||||
must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:channel" {
|
||||
error-message "Parent radio must have 'channel' configured for Access Point mode";
|
||||
}
|
||||
|
||||
must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:country-code != '00'" {
|
||||
error-message "Country code '00' (world regulatory domain) is not allowed for Access Point mode. Please configure a specific country code on the radio.";
|
||||
}
|
||||
|
||||
leaf ssid {
|
||||
type string {
|
||||
length "1..32";
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"WiFi network name (SSID) to broadcast.
|
||||
|
||||
This is the network name that clients will see when
|
||||
scanning for WiFi networks.
|
||||
|
||||
Length: 1–32 characters.";
|
||||
}
|
||||
|
||||
leaf hidden {
|
||||
type boolean;
|
||||
default false;
|
||||
description
|
||||
"Hide the SSID from broadcast beacons.
|
||||
|
||||
When true, the network will not appear in WiFi scans.
|
||||
Clients must know the exact SSID to connect.
|
||||
|
||||
Note: This provides minimal security benefit and may
|
||||
cause compatibility issues with some clients.";
|
||||
}
|
||||
|
||||
container security {
|
||||
description
|
||||
"WiFi security configuration.";
|
||||
|
||||
leaf mode {
|
||||
type enumeration {
|
||||
enum open {
|
||||
description
|
||||
"Open network (no encryption).
|
||||
|
||||
WARNING: All client traffic is unencrypted!
|
||||
Only use in controlled environments (captive portal, etc.).";
|
||||
}
|
||||
enum wpa2-personal {
|
||||
description
|
||||
"WPA2-Personal (WPA2-PSK).
|
||||
Widely compatible, secure for most use cases.";
|
||||
}
|
||||
enum wpa3-personal {
|
||||
description
|
||||
"WPA3-Personal (WPA3-SAE).
|
||||
Enhanced security with forward secrecy.
|
||||
Requires WPA3-capable clients.";
|
||||
}
|
||||
enum wpa2-wpa3-personal {
|
||||
description
|
||||
"WPA2/WPA3 transitional mode.
|
||||
Accepts both WPA2 and WPA3 clients.
|
||||
Recommended for maximum compatibility + security.";
|
||||
}
|
||||
}
|
||||
default wpa2-wpa3-personal;
|
||||
description
|
||||
"WiFi security mode.
|
||||
|
||||
Determines authentication and encryption methods.
|
||||
|
||||
Recommended: wpa2-wpa3-personal for best security
|
||||
and compatibility.";
|
||||
}
|
||||
|
||||
leaf secret {
|
||||
when "../mode != 'open'";
|
||||
type ks:central-symmetric-key-ref;
|
||||
mandatory true;
|
||||
description
|
||||
"Pre-shared key (PSK) reference.
|
||||
|
||||
References a symmetric key in the keystore.
|
||||
|
||||
This is the WiFi password that clients must provide
|
||||
to connect to the network.
|
||||
|
||||
Requirements:
|
||||
- WPA2/WPA3: 8-63 characters (configured in keystore)";
|
||||
}
|
||||
}
|
||||
|
||||
/* Operational state */
|
||||
|
||||
container stations {
|
||||
list station {
|
||||
config false;
|
||||
key mac-address;
|
||||
description
|
||||
"List of currently connected clients (stations).";
|
||||
|
||||
leaf mac-address {
|
||||
type yang:mac-address;
|
||||
description "Client MAC address.";
|
||||
}
|
||||
|
||||
leaf rssi {
|
||||
type int16;
|
||||
units "dBm";
|
||||
description "Client signal strength in dBm.";
|
||||
}
|
||||
|
||||
leaf connected-time {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
description "Time since client connected, in seconds.";
|
||||
}
|
||||
|
||||
leaf rx-packets {
|
||||
type uint32;
|
||||
description "Packets received from this client.";
|
||||
}
|
||||
|
||||
leaf tx-packets {
|
||||
type uint32;
|
||||
description "Packets transmitted to this client.";
|
||||
}
|
||||
|
||||
leaf rx-bytes {
|
||||
type uint32;
|
||||
units "octets";
|
||||
description "Bytes received from this client.";
|
||||
}
|
||||
|
||||
leaf tx-bytes {
|
||||
type uint32;
|
||||
units "octets";
|
||||
description "Bytes transmitted to this client.";
|
||||
}
|
||||
|
||||
leaf rx-speed {
|
||||
type uint32;
|
||||
units "100 kbit/s";
|
||||
description
|
||||
"Last received data rate from this client in 100 kbit/s.
|
||||
|
||||
Examples:
|
||||
- 10 = 1 Mbit/s
|
||||
- 65 = 6.5 Mbit/s
|
||||
- 866 = 86.6 Mbit/s";
|
||||
}
|
||||
|
||||
leaf tx-speed {
|
||||
type uint32;
|
||||
units "100 kbit/s";
|
||||
description
|
||||
"Last transmitted data rate to this client in 100 kbit/s.
|
||||
|
||||
Examples:
|
||||
- 10 = 1 Mbit/s
|
||||
- 65 = 6.5 Mbit/s
|
||||
- 866 = 86.6 Mbit/s";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ module infix-interfaces {
|
||||
import infix-if-type {
|
||||
prefix infix-ift;
|
||||
}
|
||||
import ietf-hardware {
|
||||
prefix iehw;
|
||||
}
|
||||
import infix-hardware {
|
||||
prefix ih;
|
||||
}
|
||||
|
||||
include infix-if-base;
|
||||
include infix-if-bridge;
|
||||
|
||||
@@ -57,7 +57,7 @@ module infix-keystore {
|
||||
case cleartext-symmetric-key {
|
||||
leaf cleartext-symmetric-key {
|
||||
type string;
|
||||
must "../infix-ks:key-format != 'infix-ct:wifi-preshared-key-format' or " +
|
||||
must "../../key-format != 'infix-ct:wifi-preshared-key-format' or " +
|
||||
"(string-length(.) >= 8 and string-length(.) <= 63)" {
|
||||
error-message "WiFi pre-shared key must be 8-63 characters long";
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@ module infix-wifi-country-codes {
|
||||
|
||||
The regulatory domain configuration follows the principles
|
||||
established in IETF RFCs for wireless access point management.";
|
||||
|
||||
revision 2025-11-28 {
|
||||
description
|
||||
"Add support for 00 - World regulatory domain.";
|
||||
reference
|
||||
"Internal";
|
||||
}
|
||||
revision 2025-06-02 {
|
||||
description
|
||||
"Initial revision for WiFi country code support.";
|
||||
@@ -27,6 +32,7 @@ module infix-wifi-country-codes {
|
||||
|
||||
typedef country-code {
|
||||
type enumeration {
|
||||
enum "00" { description "World regulatory domain (no country restrictions)"; }
|
||||
enum "AD" { description "Andorra"; }
|
||||
enum "AE" { description "United Arab Emirates"; }
|
||||
enum "AF" { description "Afghanistan"; }
|
||||
|
||||
@@ -4,7 +4,7 @@ _show_completions() {
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
commands="dhcp interface ntp routes software stp wifi-radio"
|
||||
commands="dhcp interface ntp routes software stp"
|
||||
|
||||
if [[ $COMP_CWORD -eq 1 ]]; then
|
||||
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
||||
|
||||
Reference in New Issue
Block a user