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:
Mattias Walström
2026-01-02 22:32:57 +01:00
parent da37904108
commit fec018148c
28 changed files with 2107 additions and 325 deletions
@@ -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
View File
@@ -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 -3
View File
@@ -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
+46
View File
@@ -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
]}
}
+16
View File
@@ -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"
+2 -1
View 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
+122
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+6 -5
View File
@@ -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);
+1 -1
View File
@@ -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"
+370
View File
@@ -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 {
+1 -1
View File
@@ -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";
}
}
+370 -109
View File
@@ -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: 132 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: 132 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: 132 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;
+1 -1
View File
@@ -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"; }
+1 -1
View File
@@ -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") )