From fec018148c9778efb9678056eed32233bdf3abf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 8 Dec 2025 16:07:57 +0100 Subject: [PATCH] Refactor WiFI implementation * Wi-Fi station still work (changed YANG model) * Wi-Fi access point supported Model is flexible to support even more modes --- .../bananapi,bpi-r3/etc/factory-config.cfg | 59 +- .../etc/finit.d/available/hostapd@.conf | 3 + .../rootfs/etc/finit.d/available/wifi@.conf | 4 +- .../etc/udev/rules.d/60-rename-wifi-phy.rules | 3 + .../70-remove-virtual-wifi-interfaces.rules | 7 + .../etc/udev/rules.d/70-rename-wifi.rules | 1 - doc/wifi.md | 451 ++++++++++--- src/confd/bin/bootstrap | 6 +- src/confd/bin/gen-hardware | 46 ++ .../migrate/1.7/30-remove-wifi-interfaces.sh | 16 + src/confd/share/migrate/1.7/Makefile.am | 3 +- src/confd/src/core.c | 122 ++++ src/confd/src/hardware.c | 623 +++++++++++++++++- src/confd/src/if-wifi.c | 181 ++--- src/confd/src/interfaces.c | 22 +- src/confd/src/interfaces.h | 11 +- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-hardware.yang | 370 +++++++++++ ...30.yang => infix-hardware@2025-12-04.yang} | 0 src/confd/yang/confd/infix-if-bridge.yang | 3 + src/confd/yang/confd/infix-if-type.yang | 2 +- src/confd/yang/confd/infix-if-wifi.yang | 479 +++++++++++--- ...-10.yang => infix-if-wifi@2025-12-17.yang} | 0 src/confd/yang/confd/infix-interfaces.yang | 6 + src/confd/yang/confd/infix-keystore.yang | 2 +- .../yang/confd/infix-wifi-country-codes.yang | 8 +- ... infix-wifi-country-codes@2025-11-28.yang} | 0 src/show/bash_completion.d/show | 2 +- 28 files changed, 2107 insertions(+), 325 deletions(-) create mode 100644 board/common/rootfs/etc/finit.d/available/hostapd@.conf create mode 100644 board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules create mode 100644 board/common/rootfs/etc/udev/rules.d/70-remove-virtual-wifi-interfaces.rules delete mode 100644 board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules create mode 100755 src/confd/share/migrate/1.7/30-remove-wifi-interfaces.sh rename src/confd/yang/confd/{infix-hardware@2025-10-30.yang => infix-hardware@2025-12-04.yang} (100%) rename src/confd/yang/confd/{infix-if-wifi@2025-12-10.yang => infix-if-wifi@2025-12-17.yang} (100%) rename src/confd/yang/confd/{infix-wifi-country-codes@2025-06-02.yang => infix-wifi-country-codes@2025-11-28.yang} (100%) diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg index e23da3d7..b51c0351 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg @@ -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": { diff --git a/board/common/rootfs/etc/finit.d/available/hostapd@.conf b/board/common/rootfs/etc/finit.d/available/hostapd@.conf new file mode 100644 index 00000000..638c5c61 --- /dev/null +++ b/board/common/rootfs/etc/finit.d/available/hostapd@.conf @@ -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 diff --git a/board/common/rootfs/etc/finit.d/available/wifi@.conf b/board/common/rootfs/etc/finit.d/available/wifi@.conf index eb0321df..e530b39c 100644 --- a/board/common/rootfs/etc/finit.d/available/wifi@.conf +++ b/board/common/rootfs/etc/finit.d/available/wifi@.conf @@ -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] /usr/libexec/infix/wifi-scanner %i -- Start scanning for SSID @ %i +task name:wifi-scanner :%i [2345] /usr/libexec/infix/wifi-scanner %i -- Start scanning for SSID @%i diff --git a/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules b/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules new file mode 100644 index 00000000..89c64154 --- /dev/null +++ b/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules @@ -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'" diff --git a/board/common/rootfs/etc/udev/rules.d/70-remove-virtual-wifi-interfaces.rules b/board/common/rootfs/etc/udev/rules.d/70-remove-virtual-wifi-interfaces.rules new file mode 100644 index 00000000..572fa5cc --- /dev/null +++ b/board/common/rootfs/etc/udev/rules.d/70-remove-virtual-wifi-interfaces.rules @@ -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'" diff --git a/board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules b/board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules deleted file mode 100644 index 4251fadf..00000000 --- a/board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules +++ /dev/null @@ -1 +0,0 @@ -SUBSYSTEM=="net", ACTION=="add", TEST=="/sys/class/net/$name/wireless", NAME="wifi%n" diff --git a/doc/wifi.md b/doc/wifi.md index 1ab89625..462be94d 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -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 `. 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. diff --git a/src/confd/bin/bootstrap b/src/confd/bin/bootstrap index e2473f07..371d8028 100755 --- a/src/confd/bin/bootstrap +++ b/src/confd/bin/bootstrap @@ -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 diff --git a/src/confd/bin/gen-hardware b/src/confd/bin/gen-hardware index eec613d3..33000a71 100755 --- a/src/confd/bin/gen-hardware +++ b/src/confd/bin/gen-hardware @@ -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 < "$temp" && mv "$temp" "$file" diff --git a/src/confd/share/migrate/1.7/Makefile.am b/src/confd/share/migrate/1.7/Makefile.am index 488c2765..a9e884b9 100644 --- a/src/confd/share/migrate/1.7/Makefile.am +++ b/src/confd/share/migrate/1.7/Makefile.am @@ -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 diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 9460de1a..df8754b7 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -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; } diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 83e52bfc..84e46ef2 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -10,8 +10,14 @@ #include #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) diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index edd40535..9aebc717 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -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; } diff --git a/src/confd/src/interfaces.c b/src/confd/src/interfaces.c index 0a0cc032..2aaf0538 100644 --- a/src/confd/src/interfaces.c +++ b/src/confd/src/interfaces.c @@ -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: diff --git a/src/confd/src/interfaces.h b/src/confd/src/interfaces.h index aaa58368..71d2ef1b 100644 --- a/src/confd/src/interfaces.h +++ b/src/confd/src/interfaces.h @@ -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); diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 8845f5b8..02566505 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -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" diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang index d702994f..c4bce1ea 100644 --- a/src/confd/yang/confd/infix-hardware.yang +++ b/src/confd/yang/confd/infix-hardware.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."; + } + } + } + } } } diff --git a/src/confd/yang/confd/infix-hardware@2025-10-30.yang b/src/confd/yang/confd/infix-hardware@2025-12-04.yang similarity index 100% rename from src/confd/yang/confd/infix-hardware@2025-10-30.yang rename to src/confd/yang/confd/infix-hardware@2025-12-04.yang diff --git a/src/confd/yang/confd/infix-if-bridge.yang b/src/confd/yang/confd/infix-if-bridge.yang index 72247ba6..c3af782c 100644 --- a/src/confd/yang/confd/infix-if-bridge.yang +++ b/src/confd/yang/confd/infix-if-bridge.yang @@ -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 { diff --git a/src/confd/yang/confd/infix-if-type.yang b/src/confd/yang/confd/infix-if-type.yang index 8e2ea0d6..dd937609 100644 --- a/src/confd/yang/confd/infix-if-type.yang +++ b/src/confd/yang/confd/infix-if-type.yang @@ -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"; } } diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index e3f5e39b..a68d1f60 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -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"; + } + } + } + } } } } diff --git a/src/confd/yang/confd/infix-if-wifi@2025-12-10.yang b/src/confd/yang/confd/infix-if-wifi@2025-12-17.yang similarity index 100% rename from src/confd/yang/confd/infix-if-wifi@2025-12-10.yang rename to src/confd/yang/confd/infix-if-wifi@2025-12-17.yang diff --git a/src/confd/yang/confd/infix-interfaces.yang b/src/confd/yang/confd/infix-interfaces.yang index 9fea0434..405cf832 100644 --- a/src/confd/yang/confd/infix-interfaces.yang +++ b/src/confd/yang/confd/infix-interfaces.yang @@ -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; diff --git a/src/confd/yang/confd/infix-keystore.yang b/src/confd/yang/confd/infix-keystore.yang index 705ccb0a..fec06fed 100644 --- a/src/confd/yang/confd/infix-keystore.yang +++ b/src/confd/yang/confd/infix-keystore.yang @@ -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"; } diff --git a/src/confd/yang/confd/infix-wifi-country-codes.yang b/src/confd/yang/confd/infix-wifi-country-codes.yang index 58bee770..9c797e42 100644 --- a/src/confd/yang/confd/infix-wifi-country-codes.yang +++ b/src/confd/yang/confd/infix-wifi-country-codes.yang @@ -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"; } diff --git a/src/confd/yang/confd/infix-wifi-country-codes@2025-06-02.yang b/src/confd/yang/confd/infix-wifi-country-codes@2025-11-28.yang similarity index 100% rename from src/confd/yang/confd/infix-wifi-country-codes@2025-06-02.yang rename to src/confd/yang/confd/infix-wifi-country-codes@2025-11-28.yang diff --git a/src/show/bash_completion.d/show b/src/show/bash_completion.d/show index 3c6489e6..c7963444 100644 --- a/src/show/bash_completion.d/show +++ b/src/show/bash_completion.d/show @@ -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") )