From 8538888069e4c71445d4c24f9704242c339c2208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Thu, 19 Jun 2025 10:32:01 +0200 Subject: [PATCH 01/15] confd: core: Allow all other flags then SR_SUBSCR_UPDATE Other flags may be load-bearing in some modules, but SR_SUBSCR_UPDATE, we never want to keep track on in the core hooks. --- src/confd/src/core.h | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/confd/src/core.h b/src/confd/src/core.h index bce84f7e..ffe08309 100644 --- a/src/confd/src/core.h +++ b/src/confd/src/core.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -137,9 +138,20 @@ static inline int register_change(sr_session_ctx_t *session, const char *module, int flags, sr_module_change_cb cb, void *arg, sr_subscription_ctx_t **sub) { struct confd *ptr = (struct confd *)arg; + bool need_core_hooks; int rc; - if (!flags) { + /* + * For standard subscribtions we hook into the callback chain + * for all modules to figure out, per changeset, which of the + * callbacks is the last one. This is where we want to call the + * global commit-done hook for candidate -> running changes and + * the startup-save hook for running -> startup copying. + */ + + need_core_hooks = !(flags & SR_SUBSCR_UPDATE); + + if (need_core_hooks) { sr_module_change_subscribe(ptr->session, module, xpath, core_pre_hook, NULL, 0, SR_SUBSCR_PASSIVE, sub); } @@ -151,14 +163,7 @@ static inline int register_change(sr_session_ctx_t *session, const char *module, return rc; } - /* - * For standard subscribtions we hook into the callback chain - * for all modules to figure out, per changeset, which of the - * callbacks is the last one. This is where we want to call the - * global commit-done hook for candidate -> running changes and - * the startup-save hook for running -> startup copying. - */ - if (!flags) { + if (need_core_hooks) { sr_module_change_subscribe(ptr->session, module, xpath, core_post_hook, NULL, core_hook_prio(), SR_SUBSCR_PASSIVE, sub); sr_module_change_subscribe(ptr->startup, module, xpath, core_startup_save, NULL, From 67a6fc18d574835fd2bc99596cc61beb4a4d9cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Thu, 19 Jun 2025 09:02:26 +0200 Subject: [PATCH 02/15] confd: Always run netdag_gen_afspec_set and netdag_gen_afspec_add on all iftypes If an iftype does not need to do anything, just return 0. This makes the code much more easy to follow. --- src/confd/src/ieee802-ethernet-interface.c | 17 ----------------- src/confd/src/ietf-interfaces.c | 20 +++++++++----------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/confd/src/ieee802-ethernet-interface.c b/src/confd/src/ieee802-ethernet-interface.c index 74072795..0dae3196 100644 --- a/src/confd/src/ieee802-ethernet-interface.c +++ b/src/confd/src/ieee802-ethernet-interface.c @@ -114,23 +114,6 @@ int netdag_gen_ethtool(struct dagger *net, struct lyd_node *cif, struct lyd_node struct lyd_node *eth = lydx_get_child(dif, "ethernet"); int err; - /* - * Story time: when assigning a physical interface to a container, and then - * removing it, even though our type may be 'etherlike' we will - * get the following from sysrepo: - * - * "ieee802-ethernet-interface:ethernet": { - * "@": { - * "yang:operation": "delete" - * }, - * "duplex": "full" - * }, - * - * Hence this "redundant" check. - */ - if (iftype_from_iface(cif) != IFT_ETH) - return 0; - if (!eth) return 0; diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index e39c0107..c2f15948 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -416,9 +416,11 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, return vlan_gen(NULL, cif, ip); case IFT_VXLAN: return vxlan_gen(NULL, cif, ip); - case IFT_ETH: + return netdag_gen_ethtool(net, cif, dif); case IFT_LO: + return 0; + case IFT_UNKNOWN: sr_session_set_error_message(net->session, "%s: unsupported interface type \"%s\"", ifname, lydx_get_cattr(cif, "type")); @@ -440,16 +442,17 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net, return lag_gen(dif, cif, ip, 0); case IFT_VLAN: return vlan_gen(dif, cif, ip); + case IFT_ETH: + return netdag_gen_ethtool(net, cif, dif); case IFT_DUMMY: case IFT_GRE: case IFT_GRETAP: case IFT_VETH: case IFT_VXLAN: + case IFT_LO: return 0; - case IFT_ETH: - case IFT_LO: case IFT_UNKNOWN: return ERR_IFACE(cif, -ENOSYS, "unsupported interface type \"%s\"", lydx_get_cattr(cif, "type")); @@ -580,7 +583,6 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net enum lydx_op op = lydx_get_op(dif); const char *attr; int err = 0; - bool fixed; FILE *ip; if ((err = cni_netdag_gen_iface(net, ifname, dif, cif))) { @@ -590,9 +592,7 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net goto err; } - fixed = iface_is_phys(ifname) || !strcmp(ifname, "lo"); - - DEBUG("%s(%s) %s", ifname, fixed ? "fixed" : "dynamic", + DEBUG("%s %s", ifname, (op == LYDX_OP_NONE) ? "mod" : ((op == LYDX_OP_CREATE) ? "add" : "del")); if (op == LYDX_OP_DELETE) { @@ -630,7 +630,7 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net goto err; } - if (!fixed && op == LYDX_OP_CREATE) { + if (op == LYDX_OP_CREATE) { err = netdag_gen_afspec_add(session, net, dif, cif, ip); if (err) goto err_close_ip; @@ -655,7 +655,7 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net goto err_close_ip; /* Set type specific attributes */ - if (!fixed && op != LYDX_OP_CREATE) { + if (op != LYDX_OP_CREATE) { err = netdag_gen_afspec_set(session, net, dif, cif, ip); if (err) goto err_close_ip; @@ -679,8 +679,6 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net err = err ? : netdag_gen_sysctl(net, cif, dif); - err = err ? : netdag_gen_ethtool(net, cif, dif); - err_close_ip: fclose(ip); err: From 7f9a1df6f7f27bbd2a59fdc22f4cca100cdfe95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 18 Jun 2025 09:43:06 +0200 Subject: [PATCH 03/15] qemu: Increase memory for virtual machine Image is too big, more memory is required --- board/common/qemu/Config.in.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/common/qemu/Config.in.in b/board/common/qemu/Config.in.in index 4312abdd..b0f5d22a 100644 --- a/board/common/qemu/Config.in.in +++ b/board/common/qemu/Config.in.in @@ -78,7 +78,7 @@ config QEMU_MACHINE config QEMU_MACHINE_RAM string "RAM size (k/M/G)" - default "384M" + default "448M" help The default, 384 MiB, works for most configurations. However, if you get kernel panic with: "System is deadlocked on memory", From 915bef2dd48aaa9432b88a9682456147eeac7f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sat, 7 Jun 2025 09:49:52 +0200 Subject: [PATCH 04/15] udev: Rename all wlan interfaces wifiN and start scanning This since the feature is called 'wifi' in Infix, this makes the naming consistent over the system. Also trigger start of a wpa_supplicant, for scanning in the background. The scanning results is available operational. --- .../etc/udev/rules.d/70-rename-wifi.rules | 1 + src/confd/bin/wifi-detected.sh | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules create mode 100755 src/confd/bin/wifi-detected.sh 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 new file mode 100644 index 00000000..4251fadf --- /dev/null +++ b/board/common/rootfs/etc/udev/rules.d/70-rename-wifi.rules @@ -0,0 +1 @@ +SUBSYSTEM=="net", ACTION=="add", TEST=="/sys/class/net/$name/wireless", NAME="wifi%n" diff --git a/src/confd/bin/wifi-detected.sh b/src/confd/bin/wifi-detected.sh new file mode 100755 index 00000000..079d2ba7 --- /dev/null +++ b/src/confd/bin/wifi-detected.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +if [ ! -e "/etc/wpa-supplicant-${INTERFACE}.conf" ]; then + cat < /etc/wpa-supplicant-${INTERFACE}.conf +ctrl_interface=/run/wpa_supplicant +update_config=0 +autoscan=periodic:10 +disable_scan_offload=1 +ap_scan=1 +# Empty network block to enable scanning without connecting +bgscan="simple: 30:-45:300" + +EOF + cat < /etc/finit.d/available/wpa-supplicant-${INTERFACE}.conf +# Generated by Infix wifi interface detected +service name:wpa_supplicant :${INTERFACE} <> \ + [2345] wpa_supplicant -s -i ${INTERFACE} -c /etc/wpa-supplicant-${INTERFACE}.conf \ + -- WPA supplicant @${INTERFACE} (scanning only) + +EOF + initctl -bfqn enable wpa-supplicant-${INTERFACE} + initctl reload + TIMEOUT=300 + status=$(wpa_cli -i ${INTERFACE} scan) + while [ "$status" != "OK" ]; do + status=$(wpa_cli -i ${INTERFACE} scan) + TIMEOUT=$((TIMEOUT-1)) + [ $TIMEOUT -eq 0 ] && logger -t wifi-detect "Failed to start scanning on $INTERFACE" && break + sleep 0.5 + done +fi From a7e72a46d972dd982f6dcd4d95aaacb4e5f6ebfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 6 Jun 2025 20:44:14 +0200 Subject: [PATCH 05/15] confd: Simplify enabling features in YANG models If a extra infix feature require features to be added to an already loaded module. Instead of have the exact module definition in multiple places (with date), this will allow a much more flexible sytax: MODULES=( "infix-interfaces -e containers" ) This require that the infix-interfaces is loaded *before* this file is run. --- src/confd/yang/confd.inc | 1 - src/confd/yang/containers.inc | 3 +-- utils/srload | 19 +++++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index f6074cfa..257e51e2 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -1,5 +1,4 @@ # -*- sh -*- -# REMEMBER TO UPDATE infix-interfaces ALSO IN containers.inc MODULES=( "ietf-system@2014-08-06.yang -e authentication -e local-users -e ntp -e ntp-udp-port -e timezone-name" diff --git a/src/confd/yang/containers.inc b/src/confd/yang/containers.inc index b7e66044..50447cf8 100644 --- a/src/confd/yang/containers.inc +++ b/src/confd/yang/containers.inc @@ -1,6 +1,5 @@ # -*- sh -*- -# REMEMBER TO UPDATE infix-interfaces ALSO IN confd.inc MODULES=( - "infix-interfaces@2025-01-09.yang -e vlan-filtering -e containers" + "infix-interfaces -e containers" "infix-containers@2025-05-14.yang" ) diff --git a/utils/srload b/utils/srload index 5996bf74..65d9a14a 100755 --- a/utils/srload +++ b/utils/srload @@ -82,7 +82,10 @@ chperm() enable() { - $SYSREPOCTL -c "$1" -e "$2" -v2 + local module=$1 + local feature=$2 + echo "*** Enable feature $feature in $module." + $SYSREPOCTL -c $module -e $feature -v2 local rc=$? if [ $rc -ne 0 ]; then exit $rc @@ -93,7 +96,7 @@ enable() SCTL_MODULES=$($SYSREPOCTL -l |tail -n +5 |head -n -3) for module in "${MODULES[@]}"; do - name=$(echo "$module" | awk -F'[@.]' '{print $1}') + name=$(echo "$module" | awk '{match($1, /[^@.]+/); print substr($1, RSTART, RLENGTH)}') date=$(echo "$module" | awk -F'[@.]' '{print $2}') for dir in $SEARCH_PATH; do if [ -n "${date}" ]; then @@ -105,10 +108,15 @@ for module in "${MODULES[@]}"; do MODDIR="${dir}" fi done - [ -z "${MODDIR}" ] && echo "Could not find ${yang} in ${SEARCH_PATH}" && exit 1 + # If no .yang we just modfify an existing model + if [[ "$module" == *.yang* && -z "$MODDIR" ]]; then + echo "Could not find ${yang} in ${SEARCH_PATH}" + exit 1 + fi SCTL_MODULE=$(echo "$SCTL_MODULES" | grep "^$name \+|[^|]*| I") + if [ -z "$SCTL_MODULE" ]; then # prepare command to install module with all its features echo "*** Installing YANG model $name ..." @@ -117,7 +125,7 @@ for module in "${MODULES[@]}"; do fi rev=$(echo "$SCTL_MODULE" | awk '{print $3}') - if [ "$rev" != "$date" ] || echo "$FORCE_UPDATE" | grep -qw "$name"; then + if [ -n "$date" ] && [ "$rev" != "$date" ] || echo "$FORCE_UPDATE" | grep -qw "$name"; then # update module without any features file=$(echo "$module" | cut -d' ' -f 1) echo "*** Updating YANG model $name ($file) ..." @@ -133,8 +141,7 @@ for module in "${MODULES[@]}"; do chperm "$name" fi - # parse sysrepoctl features and add extra space at the end for easier matching - sctl_features="`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{6\}\(.*\)/\2/'` " + sctl_features="`echo "$SCTL_MODULE" | awk -F '|' '{print $NF}' | xargs`" # parse features we want to enable features=`echo "$module" | sed 's/[^ ]* \(.*\)/\1/'` while [ "${features:0:3}" = "-e " ]; do From ecabce86c7cded2b50fe7fe4d817dce13dd879a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 3 Jun 2025 19:51:02 +0200 Subject: [PATCH 06/15] Add meta-package for adding Wi-Fi support Helps adding everything that needs to be compiled to get Wi-Fi support working. Both modules, firmware and applications are selected here --- package/Config.in | 2 +- package/feature-wifi/Config.in | 18 ++++++++++++ package/feature-wifi/feature-wifi.mk | 41 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 package/feature-wifi/Config.in create mode 100644 package/feature-wifi/feature-wifi.mk diff --git a/package/Config.in b/package/Config.in index c76c670b..9271f68d 100644 --- a/package/Config.in +++ b/package/Config.in @@ -2,6 +2,7 @@ menu "Packages" comment "Hardware Support" source "$BR2_EXTERNAL_INFIX_PATH/package/board/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/package/feature-wifi/Config.in" comment "Software Packages" source "$BR2_EXTERNAL_INFIX_PATH/package/bin/Config.in" @@ -42,5 +43,4 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/rauc-installation-status/Config.in" - endmenu diff --git a/package/feature-wifi/Config.in b/package/feature-wifi/Config.in new file mode 100644 index 00000000..f12390bc --- /dev/null +++ b/package/feature-wifi/Config.in @@ -0,0 +1,18 @@ +config BR2_PACKAGE_FEATURE_WIFI + bool "Feature Wi-Fi" + select BR2_PACKAGE_WPA_SUPPLICANT + select BR2_PACKAGE_WPA_SUPPLICANT_WPA3 + select BR2_PACKAGE_WPA_SUPPLICANT_DEBUG_SYSLOG + select BR2_PACKAGE_WPA_SUPPLICANT_AUTOSCAN + select BR2_PACKAGE_WPA_SUPPLICANT_CLI + help + Enables WiFi in Infix. Enables all requried applications. + +config BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK + bool "Realtek USB WiFi Dongles" + depends on BR2_PACKAGE_FEATURE_WIFI + select BR2_PACKAGE_LINUX_FIRMWARE + select BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88 + select BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW89 + help + Enables Support for RTW88 and RTW89 USB dongles. diff --git a/package/feature-wifi/feature-wifi.mk b/package/feature-wifi/feature-wifi.mk new file mode 100644 index 00000000..aa428bc7 --- /dev/null +++ b/package/feature-wifi/feature-wifi.mk @@ -0,0 +1,41 @@ +################################################################################ +# +# Wi-Fi support +# +################################################################################ + +FEATURE_WIFI_PACKAGE_VERSION = 1.0 +FEATURE_WIFI_PACKAGE_LICENSE = MIT + +define FEATURE_WIFI_LINUX_CONFIG_FIXUPS + $(call KCONFIG_ENABLE_OPT,CONFIG_WLAN) + $(call KCONFIG_ENABLE_OPT,CONFIG_RFKILL) + $(call KCONFIG_ENABLE_OPT,CONFIG_MAC80211) + $(call KCONFIG_ENABLE_OPT,CONFIG_CFG80211) + $(if $(filter y,$(BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK)), + $(call KCONFIG_ENABLE_OPT,CONFIG_WLAN_VENDOR_REALTEK) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW89) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8703B) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8723CS) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8723D) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8723DE) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8723DS) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8723DU) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8723X) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8821C) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8821CE) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8821CS) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8821CU) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822B) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822BE) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822BS) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822BU) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822C) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822CU) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTW88_8822CE) + ) +endef + + +$(eval $(generic-package)) From 0ad8fab5c7ed98016dca99e38f984ab58aedf5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 18 Jun 2025 21:24:07 +0200 Subject: [PATCH 07/15] wpa_supplicant: Add support for pidfile when not daemonize --- ...d-pidfile-support-when-not-daemonize.patch | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 patches/wpa_supplicant/0001-Add-pidfile-support-when-not-daemonize.patch diff --git a/patches/wpa_supplicant/0001-Add-pidfile-support-when-not-daemonize.patch b/patches/wpa_supplicant/0001-Add-pidfile-support-when-not-daemonize.patch new file mode 100644 index 00000000..2a12fddc --- /dev/null +++ b/patches/wpa_supplicant/0001-Add-pidfile-support-when-not-daemonize.patch @@ -0,0 +1,232 @@ +From 9df1957e656940a50b844da879911ab5981ad1a9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Wed, 18 Jun 2025 21:21:18 +0200 +Subject: [PATCH] Add pidfile support when not daemonize +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Adds pidfile.c from libite + +Signed-off-by: Mattias Walström +--- + wpa_supplicant/Makefile | 1 + + wpa_supplicant/pidfile.c | 153 ++++++++++++++++++++++++++++++++ + wpa_supplicant/wpa_supplicant.c | 7 +- + 3 files changed, 160 insertions(+), 1 deletion(-) + create mode 100644 wpa_supplicant/pidfile.c + +diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile +index fd7d7a0bd..e14c7732a 100644 +--- a/wpa_supplicant/Makefile ++++ b/wpa_supplicant/Makefile +@@ -104,6 +104,7 @@ endif + OBJS = config.o + OBJS += notify.o + OBJS += bss.o ++OBJS += pidfile.o + OBJS += eap_register.o + OBJS += ../src/utils/common.o + OBJS += ../src/utils/config.o +diff --git a/wpa_supplicant/pidfile.c b/wpa_supplicant/pidfile.c +new file mode 100644 +index 000000000..29dfbe242 +--- /dev/null ++++ b/wpa_supplicant/pidfile.c +@@ -0,0 +1,153 @@ ++/* Updated by troglobit for libite/finit/uftpd projects 2016/07/04 */ ++/* $OpenBSD: pidfile.c,v 1.11 2015/06/03 02:24:36 millert Exp $ */ ++/* $NetBSD: pidfile.c,v 1.4 2001/02/19 22:43:42 cgd Exp $ */ ++ ++/*- ++ * Copyright (c) 1999 The NetBSD Foundation, Inc. ++ * All rights reserved. ++ * ++ * This code is derived from software contributed to The NetBSD Foundation ++ * by Jason R. Thorpe. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/** ++ * @file pidfile.c ++ * @author NetBSD Foundation Inc. ++ * @date 1999 ++ * @copyright 2-clause BSD License ++ */ ++ ++#include /* utimensat() */ ++#include /* utimensat() on *BSD */ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifndef pidfile ++static char *pidfile_path = NULL; ++static pid_t pidfile_pid = 0; ++ ++static void pidfile_cleanup(void); ++ ++const char *__pidfile_path = _PATH_VARRUN; /* Note: includes trailing slash '/' */ ++const char *__pidfile_name = NULL; ++extern char *__progname; ++ ++/** ++ * Create or update mtime of process PID file. ++ * @param basename Program name, or @c NULL, may start with '/' ++ * ++ * This function is intended to be used by UNIX daemons to save the PID of the main process ++ * responsible for handling signals. If @p basename is @c NULL the implicit @a __progname ++ * variable from the C-library is used. The @p basename may also start with '/', in which ++ * case it is interpreted as the absolute path to the PID file. ++ * ++ * @returns POSIX OK(0) on success, otherwise non-zero on error. ++ */ ++int pidfile(const char *basename) ++{ ++ int save_errno; ++ int atexit_already; ++ pid_t pid; ++ FILE *f; ++ ++ if (basename == NULL) ++ basename = __progname; ++ ++ pid = getpid(); ++ atexit_already = 0; ++ ++ if (pidfile_path != NULL) { ++ if (!access(pidfile_path, R_OK) && pid == pidfile_pid) { ++ utimensat(0, pidfile_path, NULL, 0); ++ return (0); ++ } ++ free(pidfile_path); ++ pidfile_path = NULL; ++ __pidfile_name = NULL; ++ atexit_already = 1; ++ } ++ ++ if (basename[0] != '/') { ++ if (asprintf(&pidfile_path, "%s.pid", basename) == -1) ++ return (-1); ++ } else { ++ if (asprintf(&pidfile_path, "%s", basename) == -1) ++ return (-1); ++ } ++ ++ if ((f = fopen(pidfile_path, "w")) == NULL) { ++ save_errno = errno; ++ free(pidfile_path); ++ pidfile_path = NULL; ++ errno = save_errno; ++ return (-1); ++ } ++ ++ if (fprintf(f, "%ld\n", (long)pid) <= 0 || fflush(f) != 0) { ++ save_errno = errno; ++ (void) fclose(f); ++ (void) unlink(pidfile_path); ++ free(pidfile_path); ++ pidfile_path = NULL; ++ errno = save_errno; ++ return (-1); ++ } ++ (void) fclose(f); ++ __pidfile_name = pidfile_path; ++ ++ /* ++ * LITE extension, no need to set up another atexit() handler ++ * if user only called us to update the mtime of the PID file ++ */ ++ if (atexit_already) ++ return (0); ++ ++ pidfile_pid = pid; ++ if (atexit(pidfile_cleanup) < 0) { ++ save_errno = errno; ++ (void) unlink(pidfile_path); ++ free(pidfile_path); ++ pidfile_path = NULL; ++ pidfile_pid = 0; ++ errno = save_errno; ++ return (-1); ++ } ++ ++ return (0); ++} ++ ++static void ++pidfile_cleanup(void) ++{ ++ if (pidfile_path != NULL && pidfile_pid == getpid()) { ++ (void) unlink(pidfile_path); ++ free(pidfile_path); ++ pidfile_path = NULL; ++ } ++} ++#endif +diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c +index dfbd63163..3a762ebc2 100644 +--- a/wpa_supplicant/wpa_supplicant.c ++++ b/wpa_supplicant/wpa_supplicant.c +@@ -123,6 +123,7 @@ const char *const wpa_supplicant_full_license5 = + "\n"; + #endif /* CONFIG_NO_STDOUT_DEBUG */ + ++int pidfile(const char *basename); + + static void wpa_bss_tmp_disallow_timeout(void *eloop_ctx, void *timeout_ctx); + static void wpas_verify_ssid_beacon(void *eloop_ctx, void *timeout_ctx); +@@ -1490,6 +1491,9 @@ static void wpa_supplicant_reconfig(int sig, void *signal_ctx) + /* Ignore errors since we cannot really do much to fix this */ + wpa_printf(MSG_DEBUG, "Could not reopen debug log file"); + } ++ ++ if (!global->params.daemonize) ++ pidfile(global->params.pid_file); + } + + +@@ -8555,7 +8559,6 @@ struct wpa_global * wpa_supplicant_init(struct wpa_params *params) + return global; + } + +- + /** + * wpa_supplicant_run - Run the %wpa_supplicant main event loop + * @global: Pointer to global data from wpa_supplicant_init() +@@ -8589,6 +8592,8 @@ int wpa_supplicant_run(struct wpa_global *global) + eloop_register_signal_terminate(wpa_supplicant_terminate, global); + eloop_register_signal_reconfig(wpa_supplicant_reconfig, global); + ++ if (!global->params.daemonize) ++ pidfile(global->params.pid_file); + eloop_run(); + + return 0; +-- +2.43.0 + From 57d5bf15773ae0c23f99395aa35ed9700436fbe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 2 Jun 2025 13:37:20 +0200 Subject: [PATCH 08/15] confd: Add script to wait for an interface usage: wait-interface wlan0 30 --- src/confd/bin/Makefile.am | 2 +- src/confd/bin/wait-interface | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/confd/bin/wait-interface diff --git a/src/confd/bin/Makefile.am b/src/confd/bin/Makefile.am index 1748757e..925e5130 100644 --- a/src/confd/bin/Makefile.am +++ b/src/confd/bin/Makefile.am @@ -1,4 +1,4 @@ pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostname \ gen-interfaces gen-motd gen-hardware gen-version \ - mstpd-wait-online + mstpd-wait-online wait-interface sbin_SCRIPTS = dagger migrate diff --git a/src/confd/bin/wait-interface b/src/confd/bin/wait-interface new file mode 100644 index 00000000..8900fdbc --- /dev/null +++ b/src/confd/bin/wait-interface @@ -0,0 +1,21 @@ +#!/bin/sh +if [ $# -lt 2 ]; then + echo "usage: $0 " + exit 1 +fi + +ifname=$1 +timeout=$2 +while true; do + if ip link show $ifname &>/dev/null; then + break + fi + + retries=$(($retries + 1)) + if [ $retries -ge $timeout ]; then + logger -t wait-interface "Timeout: Interface $ifname not found after $timeout seconds" + exit 1 + fi + + sleep 1 +done From b53c1612f0fa8228dbfe4f6170cd182303d55008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 3 Jun 2025 19:55:36 +0200 Subject: [PATCH 09/15] confd: Add basic Wi-Fi client support Support implemented: * WPA2/3 support * scanning (in background, results in operational) * Unencrypted networks No certificate support, only PSK *only* client so far, no AP --- package/confd/confd.mk | 12 + src/confd/bin/wifi-detected.sh | 31 -- src/confd/configure.ac | 8 + src/confd/src/Makefile.am | 1 + src/confd/src/dagger.h | 3 + src/confd/src/ietf-interfaces.c | 112 ++++++- src/confd/src/ietf-interfaces.h | 5 + src/confd/src/infix-if-wifi.c | 111 +++++++ src/confd/yang/confd.inc | 8 +- src/confd/yang/confd/infix-crypto-types.yang | 14 + ...ang => infix-crypto-types@2025-06-17.yang} | 0 src/confd/yang/confd/infix-if-type.yang | 14 + src/confd/yang/confd/infix-if-wifi.yang | 185 +++++++++++ .../yang/confd/infix-if-wifi@2025-05-27.yang | 1 + src/confd/yang/confd/infix-interfaces.yang | 17 +- ....yang => infix-interfaces@2025-06-17.yang} | 0 src/confd/yang/confd/infix-keystore.yang | 37 +++ ...04.yang => infix-keystore@2025-06-17.yang} | 0 src/confd/yang/confd/infix-meta.yang | 11 + .../yang/confd/infix-wifi-country-codes.yang | 287 ++++++++++++++++++ .../infix-wifi-country-codes@2025-06-02.yang | 1 + src/confd/yang/wifi.inc | 4 + 22 files changed, 814 insertions(+), 48 deletions(-) delete mode 100755 src/confd/bin/wifi-detected.sh create mode 100644 src/confd/src/infix-if-wifi.c rename src/confd/yang/confd/{infix-crypto-types@2025-02-04.yang => infix-crypto-types@2025-06-17.yang} (100%) create mode 100644 src/confd/yang/confd/infix-if-wifi.yang create mode 120000 src/confd/yang/confd/infix-if-wifi@2025-05-27.yang rename src/confd/yang/confd/{infix-interfaces@2025-01-09.yang => infix-interfaces@2025-06-17.yang} (100%) rename src/confd/yang/confd/{infix-keystore@2025-02-04.yang => infix-keystore@2025-06-17.yang} (100%) create mode 100644 src/confd/yang/confd/infix-wifi-country-codes.yang create mode 120000 src/confd/yang/confd/infix-wifi-country-codes@2025-06-02.yang create mode 100644 src/confd/yang/wifi.inc diff --git a/package/confd/confd.mk b/package/confd/confd.mk index 063b1962..96024a1f 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -25,6 +25,11 @@ else CONFD_CONF_OPTS += --disable-containers endif +ifeq ($(BR2_PACKAGE_FEATURE_WIFI),y) +CONFD_CONF_OPTS += --enable-wifi +else +CONFD_CONF_OPTS += --disable-wifi +endif define CONFD_INSTALL_EXTRA for fn in confd.conf resolvconf.conf; do \ cp $(CONFD_PKGDIR)/$$fn $(FINIT_D)/available/; \ @@ -69,6 +74,12 @@ define CONFD_INSTALL_YANG_MODULES_CONTAINERS $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/containers.inc endef endif +ifeq ($(BR2_PACKAGE_FEATURE_WIFI),y) +define CONFD_INSTALL_YANG_MODULES_WIFI + $(COMMON_SYSREPO_ENV) \ + $(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/wifi.inc +endef +endif # PER_PACKAGE_DIR # Since the last package in the dependency chain that runs sysrepoctl is confd, we need to @@ -97,6 +108,7 @@ CONFD_PRE_BUILD_HOOKS += CONFD_CLEANUP CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_EXTRA CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS +CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WIFI CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_IN_ROMFS CONFD_TARGET_FINALIZE_HOOKS += CONFD_CLEANUP diff --git a/src/confd/bin/wifi-detected.sh b/src/confd/bin/wifi-detected.sh deleted file mode 100755 index 079d2ba7..00000000 --- a/src/confd/bin/wifi-detected.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -if [ ! -e "/etc/wpa-supplicant-${INTERFACE}.conf" ]; then - cat < /etc/wpa-supplicant-${INTERFACE}.conf -ctrl_interface=/run/wpa_supplicant -update_config=0 -autoscan=periodic:10 -disable_scan_offload=1 -ap_scan=1 -# Empty network block to enable scanning without connecting -bgscan="simple: 30:-45:300" - -EOF - cat < /etc/finit.d/available/wpa-supplicant-${INTERFACE}.conf -# Generated by Infix wifi interface detected -service name:wpa_supplicant :${INTERFACE} <> \ - [2345] wpa_supplicant -s -i ${INTERFACE} -c /etc/wpa-supplicant-${INTERFACE}.conf \ - -- WPA supplicant @${INTERFACE} (scanning only) - -EOF - initctl -bfqn enable wpa-supplicant-${INTERFACE} - initctl reload - TIMEOUT=300 - status=$(wpa_cli -i ${INTERFACE} scan) - while [ "$status" != "OK" ]; do - status=$(wpa_cli -i ${INTERFACE} scan) - TIMEOUT=$((TIMEOUT-1)) - [ $TIMEOUT -eq 0 ] && logger -t wifi-detect "Failed to start scanning on $INTERFACE" && break - sleep 0.5 - done -fi diff --git a/src/confd/configure.ac b/src/confd/configure.ac index c00cd671..12982e00 100644 --- a/src/confd/configure.ac +++ b/src/confd/configure.ac @@ -41,6 +41,10 @@ AC_ARG_ENABLE(containers, AS_HELP_STRING([--enable-containers], [Enable support for containers]),,[ enable_containers=no]) +AC_ARG_ENABLE(wifi, + AS_HELP_STRING([--enable-wifi], [Enable support for Wi-Fi]),,[ + enable_wifi=no]) + AC_ARG_WITH(login-shell, AS_HELP_STRING([--with-login-shell=shell], [Login shell for new users, default: /bin/false]), [login_shell=$withval], [login_shell=yes]) @@ -52,6 +56,9 @@ AC_ARG_WITH(crypt, AS_IF([test "x$enable_containers" = "xyes"], [ AC_DEFINE(CONTAINERS, 1, [Built with container support])]) +AS_IF([test "x$enable_wifi" = "xyes"], [ + AC_DEFINE(HAVE_WIFI, 1, [Built with Wi-Fi support])]) + AS_IF([test "x$with_login_shell" != "xno"], [ AS_IF([test "x$login_shell" = "xyes"], [login_shell=/bin/false]) AC_DEFINE_UNQUOTED(LOGIN_SHELL, "$login_shell", [Default: /bin/false])],[ @@ -122,6 +129,7 @@ cat < #include #include +#include #include "ietf-interfaces.h" +#define IFACE_PROBE_TIMEOUT 40 + bool iface_has_quirk(const char *ifname, const char *quirkname) { @@ -27,6 +30,7 @@ bool iface_has_quirk(const char *ifname, const char *quirkname) return quirk ? json_is_true(quirk) : false; } + static bool iface_is_phys(const char *ifname) { bool is_phys = false; @@ -84,7 +88,9 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path) goto out; } - if (iface_is_phys(ifname)) + if (!fnmatch("wifi+([0-9])", ifname, FNM_EXTMATCH)) + inferred.data.string_val = "infix-if-type:wifi"; + else if (iface_is_phys(ifname)) inferred.data.string_val = "infix-if-type:ethernet"; else if (!fnmatch("br+([0-9])", ifname, FNM_EXTMATCH)) inferred.data.string_val = "infix-if-type:bridge"; @@ -112,6 +118,7 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path) inferred.data.string_val = "infix-if-type:gretap"; else if (!fnmatch("vxlan+([0-9])", ifname, FNM_EXTMATCH)) inferred.data.string_val = "infix-if-type:vxlan"; + free(ifname); if (inferred.data.string_val) @@ -416,6 +423,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, return vlan_gen(NULL, cif, ip); case IFT_VXLAN: return vxlan_gen(NULL, cif, ip); + case IFT_WIFI: + return wifi_gen(NULL, cif, net); case IFT_ETH: return netdag_gen_ethtool(net, cif, dif); case IFT_LO: @@ -444,7 +453,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net, return vlan_gen(dif, cif, ip); case IFT_ETH: return netdag_gen_ethtool(net, cif, dif); - + case IFT_WIFI: + return wifi_gen(dif, cif, net); case IFT_DUMMY: case IFT_GRE: case IFT_GRETAP: @@ -469,10 +479,11 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif) case IFT_DUMMY: case IFT_LO: break; + + case IFT_WIFI: case IFT_ETH: - /* case IFT_LAG: */ - /* ... REMEMBER WHEN ADDING BOND SUPPORT ... */ return lydx_get_child(dif, "custom-phys-address"); + case IFT_GRE: case IFT_GRETAP: return lydx_get_descendant(lyd_child(dif), "gre", NULL); @@ -522,12 +533,12 @@ static int link_gen_del(struct lyd_node *dif, FILE *ip) return 0; } -static int veth_gen_del(struct lyd_node *dif, FILE *ip) +static int veth_gen_del(struct lyd_node *dif, FILE *sh) { if (!veth_is_primary(dif)) return 0; - return link_gen_del(dif, ip); + return link_gen_del(dif, sh); } static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif, @@ -557,6 +568,10 @@ 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; @@ -576,15 +591,35 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif, return 0; } +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")) { + FILE *wait = dagger_fopen_net_init(net, ifname, NETDAG_INIT_TIMEOUT, "wait-interface.sh"); + if (!wait) { + return -EIO; + } + + fprintf(wait, "/usr/libexec/confd/wait-interface %s %d\n", ifname, IFACE_PROBE_TIMEOUT); + fclose(wait); + } + return SR_ERR_OK; +} + static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net, struct lyd_node *dif, struct lyd_node *cif) { const char *ifname = lydx_get_cattr(dif, "name"); + const char *iftype = lydx_get_cattr(dif, "type")?:lydx_get_cattr(cif, "type"); enum lydx_op op = lydx_get_op(dif); const char *attr; int err = 0; FILE *ip; + + err = netdag_gen_iface_timeout(net, ifname, iftype); + if (err) + goto err; + if ((err = cni_netdag_gen_iface(net, ifname, dif, cif))) { /* error or managed by CNI/podman */ if (err > 0) @@ -708,6 +743,7 @@ static int netdag_init_iface(struct lyd_node *cif) case IFT_DUMMY: case IFT_ETH: + case IFT_WIFI: case IFT_GRE: case IFT_GRETAP: case IFT_LO: @@ -788,7 +824,7 @@ static int ifchange(sr_session_ctx_t *session, uint32_t sub_id, const char *modu if (err) return err; - err = sr_get_data(session, "/interfaces/interface", 0, 0, 0, &cfg); + err = sr_get_data(session, "//.", 0, 0, 0, &cfg); if (err || !cfg) goto err_abandon; @@ -798,7 +834,6 @@ static int ifchange(sr_session_ctx_t *session, uint32_t sub_id, const char *modu cifs = lydx_get_descendant(cfg->tree, "interfaces", "interface", NULL); difs = lydx_get_descendant(diff, "interfaces", "interface", NULL); - err = netdag_init(session, &confd->netdag, cifs, difs); if (err) goto err_free_diff; @@ -827,15 +862,72 @@ err_abandon: return err; } +static int keystorecb(sr_session_ctx_t *session, uint32_t sub_id, const char *module, + const char *xpath, sr_event_t event, unsigned request_id, void *_confd) +{ + const struct lyd_node *diff; + const char *secret_name; + struct confd *confd = _confd; + struct lyd_node *interfaces, *interface, *dkeys, *dkey, *wifi; + sr_data_t *cfg = NULL; + int err = SR_ERR_OK; + + switch (event) { + case SR_EV_CHANGE: + break; + default: + return SR_ERR_OK; + } + + err = sr_get_data(session, "/interfaces/interface", 0, 0, 0, &cfg); + if (err || !cfg) + return err; + + diff = sr_get_change_diff(session); + if (!diff) + goto cleanup; + + interfaces = lydx_get_descendant(cfg->tree, "interfaces", "interface", NULL); + dkeys = lydx_get_descendant((struct lyd_node*) diff, "keystore", "symmetric-keys", "symmetric-key", NULL); + + LYX_LIST_FOR_EACH(dkeys, dkey, "symmetric-key") { + secret_name = lydx_get_cattr(dkey, "name"); + if (!secret_name) + continue; + + LYX_LIST_FOR_EACH(interfaces, interface, "interface") { + const char *name; + + if (iftype_from_iface(interface) != IFT_WIFI) + continue; + + wifi = lydx_get_child(interface, "wifi"); + if (!wifi) + continue; + + name = lydx_get_cattr(wifi, "secret"); + if (!name || strcmp(name, secret_name)) + continue; + wifi_gen(NULL, interface, &confd->netdag); + } + } + +cleanup: + if (cfg) + sr_release_data(cfg); + return err; +} + int ietf_interfaces_init(struct confd *confd) { int rc; REGISTER_CHANGE(confd->session, "ietf-interfaces", "/ietf-interfaces:interfaces//.", - 0, ifchange, confd, &confd->sub); + SR_SUBSCR_CHANGE_ALL_MODULES, ifchange, confd, &confd->sub); + REGISTER_CHANGE(confd->session, "ietf-keystore", "//*", + SR_SUBSCR_CHANGE_ALL_MODULES, keystorecb, confd, &confd->sub); REGISTER_CHANGE(confd->cand, "ietf-interfaces", "/ietf-interfaces:interfaces//.", SR_SUBSCR_UPDATE, ifchange_cand, confd, &confd->sub); - return SR_ERR_OK; fail: ERROR("failed, error %d: %s", rc, sr_strerror(rc)); diff --git a/src/confd/src/ietf-interfaces.h b/src/confd/src/ietf-interfaces.h index 25287df3..81892ff2 100644 --- a/src/confd/src/ietf-interfaces.h +++ b/src/confd/src/ietf-interfaces.h @@ -25,6 +25,7 @@ _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") \ @@ -119,6 +120,10 @@ int bridge_mcd_gen(struct lyd_node *cifs); /* infix-if-bridge-port.c */ 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); + /* 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 new file mode 100644 index 00000000..3f32517e --- /dev/null +++ b/src/confd/src/infix-if-wifi.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#include + +#include "ietf-interfaces.h" + +#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) +{ + FILE *wpa_supplicant = NULL, *wpa = NULL; + char *encryption_str; + int rc = SR_ERR_OK; + + if (!secret || !ssid || !country || !encryption) { + /* Not an error, updated from two ways, interface cb and keystore cb. */ + return 0; + } + + wpa = dagger_fopen_net_init(net, ifname, NETDAG_INIT_POST, "wpa_supplicant.sh"); + if (!wpa) { + rc = SR_ERR_INTERNAL; + goto out; + } + + fprintf(wpa, "# Generated by Infix confd\n"); + fprintf(wpa, "initctl -bfqn enable wifi@%s\n", ifname); + fclose(wpa); + + wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF, ifname); + if (!wpa_supplicant) { + rc = SR_ERR_INTERNAL; + goto out; + } + if (ssid) { + 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); + } else { + fprintf(wpa_supplicant, + "ctrl_interface=/run/wpa_supplicant\n" + "autoscan=periodic:10\n" + "ap_scan=1\n"); + + + } + fclose(wpa_supplicant); + +out: + return rc; + +} +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; + + bool enabled; + ifname = lydx_get_cattr(cif, "name"); + + if (cif && !lydx_get_child(cif, "wifi")) { + return wifi_gen_config(ifname, NULL, NULL, NULL, NULL, net); + } + + if (dif && !lydx_get_child(dif, "wifi")) { + return SR_ERR_OK; + } + + + 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-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, "iw dev %s disconnect\n", ifname); + fprintf(iw, "initctl -bfqn disable wifi@%s\n", ifname); + fclose(iw); + erasef(WPA_SUPPLICANT_CONF, ifname); + + return SR_ERR_OK; +} diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 257e51e2..f260c53a 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -34,7 +34,9 @@ MODULES=( "ieee802-ethernet-interface@2019-06-21.yang" "infix-ethernet-interface@2024-02-27.yang" "infix-factory-default@2023-06-28.yang" - "infix-interfaces@2025-01-09.yang -e vlan-filtering" - "infix-crypto-types@2025-02-04.yang" - "infix-keystore@2025-02-04.yang" + "infix-interfaces@2025-06-17.yang -e vlan-filtering" + "ietf-crypto-types -e cleartext-symmetric-keys" + "infix-crypto-types@2025-06-17.yang" + "ietf-keystore -e symmetric-keys" + "infix-keystore@2025-06-17.yang" ) diff --git a/src/confd/yang/confd/infix-crypto-types.yang b/src/confd/yang/confd/infix-crypto-types.yang index 4ad7a2d8..25851e67 100644 --- a/src/confd/yang/confd/infix-crypto-types.yang +++ b/src/confd/yang/confd/infix-crypto-types.yang @@ -5,6 +5,10 @@ module infix-crypto-types { import ietf-crypto-types { prefix ct; } + + revision 2025-06-17 { + description "Add Wi-Fi secret support."; + } revision 2025-02-04 { description "Initial"; } @@ -24,4 +28,14 @@ module infix-crypto-types { base public-key-format; base ct:ssh-public-key-format; } + identity symmetric-key-format { + description + "Base for symmetric key format"; + } + identity wifi-preshared-key-format { + base ct:symmetric-key-format; + base symmetric-key-format; + description + "WiFi secret key"; + } } diff --git a/src/confd/yang/confd/infix-crypto-types@2025-02-04.yang b/src/confd/yang/confd/infix-crypto-types@2025-06-17.yang similarity index 100% rename from src/confd/yang/confd/infix-crypto-types@2025-02-04.yang rename to src/confd/yang/confd/infix-crypto-types@2025-06-17.yang diff --git a/src/confd/yang/confd/infix-if-type.yang b/src/confd/yang/confd/infix-if-type.yang index 6432d7c1..8e2ea0d6 100644 --- a/src/confd/yang/confd/infix-if-type.yang +++ b/src/confd/yang/confd/infix-if-type.yang @@ -38,6 +38,14 @@ module infix-if-type { reference "internal"; } + /* + * Features + */ + + feature wifi { + description "WiFi support is an optional build-time feature in Infix."; + } + /* * Identities */ @@ -98,4 +106,10 @@ module infix-if-type { base ianaift:l2vlan; description "Layer 2 Virtual LAN using 802.1Q."; } + identity wifi { + if-feature wifi; + base infix-interface-type; + base ianaift:ieee80211; + description "WiFi interface"; + } } diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang new file mode 100644 index 00000000..85a3ba34 --- /dev/null +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -0,0 +1,185 @@ +submodule infix-if-wifi { + yang-version 1.1; + belongs-to infix-interfaces { + prefix infix-if; + } + import ietf-interfaces { + prefix if; + } + import ietf-yang-types { + prefix yang; + } + import ietf-netconf-acm { + prefix nacm; + } + import ietf-keystore { + prefix ks; + } + import infix-crypto-types { + prefix ixct; + } + import infix-if-type { + prefix infixift; + } + import infix-wifi-country-codes { + prefix iwcc; + } + + organization "KernelKit"; + contact "kernelkit@googlegroups.com"; + 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. + + It supports WiFi client mode and enables comprehensive management of + wireless connections, including encryption, country codes, and scanning."; + + revision 2025-05-27 { + description "Initial revision."; + reference "internal"; + } + + feature 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 + "Applies only to interfaces of type 'wifi'."; + } + + container wifi { + if-feature wifi; + presence "Configure Wi-Fi settings"; + + description + "WiFi-specific configuration and operational data."; + + 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; + + description + "WiFi encryption method. + + - auto (default): Enables WPA2/WPA3 auto-negotiation + - disabled: Disables encryption (open network)"; + } + + leaf ssid { + type string { + length "1..32"; + } + mandatory true; + + description + "WiFi network name (SSID). + + Case-sensitive, must match the target network. + + Length: 1–32 characters."; + } + + leaf secret { + type ks:symmetric-key-ref; + mandatory true; + must "../encryption != 'disabled'" { + error-message + "Pre-shared key required unless encryption is disabled."; + } + + description + "Pre-shared key (PSK) for WPA-secured networks."; + } + + leaf rssi { + config false; + type int16; + units "dBm"; + description + "Current received signal strength (RSSI) in dBm. + + Lower (more negative) values indicate stronger signals."; + } + + list scan-results { + config false; + key ssid; + description + "List of discovered networks."; + + leaf ssid { + type string; + description + "SSID of the discovered network."; + } + + leaf bssid { + type string; + description + "BSSID of the discovered network."; + } + + leaf rssi { + type int16; + units "dBm"; + description + "Signal strength of the network."; + } + + leaf channel { + type int16; + description + "Channel on which the network was detected."; + } + + leaf-list encryption { + ordered-by user; + type string; + description + "Human-readable description of the detected security."; + } + } + } + } +} diff --git a/src/confd/yang/confd/infix-if-wifi@2025-05-27.yang b/src/confd/yang/confd/infix-if-wifi@2025-05-27.yang new file mode 120000 index 00000000..645c0ba1 --- /dev/null +++ b/src/confd/yang/confd/infix-if-wifi@2025-05-27.yang @@ -0,0 +1 @@ +infix-if-wifi.yang \ No newline at end of file diff --git a/src/confd/yang/confd/infix-interfaces.yang b/src/confd/yang/confd/infix-interfaces.yang index e11461d7..592d2318 100644 --- a/src/confd/yang/confd/infix-interfaces.yang +++ b/src/confd/yang/confd/infix-interfaces.yang @@ -3,9 +3,6 @@ module infix-interfaces { namespace "urn:infix:interfaces:ns:yang:1.0"; prefix infix-if; - import infix-if-type { - prefix infix-ift; - } import ietf-interfaces { prefix if; } @@ -15,6 +12,12 @@ module infix-interfaces { import ietf-inet-types { prefix inet; } + import ietf-keystore { + prefix ks; + } + import infix-if-type { + prefix infix-ift; + } include infix-if-base; include infix-if-bridge; @@ -24,12 +27,18 @@ module infix-interfaces { include infix-if-vlan; include infix-if-gre; include infix-if-vxlan; + include infix-if-wifi; organization "KernelKit"; contact "kernelkit@googlegroups.com"; description "Linux bridge and lag extensions for ietf-interfaces."; - revision 2025-01-09 { + revision 2025-06-17 { + description "Add support for Wi-Fi client."; + reference "internal"; + } + + revision 2025-01-09 { description "Add support for link aggregation, static and LACP."; reference "internal"; } diff --git a/src/confd/yang/confd/infix-interfaces@2025-01-09.yang b/src/confd/yang/confd/infix-interfaces@2025-06-17.yang similarity index 100% rename from src/confd/yang/confd/infix-interfaces@2025-01-09.yang rename to src/confd/yang/confd/infix-interfaces@2025-06-17.yang diff --git a/src/confd/yang/confd/infix-keystore.yang b/src/confd/yang/confd/infix-keystore.yang index 6a33633c..97ae2b70 100644 --- a/src/confd/yang/confd/infix-keystore.yang +++ b/src/confd/yang/confd/infix-keystore.yang @@ -5,9 +5,15 @@ module infix-keystore { import ietf-keystore { prefix ks; } + import ietf-crypto-types { + prefix ct; + } import infix-crypto-types { prefix infix-ct; } + revision 2025-06-17 { + description "Add Wi-Fi secrets support"; + } revision 2025-02-04 { description "Initial"; } @@ -25,4 +31,35 @@ module infix-keystore { } } } + deviation "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-format" { + deviate not-supported; + } + augment "/ks:keystore/ks:symmetric-keys/ks:symmetric-key" { + leaf key-format { + type identityref { + base infix-ct:symmetric-key-format; + } + description + "Identifies the symmetric key's format + + Valid symmetric key formats are: + wifi-preshared-key-format - WiFi preshared key"; + } + } + deviation "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-type/ks:cleartext-key/ks:cleartext-key" { + deviate not-supported; + } + augment "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-type" { + case cleartext-key { + leaf cleartext-key { + type string; + must "../../ks: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"; + } + description "WiFi pre-shared key: 8-63 printable ASCII characters"; + + } + } + } } diff --git a/src/confd/yang/confd/infix-keystore@2025-02-04.yang b/src/confd/yang/confd/infix-keystore@2025-06-17.yang similarity index 100% rename from src/confd/yang/confd/infix-keystore@2025-02-04.yang rename to src/confd/yang/confd/infix-keystore@2025-06-17.yang diff --git a/src/confd/yang/confd/infix-meta.yang b/src/confd/yang/confd/infix-meta.yang index e111deed..e81b91fd 100644 --- a/src/confd/yang/confd/infix-meta.yang +++ b/src/confd/yang/confd/infix-meta.yang @@ -3,10 +3,15 @@ module infix-meta { namespace "urn:infix:meta:ns:yang:1.0"; prefix infix-meta; + import ietf-yang-metadata { + prefix md; + } + organization "KernelKit"; contact "kernelkit@googlegroups.com"; description "Infix metadata."; + revision 2024-10-18 { description "Mark entire meta container obsolete."; reference "internal"; @@ -16,6 +21,12 @@ module infix-meta { reference "internal"; } + md:annotation dirty { + type string; + description + "data"; + } + container meta { description "Meta data"; status obsolete; diff --git a/src/confd/yang/confd/infix-wifi-country-codes.yang b/src/confd/yang/confd/infix-wifi-country-codes.yang new file mode 100644 index 00000000..58bee770 --- /dev/null +++ b/src/confd/yang/confd/infix-wifi-country-codes.yang @@ -0,0 +1,287 @@ +module infix-wifi-country-codes { + yang-version 1.1; + namespace "urn:infix:wifi-country-codes"; + prefix iwcc; + + organization "KernelKit"; + + contact "kernelkit@googlegroups.com"; + + description + "This module defines country codes for WiFi regulatory domain + configuration based on ISO 3166-1 alpha-2 standard. + + This model provides country code definitions for use in + 802.11 wireless LAN regulatory compliance configuration. + + The regulatory domain configuration follows the principles + established in IETF RFCs for wireless access point management."; + + revision 2025-06-02 { + description + "Initial revision for WiFi country code support."; + reference + "RFC 5415: Control And Provisioning of Wireless Access Points (CAPWAP) Protocol Specification + RFC 5416: Control and Provisioning of Wireless Access Points (CAPWAP) Protocol Binding for IEEE 802.11"; + } + + typedef country-code { + type enumeration { + enum "AD" { description "Andorra"; } + enum "AE" { description "United Arab Emirates"; } + enum "AF" { description "Afghanistan"; } + enum "AG" { description "Antigua and Barbuda"; } + enum "AI" { description "Anguilla"; } + enum "AL" { description "Albania"; } + enum "AM" { description "Armenia"; } + enum "AO" { description "Angola"; } + enum "AQ" { description "Antarctica"; } + enum "AR" { description "Argentina"; } + enum "AS" { description "American Samoa"; } + enum "AT" { description "Austria"; } + enum "AU" { description "Australia"; } + enum "AW" { description "Aruba"; } + enum "AX" { description "Åland Islands"; } + enum "AZ" { description "Azerbaijan"; } + enum "BA" { description "Bosnia and Herzegovina"; } + enum "BB" { description "Barbados"; } + enum "BD" { description "Bangladesh"; } + enum "BE" { description "Belgium"; } + enum "BF" { description "Burkina Faso"; } + enum "BG" { description "Bulgaria"; } + enum "BH" { description "Bahrain"; } + enum "BI" { description "Burundi"; } + enum "BJ" { description "Benin"; } + enum "BL" { description "Saint Barthélemy"; } + enum "BM" { description "Bermuda"; } + enum "BN" { description "Brunei Darussalam"; } + enum "BO" { description "Bolivia"; } + enum "BQ" { description "Bonaire, Sint Eustatius and Saba"; } + enum "BR" { description "Brazil"; } + enum "BS" { description "Bahamas"; } + enum "BT" { description "Bhutan"; } + enum "BV" { description "Bouvet Island"; } + enum "BW" { description "Botswana"; } + enum "BY" { description "Belarus"; } + enum "BZ" { description "Belize"; } + enum "CA" { description "Canada"; } + enum "CC" { description "Cocos (Keeling) Islands"; } + enum "CD" { description "Congo, Democratic Republic of the"; } + enum "CF" { description "Central African Republic"; } + enum "CG" { description "Congo"; } + enum "CH" { description "Switzerland"; } + enum "CI" { description "Côte d'Ivoire"; } + enum "CK" { description "Cook Islands"; } + enum "CL" { description "Chile"; } + enum "CM" { description "Cameroon"; } + enum "CN" { description "China"; } + enum "CO" { description "Colombia"; } + enum "CR" { description "Costa Rica"; } + enum "CU" { description "Cuba"; } + enum "CV" { description "Cabo Verde"; } + enum "CW" { description "Curaçao"; } + enum "CX" { description "Christmas Island"; } + enum "CY" { description "Cyprus"; } + enum "CZ" { description "Czechia"; } + enum "DE" { description "Germany"; } + enum "DJ" { description "Djibouti"; } + enum "DK" { description "Denmark"; } + enum "DM" { description "Dominica"; } + enum "DO" { description "Dominican Republic"; } + enum "DZ" { description "Algeria"; } + enum "EC" { description "Ecuador"; } + enum "EE" { description "Estonia"; } + enum "EG" { description "Egypt"; } + enum "EH" { description "Western Sahara"; } + enum "ER" { description "Eritrea"; } + enum "ES" { description "Spain"; } + enum "ET" { description "Ethiopia"; } + enum "FI" { description "Finland"; } + enum "FJ" { description "Fiji"; } + enum "FK" { description "Falkland Islands (Malvinas)"; } + enum "FM" { description "Micronesia"; } + enum "FO" { description "Faroe Islands"; } + enum "FR" { description "France"; } + enum "GA" { description "Gabon"; } + enum "GB" { description "United Kingdom"; } + enum "GD" { description "Grenada"; } + enum "GE" { description "Georgia"; } + enum "GF" { description "French Guiana"; } + enum "GG" { description "Guernsey"; } + enum "GH" { description "Ghana"; } + enum "GI" { description "Gibraltar"; } + enum "GL" { description "Greenland"; } + enum "GM" { description "Gambia"; } + enum "GN" { description "Guinea"; } + enum "GP" { description "Guadeloupe"; } + enum "GQ" { description "Equatorial Guinea"; } + enum "GR" { description "Greece"; } + enum "GS" { description "South Georgia and the South Sandwich Islands"; } + enum "GT" { description "Guatemala"; } + enum "GU" { description "Guam"; } + enum "GW" { description "Guinea-Bissau"; } + enum "GY" { description "Guyana"; } + enum "HK" { description "Hong Kong"; } + enum "HM" { description "Heard Island and McDonald Islands"; } + enum "HN" { description "Honduras"; } + enum "HR" { description "Croatia"; } + enum "HT" { description "Haiti"; } + enum "HU" { description "Hungary"; } + enum "ID" { description "Indonesia"; } + enum "IE" { description "Ireland"; } + enum "IL" { description "Israel"; } + enum "IM" { description "Isle of Man"; } + enum "IN" { description "India"; } + enum "IO" { description "British Indian Ocean Territory"; } + enum "IQ" { description "Iraq"; } + enum "IR" { description "Iran"; } + enum "IS" { description "Iceland"; } + enum "IT" { description "Italy"; } + enum "JE" { description "Jersey"; } + enum "JM" { description "Jamaica"; } + enum "JO" { description "Jordan"; } + enum "JP" { description "Japan"; } + enum "KE" { description "Kenya"; } + enum "KG" { description "Kyrgyzstan"; } + enum "KH" { description "Cambodia"; } + enum "KI" { description "Kiribati"; } + enum "KM" { description "Comoros"; } + enum "KN" { description "Saint Kitts and Nevis"; } + enum "KP" { description "Korea, Democratic People's Republic of"; } + enum "KR" { description "Korea, Republic of"; } + enum "KW" { description "Kuwait"; } + enum "KY" { description "Cayman Islands"; } + enum "KZ" { description "Kazakhstan"; } + enum "LA" { description "Lao People's Democratic Republic"; } + enum "LB" { description "Lebanon"; } + enum "LC" { description "Saint Lucia"; } + enum "LI" { description "Liechtenstein"; } + enum "LK" { description "Sri Lanka"; } + enum "LR" { description "Liberia"; } + enum "LS" { description "Lesotho"; } + enum "LT" { description "Lithuania"; } + enum "LU" { description "Luxembourg"; } + enum "LV" { description "Latvia"; } + enum "LY" { description "Libya"; } + enum "MA" { description "Morocco"; } + enum "MC" { description "Monaco"; } + enum "MD" { description "Moldova"; } + enum "ME" { description "Montenegro"; } + enum "MF" { description "Saint Martin (French part)"; } + enum "MG" { description "Madagascar"; } + enum "MH" { description "Marshall Islands"; } + enum "MK" { description "North Macedonia"; } + enum "ML" { description "Mali"; } + enum "MM" { description "Myanmar"; } + enum "MN" { description "Mongolia"; } + enum "MO" { description "Macao"; } + enum "MP" { description "Northern Mariana Islands"; } + enum "MQ" { description "Martinique"; } + enum "MR" { description "Mauritania"; } + enum "MS" { description "Montserrat"; } + enum "MT" { description "Malta"; } + enum "MU" { description "Mauritius"; } + enum "MV" { description "Maldives"; } + enum "MW" { description "Malawi"; } + enum "MX" { description "Mexico"; } + enum "MY" { description "Malaysia"; } + enum "MZ" { description "Mozambique"; } + enum "NA" { description "Namibia"; } + enum "NC" { description "New Caledonia"; } + enum "NE" { description "Niger"; } + enum "NF" { description "Norfolk Island"; } + enum "NG" { description "Nigeria"; } + enum "NI" { description "Nicaragua"; } + enum "NL" { description "Netherlands"; } + enum "NO" { description "Norway"; } + enum "NP" { description "Nepal"; } + enum "NR" { description "Nauru"; } + enum "NU" { description "Niue"; } + enum "NZ" { description "New Zealand"; } + enum "OM" { description "Oman"; } + enum "PA" { description "Panama"; } + enum "PE" { description "Peru"; } + enum "PF" { description "French Polynesia"; } + enum "PG" { description "Papua New Guinea"; } + enum "PH" { description "Philippines"; } + enum "PK" { description "Pakistan"; } + enum "PL" { description "Poland"; } + enum "PM" { description "Saint Pierre and Miquelon"; } + enum "PN" { description "Pitcairn"; } + enum "PR" { description "Puerto Rico"; } + enum "PS" { description "Palestine, State of"; } + enum "PT" { description "Portugal"; } + enum "PW" { description "Palau"; } + enum "PY" { description "Paraguay"; } + enum "QA" { description "Qatar"; } + enum "RE" { description "Réunion"; } + enum "RO" { description "Romania"; } + enum "RS" { description "Serbia"; } + enum "RU" { description "Russian Federation"; } + enum "RW" { description "Rwanda"; } + enum "SA" { description "Saudi Arabia"; } + enum "SB" { description "Solomon Islands"; } + enum "SC" { description "Seychelles"; } + enum "SD" { description "Sudan"; } + enum "SE" { description "Sweden"; } + enum "SG" { description "Singapore"; } + enum "SH" { description "Saint Helena, Ascension and Tristan da Cunha"; } + enum "SI" { description "Slovenia"; } + enum "SJ" { description "Svalbard and Jan Mayen"; } + enum "SK" { description "Slovakia"; } + enum "SL" { description "Sierra Leone"; } + enum "SM" { description "San Marino"; } + enum "SN" { description "Senegal"; } + enum "SO" { description "Somalia"; } + enum "SR" { description "Suriname"; } + enum "SS" { description "South Sudan"; } + enum "ST" { description "Sao Tome and Principe"; } + enum "SV" { description "El Salvador"; } + enum "SX" { description "Sint Maarten (Dutch part)"; } + enum "SY" { description "Syrian Arab Republic"; } + enum "SZ" { description "Eswatini"; } + enum "TC" { description "Turks and Caicos Islands"; } + enum "TD" { description "Chad"; } + enum "TF" { description "French Southern Territories"; } + enum "TG" { description "Togo"; } + enum "TH" { description "Thailand"; } + enum "TJ" { description "Tajikistan"; } + enum "TK" { description "Tokelau"; } + enum "TL" { description "Timor-Leste"; } + enum "TM" { description "Turkmenistan"; } + enum "TN" { description "Tunisia"; } + enum "TO" { description "Tonga"; } + enum "TR" { description "Turkey"; } + enum "TT" { description "Trinidad and Tobago"; } + enum "TV" { description "Tuvalu"; } + enum "TW" { description "Taiwan"; } + enum "TZ" { description "Tanzania"; } + enum "UA" { description "Ukraine"; } + enum "UG" { description "Uganda"; } + enum "UM" { description "United States Minor Outlying Islands"; } + enum "US" { description "United States of America"; } + enum "UY" { description "Uruguay"; } + enum "UZ" { description "Uzbekistan"; } + enum "VA" { description "Holy See (Vatican City State)"; } + enum "VC" { description "Saint Vincent and the Grenadines"; } + enum "VE" { description "Venezuela"; } + enum "VG" { description "Virgin Islands, British"; } + enum "VI" { description "Virgin Islands, U.S."; } + enum "VN" { description "Viet Nam"; } + enum "VU" { description "Vanuatu"; } + enum "WF" { description "Wallis and Futuna"; } + enum "WS" { description "Samoa"; } + enum "YE" { description "Yemen"; } + enum "YT" { description "Mayotte"; } + enum "ZA" { description "South Africa"; } + enum "ZM" { description "Zambia"; } + enum "ZW" { description "Zimbabwe"; } + } + description + "Complete list of ISO 3166-1 alpha-2 country codes for + regulatory domain configuration."; + reference + "ISO 3166-1:2020 Codes for the representation of names of countries + and their subdivisions -- Part 1: Country codes"; + } +} 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-06-02.yang new file mode 120000 index 00000000..8c580a1e --- /dev/null +++ b/src/confd/yang/confd/infix-wifi-country-codes@2025-06-02.yang @@ -0,0 +1 @@ +infix-wifi-country-codes.yang \ No newline at end of file diff --git a/src/confd/yang/wifi.inc b/src/confd/yang/wifi.inc new file mode 100644 index 00000000..280851db --- /dev/null +++ b/src/confd/yang/wifi.inc @@ -0,0 +1,4 @@ +MODULES=( + "infix-interfaces -e wifi" + "infix-if-type -e wifi" +) From f25f0ab0507219483596d01caae8d4cc01124ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 18 Jun 2025 19:58:30 +0200 Subject: [PATCH 10/15] Wi-Fi: Add template finit service for wpa_supplicant --- .../rootfs/etc/finit.d/available/wifi@.conf | 5 +++++ .../common/rootfs/usr/libexec/infix/wifi-scanner | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 board/common/rootfs/etc/finit.d/available/wifi@.conf create mode 100755 board/common/rootfs/usr/libexec/infix/wifi-scanner diff --git a/board/common/rootfs/etc/finit.d/available/wifi@.conf b/board/common/rootfs/etc/finit.d/available/wifi@.conf new file mode 100644 index 00000000..eb0321df --- /dev/null +++ b/board/common/rootfs/etc/finit.d/available/wifi@.conf @@ -0,0 +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 + +task name:wifi-scanner :%i [2345] /usr/libexec/infix/wifi-scanner %i -- Start scanning for SSID @ %i diff --git a/board/common/rootfs/usr/libexec/infix/wifi-scanner b/board/common/rootfs/usr/libexec/infix/wifi-scanner new file mode 100755 index 00000000..b34c698b --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/wifi-scanner @@ -0,0 +1,16 @@ +#!/bin/sh + +if [ $# -ne 1 ]; then + echo "usage: $0 " + exit 1 +fi +ifname=$1 + +TIMEOUT=300 +status=$(wpa_cli -i $ifname scan) +while [ "$status" != "OK" ]; do + status=$(wpa_cli -i $ifname scan) + TIMEOUT=$((TIMEOUT-1)) + [ $TIMEOUT -eq 0 ] && logger -t wifi-scanner "Failed to start scanning $ifname" && exit 1 + sleep 0.5 +done From 5fbb30fed5dcba7a09a60cbf89f739a1665165e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 3 Jun 2025 19:56:36 +0200 Subject: [PATCH 11/15] Wi-Fi: Add Wi-Fi information to operational and CLI output * show wifi scan to see located SSIDs * interface status --- .../klish-plugin-infix/klish-plugin-infix.mk | 1 - src/show/show.py | 15 ++ src/statd/python/cli_pretty/cli_pretty.py | 106 ++++++++++-- .../python/yanger/ietf_interfaces/link.py | 10 +- .../python/yanger/ietf_interfaces/wifi.py | 160 ++++++++++++++++++ 5 files changed, 280 insertions(+), 12 deletions(-) create mode 100644 src/statd/python/yanger/ietf_interfaces/wifi.py diff --git a/package/klish-plugin-infix/klish-plugin-infix.mk b/package/klish-plugin-infix/klish-plugin-infix.mk index 23c0c387..2b5f4e6d 100644 --- a/package/klish-plugin-infix/klish-plugin-infix.mk +++ b/package/klish-plugin-infix/klish-plugin-infix.mk @@ -28,7 +28,6 @@ KLISH_PLUGIN_INFIX_CONF_OPTS += --enable-shell else KLISH_PLUGIN_INFIX_CONF_OPTS += --disable-shell endif - ifeq ($(BR2_PACKAGE_BASH),y) KLISH_PLUGIN_INFIX_CONF_OPTS += --with-shell=/bin/bash else diff --git a/src/show/show.py b/src/show/show.py index 49d2a3e3..573ee160 100755 --- a/src/show/show.py +++ b/src/show/show.py @@ -164,6 +164,20 @@ def lldp(args: List[str]): return cli_pretty(data, "show-lldp") +def wifi(args: List[str]): + iface = args[0] + if len(args) == 0: + print("Illigal usage") + return + if is_valid_interface_name(iface): + if not os.path.exists(f"/sys/class/net/{iface}/wireless"): + print("Not a Wi-Fi interface") + return + data = run_sysrepocfg("/ietf-interfaces:interfaces") + cli_pretty(data, "show-wifi-scan", "-n", iface) + else: + print(f"Invalid interface name: {iface}") + def execute_command(command: str, args: List[str]): command_mapping = { 'dhcp': dhcp, @@ -174,6 +188,7 @@ def execute_command(command: str, args: List[str]): 'lldp': lldp, 'software' : software, 'stp': stp, + 'wifi': wifi } if command in command_mapping: diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 19e64c54..232818ce 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -82,6 +82,20 @@ class PadNtpSource: poll = 14 +class PadWifiScan: + ssid = 40 + encryption = 30 + signal = 9 + + +class PadLldp: + interface = 16 + rem_idx = 10 + time = 12 + chassis_id = 20 + port_id = 20 + + class Decore(): @staticmethod def decorate(sgr, txt, restore="0"): @@ -103,6 +117,10 @@ class Decore(): def green(txt): return Decore.decorate("32", txt, "39") + @staticmethod + def bright_green(txt): + return Decore.decorate("1;32", txt, "39") + @staticmethod def yellow(txt): return Decore.decorate("33", txt, "39") @@ -116,12 +134,17 @@ class Decore(): return Decore.decorate("100", txt) -class PadLldp: - interface = 16 - rem_idx = 10 - time = 12 - chassis_id = 20 - port_id = 20 +def rssi_to_status(rssi): + if rssi <= -75: + status = Decore.bright_green("excellent") + elif rssi <= -65: + status = Decore.green("good") + elif rssi <= -50: + status = Decore.yellow("poor") + else: + status = Decore.red("bad") + + return status def datetime_now(): @@ -196,7 +219,6 @@ class Date(datetime): tz = tz.replace(":", "") return cls.strptime(f"{date}+{tz}", "%Y-%m-%dT%H:%M:%S%z") - class Route: def __init__(self, data, ip): self.data = data @@ -524,12 +546,16 @@ class Iface: self.gre = self.data.get('infix-interfaces:gre') self.vxlan = self.data.get('infix-interfaces:vxlan') + self.wifi = self.data.get('infix-interfaces:wifi') if self.data.get('infix-interfaces:vlan'): self.lower_if = self.data.get('infix-interfaces:vlan', None).get('lower-layer-if',None) else: self.lower_if = '' + def is_wifi(self): + return self.type == "infix-if-type:wifi" + def is_vlan(self): return self.type == "infix-if-type:vlan" @@ -554,7 +580,7 @@ class Iface: def is_gretap(self): return self.data['type'] == "infix-if-type:gretap" - + def oper(self, detail=False): """Remap in brief overview to fit column widths.""" if not detail and self.oper_status == "lower-layer-down": @@ -625,6 +651,47 @@ class Iface: row = self._pr_proto_common("loopback", False, pipe); print(row) + def pr_wifi_ssids(self): + 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"]) + status=rssi_to_status(result["rssi"]) + row = f"{result['ssid']:<{PadWifiScan.ssid}}" + row += f"{encstr:<{PadWifiScan.encryption}}" + row += f"{status:<{PadWifiScan.signal}}" + + print(row) + + + def pr_proto_wifi(self, pipe=''): + row = self._pr_proto_common("ethernet", True, pipe); + print(row) + ssid = None + rssi = None + + if self.wifi: + rssi=self.wifi.get("rssi") + ssid=self.wifi.get("ssid") + if ssid is None: + ssid="------" + + if rssi is None: + signal="------" + else: + signal=rssi_to_status(rssi) + data_str = f"ssid: {ssid}, signal: {signal}" + + row = f"{'':<{Pad.iface}}" + row += f"{'wifi':<{Pad.proto}}" + row += f"{'':<{Pad.state}}{data_str}" + print(row) + def pr_proto_br(self, br_vlans): data_str = "" @@ -765,6 +832,12 @@ class Iface: self.pr_proto_ipv4() self.pr_proto_ipv6() + def pr_wifi(self): + self.pr_name(pipe="") + self.pr_proto_wifi() + self.pr_proto_ipv4() + self.pr_proto_ipv6() + def pr_vlan(self, _ifaces): self.pr_name(pipe="") self.pr_proto_eth() @@ -874,6 +947,14 @@ class Iface: else: 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("") + self.pr_wifi_ssids() + if self.gre: print(f"{'local address':<{20}}: {self.gre['local']}") print(f"{'remote address':<{20}}: {self.gre['remote']}") @@ -1045,6 +1126,10 @@ def pr_interface_list(json): iface.pr_vxlan() continue + if iface.is_wifi(): + iface.pr_wifi() + continue + if iface.is_vlan(): iface.pr_vlan(ifaces) continue @@ -1320,13 +1405,13 @@ def show_lldp(json): f"{'CHASSIS-ID':<{PadLldp.chassis_id}}" f"{'PORT-ID':<{PadLldp.port_id}}" ) - + print(Decore.invert(header)) for port_data in lldp_ports: port_name = port_data["name"] neighbors = port_data.get("remote-systems-data", []) - + for neighbor in neighbors: entry = LldpNeighbor(port_name, neighbor) entry.print() @@ -1394,6 +1479,7 @@ def main(): show_routing_table(json_data, args.ip) elif args.command == "show-software": show_software(json_data, args.name) + else: print(f"Error, unknown command '{args.command}'") sys.exit(1) diff --git a/src/statd/python/yanger/ietf_interfaces/link.py b/src/statd/python/yanger/ietf_interfaces/link.py index 5fff2a6f..fd7c5d8c 100644 --- a/src/statd/python/yanger/ietf_interfaces/link.py +++ b/src/statd/python/yanger/ietf_interfaces/link.py @@ -1,3 +1,4 @@ +from ..host import HOST from . import common from . import bridge @@ -7,6 +8,7 @@ from . import lag from . import tun from . import veth from . import vlan +from . import wifi def statistics(iplink): @@ -24,13 +26,16 @@ def statistics(iplink): def iplink2yang_type(iplink): + ifname=iplink["ifname"] match iplink["link_type"]: case "loopback": return "infix-if-type:loopback" case "gre"|"gre6": return "infix-if-type:gre" case "ether": - pass + data = HOST.run(tuple(f"ls /sys/class/net/{ifname}/wireless/".split()), default="no") + if data != "no": + return "infix-if-type:wifi" case _: return "infix-if-type:other" @@ -133,6 +138,9 @@ def interface(iplink, ipaddr): case "infix-if-type:vlan": if v := vlan.vlan(iplink): interface["infix-interfaces:vlan"] = v + case "infix-if-type:wifi": + if w := wifi.wifi(iplink["ifname"]): + interface["infix-interfaces:wifi"] = w match iplink2yang_lower(iplink): case "infix-interfaces:bridge-port": diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py new file mode 100644 index 00000000..d373dd5c --- /dev/null +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -0,0 +1,160 @@ +from ..host import HOST +import json +import re + +def wifi(ifname): + data=HOST.run(tuple(f"wpa_cli -i {ifname} status".split()), default="") + wifi_data={} + + if data != "": + for line in data.splitlines(): + 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") + + # 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["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) + + return wifi_data + + +def parse_wpa_scan_result(scan_output): + networks = {} + lines = scan_output.strip().split('\n') + + # Skip header line and any empty lines + for line in lines: + line = line.strip() + if not line or 'bssid / frequency' in line.lower(): + continue + + # Split by tabs or multiple spaces + parts = re.split(r'\t+|\s{2,}', line) + + if len(parts) >= 5: + bssid = parts[0].strip() + frequency = int(parts[1].strip()) + rssi = int(parts[2].strip()) + flags = parts[3].strip() + ssid = parts[4].strip() if len(parts) > 4 else "" + + # Skip hidden SSIDs (empty or whitespace only) + if not ssid or ssid.isspace() or '\\x00' in ssid: + continue + + # Extract encryption information from flags + encryption = extract_encryption(flags) + + # Convert frequency to channel + channel = frequency_to_channel(frequency) + + # Keep only the network with best (highest) RSSI per SSID + if ssid not in networks or rssi < networks[ssid]['rssi']: + networks[ssid] = { + 'bssid': bssid, + 'ssid': ssid, + 'rssi': rssi, + 'encryption': encryption, + 'channel': channel + } + # Convert to list and sort by RSSI (best first) + result = list(networks.values()) + result.sort(key=lambda x: x['rssi'], reverse=False) + + return result + +def frequency_to_channel(frequency): + """Convert frequency (MHz) to WiFi channel number""" + freq = int(frequency) + + # 2.4 GHz band (channels 1-14) + if 2412 <= freq <= 2484: # Channel 14 is special + if freq == 2484: + return 14 + return (freq - 2412) // 5 + 1 + + # 5 GHz band (channels 36-165) + elif 5170 <= freq <= 5825: + return (freq - 5000) // 5 + + # 6 GHz band (channels 1-233) + elif 5955 <= freq <= 7115: + return (freq - 5950) // 5 + + else: + return f"Unknown ({freq} MHz)" + +def extract_encryption(flags): + """Extract detailed encryption information from flags string""" + flags = flags.upper() + encryption_info = { + 'protocols': [], + 'key_mgmt': [], + 'ciphers': [], + 'auth_type': 'Unknown' + } + + # Extract WPA protocols + if 'WPA3' in flags: + encryption_info['protocols'].append('WPA3') + if 'WPA2' in flags: + encryption_info['protocols'].append('WPA2') + if 'WPA-' in flags and 'WPA2' not in flags and 'WPA3' not in flags: + encryption_info['protocols'].append('WPA') + + # Extract key management methods + if 'PSK' in flags: + encryption_info['key_mgmt'].append('PSK') + encryption_info['auth_type'] = 'Personal' + if 'EAP' in flags: + encryption_info['key_mgmt'].append('EAP') + encryption_info['auth_type'] = 'Enterprise' + if 'SAE' in flags: # WPA3 Personal + encryption_info['key_mgmt'].append('SAE') + encryption_info['auth_type'] = 'Personal' + if 'OWE' in flags: # Enhanced Open (WPA3) + encryption_info['key_mgmt'].append('OWE') + encryption_info['auth_type'] = 'Enhanced Open' + if 'FT' in flags: + encryption_info['key_mgmt'].append('FT') + + # Extract cipher suites + if 'CCMP' in flags: + encryption_info['ciphers'].append('CCMP') + if 'TKIP' in flags: + encryption_info['ciphers'].append('TKIP') + if 'GCMP' in flags: + encryption_info['ciphers'].append('GCMP') + + # Handle special cases + if 'WEP' in flags: + return ['WEP'] + + if not encryption_info['protocols'] and 'ESS' in flags: + return ['Open'] + + # Return array of supported protocols with auth type + result = [] + for protocol in encryption_info['protocols']: + if encryption_info['auth_type'] == 'Enterprise': + result.append(f"{protocol}-Enterprise") + elif encryption_info['auth_type'] == 'Personal': + result.append(f"{protocol}-Personal") + elif encryption_info['auth_type'] == 'Enhanced Open': + result.append(f"{protocol}-Enhanced-Open") + else: + result.append(protocol) + + return result if result else ['Unknown'] From 08f42072774d1cf69936ff0887ac7887378a5d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 18 Jun 2025 07:52:25 +0200 Subject: [PATCH 12/15] configs: Enable Wi-Fi support --- configs/aarch64_defconfig | 2 ++ configs/x86_64_defconfig | 2 ++ 2 files changed, 4 insertions(+) diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index 9783987b..9612ff49 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -165,6 +165,8 @@ BR2_PACKAGE_PODMAN_DRIVER_VFS=y BR2_PACKAGE_TETRIS=y BR2_PACKAGE_ROUSETTE=y BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y +BR2_PACKAGE_FEATURE_WIFI=y +BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK=y BR2_PACKAGE_HOST_PYTHON_YANGDOC=y BR2_DOWNLOAD_FORCE_CHECK_HASHES=y BR2_PACKAGE_GETENT=y diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index 3aa5c2e8..216fcd1e 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -167,6 +167,8 @@ BR2_PACKAGE_PODMAN_DRIVER_VFS=y BR2_PACKAGE_TETRIS=y BR2_PACKAGE_ROUSETTE=y BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y +BR2_PACKAGE_FEATURE_WIFI=y +BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK=y BR2_PACKAGE_HOST_PYTHON_YANGDOC=y BR2_DOWNLOAD_FORCE_CHECK_HASHES=y BR2_PACKAGE_GETENT=y From 24852caad0aab503e55ca61be7c3a46562e9807d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Thu, 12 Jun 2025 20:33:18 +0200 Subject: [PATCH 13/15] doc: Add Wi-Fi Documention --- doc/wifi.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 doc/wifi.md diff --git a/doc/wifi.md b/doc/wifi.md new file mode 100644 index 00000000..be5574b3 --- /dev/null +++ b/doc/wifi.md @@ -0,0 +1,152 @@ +# 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. + +## 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 + +## Supported Wi-Fi Adapters + +Wi-Fi support is primarily tested with Realtek chipset-based adapters. + +### Known Working Chipsets + +- RTL8821CU +- Other Realtek chipsets may work but are not guaranteed + + +> [!NOTE] Some Realtek chipsets require proprietary drivers not included in the standard kernel +> Firmware requirements vary by chipset +> Check kernel logs if your adapter is not detected + +## Configuration + +Add a supported Wi-Fi network device. To verify that it has been +detected, look for `wifi0` in `show interfaces` + +``` +admin@example:/> show interfaces +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: ------ + +``` +Add the new Wi-Fi interface to the configuration to start scanning. +``` +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 + +``` +admin@infix-00-00-00:/> show interfaces name wifi0 +name : wifi0 +type : wifi +index : 3 +mtu : 1500 +operational status : down +physical address : f0:09:0d:36:5f:86 +ipv4 addresses : +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 +``` + +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. + +Configure your Wi-Fi secret in the keystore, it should be between 8 +and 63 characters + +``` +admin@example:/> configure +admin@example:/config/> edit keystore symmetric-key example +admin@example:/config/keystore/symmetric-key/example/> set key-format wifi-preshared-key-format +admin@example:/config/keystore/symmetric-key/example/> set cleartext-key mysecret +admin@example:/config/keystore/symmetric-key/example/> leave +admin@example:/> +``` + +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` + + +``` +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/> 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 interfaces` again. + + +``` +admin@example:/> show interfaces +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:/> +``` + +## Troubleshooting Connection Issues + +Use `show wifi scan wifi0` and `show interfaces` 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` +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 interfaces` + +If issues persist, check the system log for specific error messages that can help identify the root cause. From 5eeed0e743921e39fbcfb97dae28557cb063a490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 17 Jun 2025 21:42:51 +0200 Subject: [PATCH 14/15] changelog: Update with Wi-Fi client Also add kernel update to correct release. --- doc/ChangeLog.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 4948f025..5afaf6c7 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -3,11 +3,21 @@ Change Log All notable changes to the project are documented in this file. -[v25.05.1][] - 2025-06-12 +[v25.06.0][UNRELEASED] - ------------------------- ### Changes - Upgrade Linux kernel to 6.12.33 (LTS) +- Add Wi-Fi client support and add support for some USB-Wi-Fi cards + +### Fixes + + +[v25.05.1][] - 2025-06-12 +------------------------- + +### Changes +- Upgrade Linux kernel to 6.12.32 (LTS) ### Fixes - Fix #1060: Restore of missing CLI commands, regression in Infix v25.05.0 From de33df14e2389b15555644d22d22166f827bf7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Thu, 19 Jun 2025 13:30:42 +0200 Subject: [PATCH 15/15] test: case: ietf_syslog/remote: Fix typo of interface type Has not been triggered before because we ignored operations on fixed interfaces. --- test/case/ietf_syslog/remote/test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/case/ietf_syslog/remote/test.py b/test/case/ietf_syslog/remote/test.py index 46f9b47d..c65a133b 100755 --- a/test/case/ietf_syslog/remote/test.py +++ b/test/case/ietf_syslog/remote/test.py @@ -90,7 +90,6 @@ with infamy.Test() as test: "interface": [ { "name": server_link, - "type": "infix-if-type:bridge", "enabled": True, "ipv4": { "address": [