From a953716a250dacd989da676b6b17889e4910262d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 28 Jul 2025 23:27:17 +0200 Subject: [PATCH 1/8] gen-interfaces: Fix bug when generating on boards with mounted Wi-Fi chipsets These should not appear in a generated factory-config or failure-config. --- src/confd/bin/gen-interfaces | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confd/bin/gen-interfaces b/src/confd/bin/gen-interfaces index cfe3b6c0b..cad922c23 100755 --- a/src/confd/bin/gen-interfaces +++ b/src/confd/bin/gen-interfaces @@ -96,8 +96,8 @@ filter_iface_ports() iface_devs="" for phy in $ifaces; do found="" + [ -d "/sys/class/net/${phy}/wireless" ] && continue for port in $ports; do - [ -d "/sys/class/net/${port}/wireless" ] && continue if [ "$port" = "$phy" ]; then found=true break From 904cb5c5c977924476c8d4cb0e30420cd74c5124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sat, 26 Jul 2025 23:01:54 +0200 Subject: [PATCH 2/8] yanger: remove fallback to start scanning if not running We can not know in here if the scanning should be enabled or not. This is one cause of #1082 --- src/statd/python/yanger/ietf_interfaces/wifi.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index d373dd5cc..3c3d12c2a 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -11,8 +11,6 @@ def wifi(ifname): k,v = line.split("=") if k == "ssid": wifi_data["ssid"] = v - if k == "wpa_state" and v == "DISCONNECTED": # wpa_suppicant has most likely restarted, restart scanning - HOST.run(tuple(f"wpa_cli -i {ifname} scan".split()), default="") data=HOST.run(tuple(f"wpa_cli -i {ifname} signal_poll".split()), default="FAIL") From 54e2efcd7c93f0797f5f5b7c19aef2fc9f1b17d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 28 Jul 2025 09:14:14 +0200 Subject: [PATCH 3/8] udev: Do not rename virtual interfaces created by hostapd Hostapd create virtual interfaces on top of wifi interface these should *not* be renamed. --- board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 4251fadf8..933ec6743 100644 --- a/board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules +++ b/board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules @@ -1 +1,2 @@ -SUBSYSTEM=="net", ACTION=="add", TEST=="/sys/class/net/$name/wireless", NAME="wifi%n" +# Only rename physical interfaces, skip virtual ones created by hostapd +SUBSYSTEM=="net", ACTION=="add", TEST=="/sys/class/net/$name/wireless", TEST=="/sys/class/net/$name/device", KERNEL!="*_*", NAME="wifi%n" From 85219fad5dd310085edc130d8689acb45170cf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sat, 26 Jul 2025 23:23:53 +0200 Subject: [PATCH 4/8] feature-wifi: Enable hostapd for Access point mode --- package/feature-wifi/Config.in | 4 ++++ package/feature-wifi/feature-wifi.mk | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package/feature-wifi/Config.in b/package/feature-wifi/Config.in index 87ed3852a..97f4e09a3 100644 --- a/package/feature-wifi/Config.in +++ b/package/feature-wifi/Config.in @@ -6,6 +6,10 @@ config BR2_PACKAGE_FEATURE_WIFI select BR2_PACKAGE_WPA_SUPPLICANT_AUTOSCAN select BR2_PACKAGE_WPA_SUPPLICANT_CLI select BR2_PACKAGE_WIRELESS_REGDB + select BR2_PACKAGE_HOSTAPD + select BR2_PACKAGE_HOSTAPD_DRIVER_NL80211 + select BR2_PACKAGE_HOSTAPD_WPA3 + select BR2_PACKAGE_HOSTAPD_WPS select BR2_PACKAGE_IW help Enables WiFi in Infix. Enables all requried applications. diff --git a/package/feature-wifi/feature-wifi.mk b/package/feature-wifi/feature-wifi.mk index 90d5bdd3a..08cdd6627 100644 --- a/package/feature-wifi/feature-wifi.mk +++ b/package/feature-wifi/feature-wifi.mk @@ -12,7 +12,6 @@ define FEATURE_WIFI_LINUX_CONFIG_FIXUPS $(call KCONFIG_ENABLE_OPT,CONFIG_RFKILL) $(call KCONFIG_SET_OPT,CONFIG_MAC80211,m) $(call KCONFIG_SET_OPT,CONFIG_CFG80211,m) - $(if $(filter y,$(BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK)), $(call KCONFIG_ENABLE_OPT,CONFIG_WLAN_VENDOR_REALTEK) $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88) From d77cee13a4d98f9498d1dfc5cfec79a4df6d391d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sat, 26 Jul 2025 23:27:27 +0200 Subject: [PATCH 5/8] Wi-Fi: Refactor YANG module to make room for Accesspoint mode --- src/confd/src/ietf-interfaces.h | 2 +- src/confd/src/infix-if-wifi.c | 72 ++++--- src/confd/yang/confd/infix-if-wifi.yang | 203 +++++++++++++----- ...-27.yang => infix-if-wifi@2025-07-27.yang} | 0 src/statd/python/cli_pretty/cli_pretty.py | 28 ++- .../python/yanger/ietf_interfaces/wifi.py | 4 +- 6 files changed, 209 insertions(+), 100 deletions(-) rename src/confd/yang/confd/{infix-if-wifi@2025-05-27.yang => infix-if-wifi@2025-07-27.yang} (100%) diff --git a/src/confd/src/ietf-interfaces.h b/src/confd/src/ietf-interfaces.h index 81892ff26..e2aeaa209 100644 --- a/src/confd/src/ietf-interfaces.h +++ b/src/confd/src/ietf-interfaces.h @@ -122,7 +122,7 @@ int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip); /* infix-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_gen_del(struct lyd_node *iface, struct dagger *net); /* infix-if-gre.c */ int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip); diff --git a/src/confd/src/infix-if-wifi.c b/src/confd/src/infix-if-wifi.c index 2ef7bfd2b..c201c2160 100644 --- a/src/confd/src/infix-if-wifi.c +++ b/src/confd/src/infix-if-wifi.c @@ -6,7 +6,7 @@ #define WPA_SUPPLICANT_FINIT_CONF "/etc/finit.d/available/wpa_supplicant-%s.conf" #define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf" -static int wifi_gen_config(const char *ifname, const char *ssid, const char *country, const char *secret, const char* encryption, struct dagger *net) +static int wifi_gen_client_config(const char *ifname, const char *ssid, const char *country, const char *secret, const char* encryption, struct dagger *net, bool first) { FILE *wpa_supplicant = NULL, *wpa = NULL; char *encryption_str; @@ -32,34 +32,36 @@ static int wifi_gen_config(const char *ifname, const char *ssid, const char *cou fprintf(wpa, "fi\n"); fclose(wpa); - wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF, ifname); + wpa_supplicant = fopenf("a+", WPA_SUPPLICANT_CONF, ifname); if (!wpa_supplicant) { rc = SR_ERR_INTERNAL; goto out; } - if (!secret || !ssid || !country || !encryption) { + if (!ssid || !secret) { fprintf(wpa_supplicant, "ctrl_interface=/run/wpa_supplicant\n" "autoscan=periodic:10\n" "ap_scan=1\n"); } else { - if (!strcmp(encryption, "disabled")) { + if (!encryption) { 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" + if (first) { + fprintf(wpa_supplicant, + "country=%s\n" + "ctrl_interface=/run/wpa_supplicant\n" + "autoscan=periodic:10\n" + "ap_scan=1\n\n", country); + } + fprintf(wpa_supplicant, "network={\n" "bgscan=\"simple: 30:-45:300\"\n" "ssid=\"%s\"\n" "%s\n" - "}\n", country, ssid, encryption_str); - free(encryption_str); + "}\n\n", ssid, encryption_str); + free(encryption_str); } fclose(wpa_supplicant); @@ -69,35 +71,47 @@ static int wifi_gen_config(const char *ifname, const char *ssid, const char *cou } int wifi_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net) { - const char *ssid, *secret_name, *secret, *ifname, *country, *encryption; - struct lyd_node *wifi, *secret_node; - + const char *ssid_name, *secret_name, *secret, *ifname, *country, *encryption, *mode; + struct lyd_node *wifi, *secret_node, *ssid; + bool first = true; bool enabled; ifname = lydx_get_cattr(cif, "name"); + enabled = lydx_get_bool(cif, "enabled"); - if (cif && !lydx_get_child(cif, "wifi")) { - return wifi_gen_config(ifname, NULL, NULL, NULL, NULL, net); - } + if (!enabled) + return wifi_gen_del(cif, net); - enabled = lydx_get_bool(cif, "enabled"); wifi = lydx_get_child(cif, "wifi"); + if (!wifi) + return wifi_gen_del(cif, net); - ssid = lydx_get_cattr(wifi, "ssid"); - secret_name = lydx_get_cattr(wifi, "secret"); + if (wifi && !lydx_get_child(wifi, "ssid")) { /* Only the precense container is set. */ + return wifi_gen_client_config(ifname, NULL, NULL, NULL, NULL, net, true); + } 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-key"); + mode = lydx_get_cattr(wifi, "mode"); - if (!enabled) - return wifi_gen_del(cif, net); - return wifi_gen_config(ifname, ssid, country, secret, encryption, net); + if (!strcmp(mode, "client")) { + erasef(WPA_SUPPLICANT_CONF, ifname); + + LYX_LIST_FOR_EACH(lyd_child(wifi), ssid, "ssid") { + ssid_name = lydx_get_cattr(ssid, "name"); + secret_name = lydx_get_cattr(ssid, "secret"); + encryption = lydx_get_cattr(ssid, "encryption"); + secret_node = lydx_get_xpathf(cif, "../../keystore/symmetric-keys/symmetric-key[name='%s']", secret_name); + secret = lydx_get_cattr(secret_node, "cleartext-key"); + wifi_gen_client_config(ifname, ssid_name, country, secret, encryption, net, first); + first = false; + } + } + + return SR_ERR_OK; } -int wifi_gen_del(struct lyd_node *dif, struct dagger *net) +int wifi_gen_del(struct lyd_node *iface, struct dagger *net) { - const char *ifname = lydx_get_cattr(dif, "name"); + const char *ifname = lydx_get_cattr(iface, "name"); FILE *iw = dagger_fopen_net_exit(net, ifname, NETDAG_EXIT_PRE, "iw.sh"); fprintf(iw, "# Generated by Infix confd\n"); diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 85a3ba34b..2efa8c6e2 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -37,38 +37,78 @@ submodule infix-if-wifi { It supports WiFi client mode and enables comprehensive management of wireless connections, including encryption, country codes, and scanning."; + revision 2025-07-27 { + description "Refactor module and add support for Accesspoint mode."; + reference "internal"; + } revision 2025-05-27 { description "Initial revision."; reference "internal"; } feature wifi { - description "WiFi support is an optional build-time feature in Infix."; + description + "WiFi support is an optional build-time feature in Infix."; } - typedef encryption { + typedef mode { type enumeration { - enum auto { + enum client { description - "Enables WPA/WPA2/WPA3 encryption with automatic protocol - negotiation. The system uses the strongest supported variant supported by Access Point."; + "WiFi client mode - connects to existing access points."; } - enum disabled { + enum accesspoint { description - "Disables encryption for an open network. - - WARNING: Open networks transmit data unencrypted and should only - be used in trusted environments."; + "WiFi access point mode - provides network access to clients."; } } description - "Encryption modes available for WiFi connections. - - - auto: Secure connection using WPA3/WPA2/WPA (auto-selected) - - disabled: Open network (unencrypted)"; + "WiFi operation modes."; + } + typedef band { + type enumeration { + enum "2.4GHz" { + description + "2.4 GHz frequency band."; + } + enum "5GHz" { + description + "5 GHz frequency band."; + } + } + description "WiFi frequency bands."; + } + typedef channel { + type enumeration { + enum "auto" { + description "Automatic channel selection (recommended)."; + } + enum "1" { description "2.412 GHz"; } + enum "2" { description "2.417 GHz"; } + enum "3" { description "2.422 GHz"; } + enum "4" { description "2.427 GHz"; } + enum "5" { description "2.432 GHz"; } + enum "6" { description "2.437 GHz"; } + enum "7" { description "2.442 GHz"; } + enum "8" { description "2.447 GHz"; } + enum "9" { description "2.452 GHz"; } + enum "10" { description "2.457 GHz"; } + enum "11" { description "2.462 GHz"; } + enum "12" { description "2.467 GHz"; } + enum "13" { description "2.472 GHz"; } + enum "14" { description "2.484 GHz (Only valid in Japan)"; } + enum "36" { description "5.18 GHz"; } + enum "40" { description "5.20 GHz"; } + enum "44" { description "5.22 GHz"; } + enum "48" { description "5.24 GHz"; } + enum "149" { description "5.745 GHz"; } + enum "153" { description "5.765 GHz"; } + enum "157" { description "5.785 GHz"; } + enum "161" { description "5.805 GHz"; } + enum "165" { description "5.825 GHz"; } + } + description "WiFi channels with auto-selection option."; } - - augment "/if:interfaces/if:interface" { when "derived-from-or-self(if:type, 'infixift:wifi')" { description @@ -84,65 +124,104 @@ submodule infix-if-wifi { leaf country-code { type iwcc:country-code; - mandatory true; description "Two-letter ISO 3166-1 country code for regulatory compliance. Examples: 'US', 'DE', 'JP'. WARNING: Incorrect values may violate local laws."; - - } - - leaf encryption { - default auto; - type encryption; - + leaf active-ssid { + type string; + config false; + when "../mode = 'client'" { + description + "Applies only when in mode 'client'."; + } + description "Active SSID"; + } + leaf active-rssi { + type int16; + units "dBm"; + config false; + when "../mode = 'client'" { + description + "Applies only when in mode 'client'."; + } description - "WiFi encryption method. - - - auto (default): Enables WPA2/WPA3 auto-negotiation - - disabled: Disables encryption (open network)"; + "Active RSSI for connected SSID."; + } + leaf mode { + type mode; + default 'client'; + description "WiFi operation mode."; } - leaf ssid { - type string { - length "1..32"; + leaf band { + type band; + when "../mode = 'accesspoint'" { + description + "Applies only when in mode 'accesspoint'."; } mandatory true; - description - "WiFi network name (SSID). - - Case-sensitive, must match the target network. - - Length: 1–32 characters."; + "WiFi frequency band for access point mode."; } - leaf secret { - type ks:symmetric-key-ref; - mandatory true; - must "../encryption != 'disabled'" { - error-message - "Pre-shared key required unless encryption is disabled."; + leaf channel { + when "../mode = 'accesspoint'"; + type channel; + default "auto"; + must ". = 'auto' or " + + "(../band = '2.4GHz' and . >= '1' and . <= '14') or " + + "(../band = '5GHz' and . >= '36' and . <= '165')" { + error-message "Channel must be valid for selected band or use 'auto'."; } - description - "Pre-shared key (PSK) for WPA-secured networks."; + "WiFi channel selection for access point mode. + 'auto' (default) + Manual selection available for common channels."; } + list ssid { + must "../country-code" { + error-message "Country code is required when SSIDs are configured."; + } - leaf rssi { - config false; - type int16; - units "dBm"; - description - "Current received signal strength (RSSI) in dBm. + key name; + leaf name { + type string { + length "1..32"; + } + mandatory true; + description + "WiFi network name (SSID). - Lower (more negative) values indicate stronger signals."; - } + Case-sensitive. + Length: 1–32 characters."; + } + + leaf encryption { + type empty; + description + "Enable WPA2/WPA3 encryption with automatic protocol negotiation. + If not present, network operates as open (no encryption)."; + } + + leaf secret { + type ks:symmetric-key-ref; + must "../encryption" { + error-message "Pre-shared key can only be set when encryption is enabled."; + } + description + "Pre-shared key (PSK) for WPA-secured networks."; + } + } list scan-results { + when "../mode = 'client'" { + description + "Applies only when in mode 'client'."; + } config false; key ssid; description @@ -180,6 +259,24 @@ submodule infix-if-wifi { "Human-readable description of the detected security."; } } + list connected-stations { + when "../mode = 'accesspoint'" { + description + "Applies only when in mode 'accesspoint'."; + } + key mac; + leaf mac { + type yang:phys-address; + description + "Mac address of connected station"; + } + leaf rssi { + type int16; + units "dBm"; + description + "Signal for connected station"; + } + } } } } diff --git a/src/confd/yang/confd/infix-if-wifi@2025-05-27.yang b/src/confd/yang/confd/infix-if-wifi@2025-07-27.yang similarity index 100% rename from src/confd/yang/confd/infix-if-wifi@2025-05-27.yang rename to src/confd/yang/confd/infix-if-wifi@2025-07-27.yang diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 232818ce3..820b6ee2f 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -674,22 +674,19 @@ def pr_proto_wifi(self, pipe=''): print(row) ssid = None rssi = None - + status_str="" if self.wifi: - rssi=self.wifi.get("rssi") - ssid=self.wifi.get("ssid") - if ssid is None: - ssid="------" - - if rssi is None: - signal="------" + rssi=self.wifi.get("active-rssi") + ssid=self.wifi.get("active-ssid") else: signal=rssi_to_status(rssi) - data_str = f"ssid: {ssid}, signal: {signal}" + + if ssid is not None: + status_str = f"ssid: {ssid}, signal: {signal}" row = f"{'':<{Pad.iface}}" row += f"{'wifi':<{Pad.proto}}" - row += f"{'':<{Pad.state}}{data_str}" + row += f"{'':<{Pad.state}}{status_str}" print(row) def pr_proto_br(self, br_vlans): @@ -948,11 +945,12 @@ def pr_iface(self): print(f"{'ipv6 addresses':<{20}}:") if self.wifi: - ssid=self.wifi.get('ssid', "----") - rssi=self.wifi.get('rssi', "----") - print(f"{'SSID':<{20}}: {ssid}") - print(f"{'Signal':<{20}}: {rssi}") - print("") + ssid=self.wifi.get('active-ssid') + rssi=self.wifi.get('active-rssi') + if ssid is not None: + print(f"{'SSID':<{20}}: {ssid}") + print(f"{'Signal':<{20}}: {rssi}") + print("") self.pr_wifi_ssids() if self.gre: diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index 3c3d12c2a..76253eb62 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -10,7 +10,7 @@ def wifi(ifname): for line in data.splitlines(): k,v = line.split("=") if k == "ssid": - wifi_data["ssid"] = v + wifi_data["active-ssid"] = v data=HOST.run(tuple(f"wpa_cli -i {ifname} signal_poll".split()), default="FAIL") @@ -19,7 +19,7 @@ def wifi(ifname): for line in data.splitlines(): k,v = line.strip().split("=") if k == "RSSI": - wifi_data["rssi"]=int(v) + wifi_data["active-rssi"]=int(v) data=HOST.run(tuple(f"wpa_cli -i {ifname} scan_result".split()), default="FAIL") if data != "FAIL": From aff82694ae0747171d518ddec63c6a76841afc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 29 Jul 2025 00:07:52 +0200 Subject: [PATCH 6/8] Wi-Fi: Add support for accesspoint Simple configuration: admin@rpi4:/config/> edit interface wifi0 wifi admin@rpi4:/config/interface/wifi0/wifi/> set country-code SE admin@rpi4:/config/interface/wifi0/wifi/> set band 2.4GHz 5GHz admin@rpi4:/config/interface/wifi0/wifi/> set band 2.4GHz admin@rpi4:/config/interface/wifi0/wifi/> edit ssid test admin@rpi4:/config/interface/wifi0/wifi/ssid/test/> set secret se admin@rpi4:/config/interface/wifi0/wifi/ssid/test/> set encryption admin@rpi4:/config/interface/wifi0/wifi/ssid/test/> --- src/confd/src/infix-if-wifi.c | 167 ++++++++++++++++++++---- src/confd/yang/confd/infix-if-wifi.yang | 3 + 2 files changed, 141 insertions(+), 29 deletions(-) diff --git a/src/confd/src/infix-if-wifi.c b/src/confd/src/infix-if-wifi.c index c201c2160..cb0adb459 100644 --- a/src/confd/src/infix-if-wifi.c +++ b/src/confd/src/infix-if-wifi.c @@ -5,8 +5,9 @@ #define WPA_SUPPLICANT_FINIT_CONF "/etc/finit.d/available/wpa_supplicant-%s.conf" #define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf" +#define HOSTAPD_SUPPLICANT_CONF "/etc/hostapd-%s.conf" -static int wifi_gen_client_config(const char *ifname, const char *ssid, const char *country, const char *secret, const char* encryption, struct dagger *net, bool first) +static int wifi_gen_client_config(const char *ifname, const char *ssid, const char *country, const char *secret, const char* encryption, struct dagger *net, int counter) { FILE *wpa_supplicant = NULL, *wpa = NULL; char *encryption_str; @@ -49,7 +50,7 @@ static int wifi_gen_client_config(const char *ifname, const char *ssid, const ch } else { asprintf(&encryption_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret); } - if (first) { + if (!counter) { /* First SSID */ fprintf(wpa_supplicant, "country=%s\n" "ctrl_interface=/run/wpa_supplicant\n" @@ -69,56 +70,164 @@ static int wifi_gen_client_config(const char *ifname, const char *ssid, const ch return rc; } +static int wifi_gen_accesspoint_config(const char *ifname, const char *ssid, const char *country, const char *secret, const char* encryption, const char *band, const char *channel, struct dagger *net, int counter) +{ + FILE *hostapd_conf, *hostapd_finit; + int rc = 0; + + hostapd_finit = dagger_fopen_net_init(net, ifname, NETDAG_INIT_POST, "hostapd.sh"); + if (!hostapd_finit) { + rc = SR_ERR_INTERNAL; + goto out; + } + + fprintf(hostapd_finit, "# Generated by Infix confd\n"); + + fprintf(hostapd_finit, "if [ -f '/etc/finit.d/enabled/hostapd@%s.conf' ];then\n", ifname); + fprintf(hostapd_finit, "initctl -bfqn touch hostapd@%s\n", ifname); + fprintf(hostapd_finit, "else\n"); + fprintf(hostapd_finit, "initctl -bfqn enable hostapd@%s\n", ifname); + fprintf(hostapd_finit, "fi\n"); + fclose(hostapd_finit); + hostapd_conf = fopenf("a+", HOSTAPD_SUPPLICANT_CONF, ifname); + if (!hostapd_conf) { + rc = SR_ERR_INTERNAL; + goto out; + } + + if (!counter) { /* First SSID */ + bool freq_24GHz = !strcmp(band, "2.4GHz"); + fprintf(hostapd_conf, "# Generated by Infix confd\n"); + + if (!strcmp(channel, "auto")) + channel = freq_24GHz ? "6" : "149"; + + fprintf(hostapd_conf, + "interface=%s\n" + "driver=nl80211\n" + "hw_mode=%c\n" + "country_code=%s\n" + "wmm_enabled=1\n" /* QoS */ + "channel=%s\n" + "logger_syslog=-1\n" + "logger_syslog_level=0\n\n" + "logger_stdout=0\n" + "ctrl_interface=/var/run/hostapd\n" + "ctrl_interface_group=0\n", + ifname, freq_24GHz ? 'g' : 'a', country, channel); + if (freq_24GHz) + fprintf(hostapd_conf, "ieee80211n=1\n"); + else + fprintf(hostapd_conf, "ieee80211ac=1\n"); + } + + + fprintf (hostapd_conf, + "\n\n#################################\n" + "# SSID %s\n" + "#################################\n\n", + ssid); + if (counter) + fprintf(hostapd_conf, "bss=%s_%d\n", ifname, counter); + + fprintf(hostapd_conf, "ssid=%s\n", ssid); + if (encryption) { + fprintf(hostapd_conf, "wpa_key_mgmt=WPA-PSK SAE\n"); + fprintf(hostapd_conf, "wpa_passphrase=%s\n", secret); + fprintf(hostapd_conf, "sae_password=%s\n", secret); + fputs("wpa_pairwise=CCMP\n",hostapd_conf); + fputs("rsn_pairwise=CCMP\n", hostapd_conf); + fputs("ieee80211w=1\n",hostapd_conf); /* This to allow WPA2 clients */ + fputs("wpa=2\n",hostapd_conf); + } + fputs("ignore_broadcast_ssid=0\n", hostapd_conf); + fputs("\n", hostapd_conf); + fclose(hostapd_conf); +out: + return rc; + +} +static void disable_wifi(const char *ifname, FILE *fp) +{ + fprintf(fp, "# Generated by Infix confd\n"); + fprintf(fp, "iw dev %s disconnect\n", ifname); + fprintf(fp, "initctl -bfqn disable wifi@%s\n", ifname); + fprintf(fp, "initctl -bfqn disable hostapd@%s\n", ifname); + erasef(WPA_SUPPLICANT_CONF, ifname); + erasef(HOSTAPD_SUPPLICANT_CONF, ifname); +} int wifi_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net) { - const char *ssid_name, *secret_name, *secret, *ifname, *country, *encryption, *mode; + const char *ssid_name, *secret_name, *secret = NULL, *ifname, *country; + const char *encryption, *mode, *band, *channel;; struct lyd_node *wifi, *secret_node, *ssid; - bool first = true; bool enabled; - ifname = lydx_get_cattr(cif, "name"); - enabled = lydx_get_bool(cif, "enabled"); + int counter = 0; + FILE *fp; - if (!enabled) - return wifi_gen_del(cif, net); + ifname = lydx_get_cattr(cif, "name"); + fp = dagger_fopen_net_init(net, ifname, NETDAG_INIT_POST, "disable-wifi.sh"); + if (!fp) { + ERROR("Could not open disable-wifi.sh"); + return SR_ERR_INTERNAL; + } + enabled = lydx_get_bool(cif, "enabled"); wifi = lydx_get_child(cif, "wifi"); - if (!wifi) - return wifi_gen_del(cif, net); + + if (!enabled || !wifi) { + disable_wifi(ifname, fp); + goto out; + } if (wifi && !lydx_get_child(wifi, "ssid")) { /* Only the precense container is set. */ - return wifi_gen_client_config(ifname, NULL, NULL, NULL, NULL, net, true); + wifi_gen_client_config(ifname, NULL, NULL, NULL, NULL, net, 0); + goto out; } + country = lydx_get_cattr(wifi, "country-code"); mode = lydx_get_cattr(wifi, "mode"); + band = lydx_get_cattr(wifi, "band"); /* Only set in AP mode */ + channel = lydx_get_cattr(wifi, "channel"); /* Only set in AP mode */ - - if (!strcmp(mode, "client")) { + if (!strcmp(mode, "client")) erasef(WPA_SUPPLICANT_CONF, ifname); - - LYX_LIST_FOR_EACH(lyd_child(wifi), ssid, "ssid") { - ssid_name = lydx_get_cattr(ssid, "name"); - secret_name = lydx_get_cattr(ssid, "secret"); - encryption = lydx_get_cattr(ssid, "encryption"); + else + erasef(HOSTAPD_SUPPLICANT_CONF, ifname); + LYX_LIST_FOR_EACH(lyd_child(wifi), ssid, "ssid") { + ssid_name = lydx_get_cattr(ssid, "name"); + secret_name = lydx_get_cattr(ssid, "secret"); + encryption = lydx_get_cattr(ssid, "encryption"); + if (encryption) { secret_node = lydx_get_xpathf(cif, "../../keystore/symmetric-keys/symmetric-key[name='%s']", secret_name); secret = lydx_get_cattr(secret_node, "cleartext-key"); - wifi_gen_client_config(ifname, ssid_name, country, secret, encryption, net, first); - first = false; } + if (!strcmp(mode, "client")) { + wifi_gen_client_config(ifname, ssid_name, country, secret, encryption, net, counter); + } else { + wifi_gen_accesspoint_config(ifname, ssid_name, country, secret, encryption, band, channel, net, counter); + } + counter++; + } +out: + fclose(fp); return SR_ERR_OK; } int wifi_gen_del(struct lyd_node *iface, struct dagger *net) { - const char *ifname = lydx_get_cattr(iface, "name"); - FILE *iw = dagger_fopen_net_exit(net, ifname, NETDAG_EXIT_PRE, "iw.sh"); - - fprintf(iw, "# Generated by Infix confd\n"); - fprintf(iw, "iw dev %s disconnect\n", ifname); - fprintf(iw, "initctl -bfqn disable wifi@%s\n", ifname); - fclose(iw); - erasef(WPA_SUPPLICANT_CONF, ifname); - + const char *ifname; + FILE *fp; + + ifname = lydx_get_cattr(iface, "name"); + fp = dagger_fopen_net_exit(net, ifname, NETDAG_EXIT_PRE, "disable-wifi.sh"); + if (!fp) { + ERROR("Failed to open disable-wifi.sh"); + return SR_ERR_INTERNAL; + } + disable_wifi(ifname, fp); + fclose(fp); return SR_ERR_OK; } diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 2efa8c6e2..75356cc7a 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -183,6 +183,9 @@ submodule infix-if-wifi { Manual selection available for common channels."; } list ssid { + max-elements 1; + description "WiFi network configuration (currently limited to 1 SSID)."; + must "../country-code" { error-message "Country code is required when SSIDs are configured."; } From 300c4e6743725bc88ce2d02ef1e6abeb4f36fd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 29 Jul 2025 00:03:52 +0200 Subject: [PATCH 7/8] raspberry pi 4: factory.cfg: Add presence container wifi This to work as intended now when #1082 is fixed. Default setting: Scan on wifi interface --- .../share/product/raspberrypi,4-model-b/etc/factory-config.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/board/raspberry-pi-4/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg b/src/board/raspberry-pi-4/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg index 171c5146b..d7b11f7a2 100644 --- a/src/board/raspberry-pi-4/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg +++ b/src/board/raspberry-pi-4/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg @@ -41,7 +41,8 @@ }, { "name": "wifi0", - "type": "infix-if-type:wifi" + "type": "infix-if-type:wifi", + "infix-interfaces:wifi": {} } ] }, From 730f0fdd5514be9ed0c67c870f505f87538ce237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 29 Jul 2025 00:07:13 +0200 Subject: [PATCH 8/8] Oper/Cli: Add operational data for accesspoint status --- src/statd/python/cli_pretty/cli_pretty.py | 62 ++++++++++++----- .../python/yanger/ietf_interfaces/wifi.py | 66 +++++++++++++------ 2 files changed, 90 insertions(+), 38 deletions(-) diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 820b6ee2f..e9c35ae9a 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -87,6 +87,10 @@ class PadWifiScan: encryption = 30 signal = 9 +class PadWifiStations: + mac = 20 + signal = 9 + class PadLldp: interface = 16 @@ -651,13 +655,30 @@ def pr_proto_loopack(self, pipe=''): row = self._pr_proto_common("loopback", False, pipe); print(row) + def pr_wifi_stations(self): + hdr = "\nCONNECTED STATIONS" + print(Decore.invert(hdr)) + hdr = (f"{'MAC':<{PadWifiStations.mac}}" + f"{'SIGNAL':<{PadWifiStations.signal}}" + ) + print(Decore.invert(hdr)) + + stations=self.wifi.get("connected-stations", {}) + for station in stations: + status=rssi_to_status(station["rssi"]) + row = f"{station['mac']:<{PadWifiStations.mac}}" + row += f"{status:<{PadWifiStations.signal}}" + print(row) + def pr_wifi_ssids(self): + hdr = "\nSCAN RESULTS" + print(Decore.invert(hdr)) hdr = (f"{'SSID':<{PadWifiScan.ssid}}" f"{'ENCRYPTION':<{PadWifiScan.encryption}}" f"{'SIGNAL':<{PadWifiScan.signal}}" ) - print(Decore.invert(hdr)) + results=self.wifi.get("scan-results", {}) for result in results: encstr = ",".join(result["encryption"]) @@ -676,14 +697,16 @@ def pr_proto_wifi(self, pipe=''): rssi = None status_str="" if self.wifi: - rssi=self.wifi.get("active-rssi") - ssid=self.wifi.get("active-ssid") - else: - signal=rssi_to_status(rssi) - - if ssid is not None: - status_str = f"ssid: {ssid}, signal: {signal}" - + if self.wifi.get("mode", "") == "client": + ssid=self.wifi.get("active-ssid") + if ssid is not None: + rssi=self.wifi.get("active-rssi") + signal=rssi_to_status(rssi) + + status_str = f"ssid: {ssid}, signal: {signal}" + elif self.wifi.get("mode", "") == "accesspoint": + stations=self.wifi.get("connected-stations", {}) + status_str = f"Connected stations: {len(stations)}" row = f"{'':<{Pad.iface}}" row += f"{'wifi':<{Pad.proto}}" row += f"{'':<{Pad.state}}{status_str}" @@ -944,15 +967,6 @@ def pr_iface(self): else: print(f"{'ipv6 addresses':<{20}}:") - if self.wifi: - ssid=self.wifi.get('active-ssid') - rssi=self.wifi.get('active-rssi') - if ssid is not None: - print(f"{'SSID':<{20}}: {ssid}") - print(f"{'Signal':<{20}}: {rssi}") - print("") - self.pr_wifi_ssids() - if self.gre: print(f"{'local address':<{20}}: {self.gre['local']}") print(f"{'remote address':<{20}}: {self.gre['remote']}") @@ -973,6 +987,18 @@ def pr_iface(self): for key, val in frame.items(): key = remove_yang_prefix(key) print(f"eth-{key:<{25}}: {val}") + if self.wifi: + ssid=self.wifi.get('active-ssid', "") + rssi=self.wifi.get('active-rssi', "") + mode=self.wifi.get('mode') + if mode == "client": + print(f"{'SSID':<{20}}: {ssid}") + print(f"{'Signal':<{20}}: {rssi}") + print("") + self.pr_wifi_ssids() + if mode == "accesspoint": + self.pr_wifi_stations() + def pr_mdb(self, bridge): for group in self.br_mdb.get("multicast-filter", {}): diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index 76253eb62..e5104e871 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -3,28 +3,54 @@ import re def wifi(ifname): - data=HOST.run(tuple(f"wpa_cli -i {ifname} status".split()), default="") + iw_data=HOST.run(tuple(f"iw dev {ifname} info".split()), default="") wifi_data={} - if data != "": - for line in data.splitlines(): - k,v = line.split("=") - if k == "ssid": - wifi_data["active-ssid"] = v - - data=HOST.run(tuple(f"wpa_cli -i {ifname} signal_poll".split()), default="FAIL") - - # signal_poll return FAIL not connected - if data.strip() != "FAIL": - for line in data.splitlines(): - k,v = line.strip().split("=") - if k == "RSSI": - wifi_data["active-rssi"]=int(v) - data=HOST.run(tuple(f"wpa_cli -i {ifname} scan_result".split()), default="FAIL") - - if data != "FAIL": - wifi_data["scan-results"] = parse_wpa_scan_result(data) - + if iw_data != "": + for line in iw_data.splitlines(): + line=line.strip() # Fix crazy output from iw. + l=line.split(" ") + if l[0] == "type": + if l[1] == "AP": + wifi_data["mode"] = "accesspoint" + else: + wifi_data["mode"] = "client" + + if wifi_data["mode"] == "client": + client_data=HOST.run(tuple(f"wpa_cli -i {ifname} status".split()), default="") + if client_data != "": + for line in client_data.splitlines(): + k,v = line.split("=") + if k == "ssid": + wifi_data["active-ssid"] = v + + data=HOST.run(tuple(f"wpa_cli -i {ifname} signal_poll".split()), default="FAIL") + + # signal_poll return FAIL not connected + if data.strip() != "FAIL": + for line in data.splitlines(): + k,v = line.strip().split("=") + if k == "RSSI": + wifi_data["active-rssi"]=int(v) + data=HOST.run(tuple(f"wpa_cli -i {ifname} scan_result".split()), default="FAIL") + if data != "FAIL": + wifi_data["scan-results"] = parse_wpa_scan_result(data) + elif wifi_data["mode"] == "accesspoint": + ap_data=HOST.run(tuple(f"hostapd_cli -i {ifname} list_sta".split()), default="") + if ap_data != "": + stations=[] + for mac in ap_data.splitlines(): + station = {} + status=HOST.run(tuple(f"hostapd_cli -i {ifname} sta {mac}".split()), default="") + if status != "": + for line in status.splitlines()[1:]: + k,v = line.split("=") + if k == "signal": + station["rssi"] = int(v) + station["mac"] = mac + stations.append(station) + + wifi_data["connected-stations"] = stations return wifi_data