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] 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" +)