Subject: [PATCH v2 5/5] services: networking: Add iwd-service-type.

From: Sergey Trofimov <[email-masked]>
Date: Sat, 24 May 2025 15:01:32 +0200

* gnu/services/networking.scm (iwd-service-type): New service type.
(iwd-configuration), (iwd-settings), (iwd-scan-settings),
(iwd-general-settings), (iwd-network-settings): New configuration types.
* doc/guix.texi (Networking setup): Document it.

Change-Id: I852115b9c6768b3ec4eedb34a7f9e66438bd1429
---
 doc/guix.texi               | 162 +++++++++++++++++++++++
 gnu/services/networking.scm | 249 ++++++++++++++++++++++++++++++++++++
 2 files changed, 411 insertions(+)

diff --git a/doc/guix.texi b/doc/guix.texi
index a9c815c35c..4d1b6372df 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -22408,6 +22408,168 @@ Networking Setup
 @end table
 @end deftp
 
+@cindex IWD
+@defvar iwd-service-type
+This is the service type to run @acronyml{IWD,Internet Wireless Daemon},
+a wireless daemon required to authenticate against encrypted WiFi
+networks.
+@end defvar
+
+@quotation Warning
+By default @code{iwd} removes and re-creates interfaces it manages.  It
+doesn't play nicely with @code{dhcp-client-service-type} that enumerates
+wireless interfaces before starting the Shepherd service.  Use either
+@code{dhcpcd-service-type} or the @code{iwd}'s built-in DHCP client (see
+@code{enable-network-configuration} option below).
+@end quotation
+
+@quotation Warning
+@code{iwd-service-type} conflicts with @code{wpa-supplicant-service-type}.
+Make sure only one is configured.
+@end quotation
+
+@c %start of fragment
+
+@deftp {Data Type} iwd-configuration
+Available @code{iwd-configuration} fields are:
+
+@table @asis
+@item @code{iwd} (default: @code{iwd}) (type: file-like)
+The IWD package to use.
+
+@item @code{interfaces} (default: @code{()}) (type: list-of-strings)
+If this is set, it must specify @dfn{glob patterns} matching network
+interfaces that IWD will control.
+
+@item @code{ignored-interfaces} (default: @code{()}) (type: list-of-strings)
+If this is set, it must specify @dfn{glob patterns} matching network
+interfaces that IWD will not manage.
+
+@item @code{phys} (default: @code{()}) (type: list-of-strings)
+If this is set, it must specify @dfn{glob patterns} matching network
+PHYs names that IWD will control.
+
+@item @code{ignored-phys} (default: @code{()}) (type: list-of-strings)
+If this is set, it must specify @dfn{glob patterns} matching network
+PHYs names that IWD will not manage.
+
+@item @code{shepherd-requirement} (default: @code{()}) (type: list-of-symbols)
+Shepherd requirements the service should depend on.
+
+@item @code{shepherd-provision} (default: @code{(iwd wireless-daemon)}) (type: list-of-symbols)
+The name(s) of the service.
+
+@item @code{config} (type: iwd-settings)
+Configuration settings.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} iwd-settings
+Available @code{iwd-settings} fields are:
+
+@table @asis
+@item @code{general} (type: iwd-general-settings)
+General settings.
+
+@item @code{network} (type: maybe-iwd-network-settings)
+Network settings.
+
+@item @code{scan} (type: maybe-iwd-scan-settings)
+Scan settings.
+
+@item @code{extra-config} (default: @code{()}) (type: list-of-strings)
+Extra configuration values to append to the IWD configuration file.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} iwd-general-settings
+Available @code{iwd-general-settings} fields are:
+
+@table @asis
+@item @code{enable-network-configuration?} (default: @code{#f}) (type: boolean)
+Setting this option to true enables @code{iwd} to configure the network
+interfaces with the IP addresses.
+
+@item @code{extra-options} (default: @code{()}) (type: alist)
+An association list of option symbols/strings to string values to be
+appended to the General settings group.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} iwd-network-settings
+Available @code{iwd-network-settings} fields are:
+
+@table @asis
+@item @code{enable-ipv6?} (default: @code{#t}) (type: boolean)
+Sets the global default that tells @code{iwd} whether it should
+configure IPv6 addresses and routes
+
+@item @code{name-resolving-service} (default: @code{none}) (type: resolving-service)
+Configures a DNS resolution method used by the system.
+
+@item @code{extra-options} (default: @code{()}) (type: alist)
+An association list of option symbols/strings to string values to be
+appended to the Network settings group.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} iwd-scan-settings
+Available @code{iwd-scan-settings} fields are:
+
+@table @asis
+@item @code{disable-periodic-scan?} (default: @code{#f}) (type: boolean)
+Setting this option to @code{#t} will prevent @code{iwd} from issuing
+the periodic scans for the available networks while disconnected.
+
+@item @code{initial-periodic-scan-interval} (default: @code{10}) (type: number)
+The initial periodic scan interval upon disconnect (in seconds).
+
+@item @code{maximum-periodic-scan-interval} (default: @code{300}) (type: number)
+The maximum periodic scan interval (in seconds).
+
+@item @code{disable-roaming-scan?} (default: @code{#f}) (type: boolean)
+Setting this option to @code{#t} will prevent @code{iwd} from trying to
+scan when roaming decisions are activated.
+
+@item @code{extra-options} (default: @code{()}) (type: alist)
+An association list of option symbols/strings to string values to be
+appended to the Scan settings group.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
 @cindex ModemManager
 Some networking devices such as modems require special care, and this is
 what the services below focus on.
diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm
index 900790ef2d..0a114d2a18 100644
--- a/gnu/services/networking.scm
+++ b/gnu/services/networking.scm
@@ -41,6 +41,7 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services networking)
+  #:use-module (gnu home services utils)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
@@ -56,6 +57,7 @@ (define-module (gnu services networking)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages cluster)
   #:use-module (gnu packages connman)
+  #:use-module (gnu packages dns)
   #:use-module (gnu packages freedesktop)
   #:use-module (gnu packages linux)
   #:use-module (gnu packages tor)
@@ -80,6 +82,7 @@ (define-module (gnu services networking)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-43)
+  #:use-module ((ice-9 curried-definitions) #:select (define))
   #:use-module (ice-9 match)
   #:use-module (ice-9 string-fun)
   #:use-module (json)
@@ -235,6 +238,21 @@ (define-module (gnu services networking)
             wpa-supplicant-configuration-extra-options
             wpa-supplicant-service-type
 
+            iwd-configuration
+            iwd-configuration-config
+            iwd-configuration-ignored-interfaces
+            iwd-configuration-ignored-phys
+            iwd-configuration-interfaces
+            iwd-configuration-iwd
+            iwd-configuration-phys
+            iwd-configuration-shepherd-provision
+            iwd-configuration-shepherd-requirement
+            iwd-service-type
+            iwd-general-settings
+            iwd-network-settings
+            iwd-scan-settings
+            iwd-settings
+
             hostapd-configuration
             hostapd-configuration?
             hostapd-configuration-package
@@ -2099,6 +2117,237 @@ (define wpa-supplicant-service-type
 implements authentication, key negotiation and more for wireless networks.")
                   (default-value (wpa-supplicant-configuration)))))
 
+
+;;;
+;;; IWD
+;;;
+(define (iwd-uglify-field-name name)
+  (object->camel-case-string name 'upper))
+
+(define (iwd-serialize-base field-name val)
+  (format #f "~a=~a\n" field-name val))
+
+(define (iwd-serialize-field field-name val)
+  (iwd-serialize-base (iwd-uglify-field-name field-name) val))
+
+(define iwd-serialize-number iwd-serialize-field)
+
+(define (iwd-serialize-boolean field-name value)
+  (iwd-serialize-field
+   (string-trim-right (symbol->string field-name) #\?)
+   (if value "true" "false")))
+
+(define (iwd-serialize-alist field-name value)
+  (if (null? value)
+      ""
+      #~(string-append #$@(generic-serialize-alist list
+                                                   iwd-serialize-base
+                                                   value))))
+
+(define-enumerated-field-type resolving-service
+  (none systemd resolvconf)
+  (prefix iwd-))
+
+(define-configuration iwd-general-settings
+  (enable-network-configuration?
+   (boolean #f)
+   "Setting this option to true enables @code{iwd} to configure the network
+interfaces with the IP addresses.")
+  (extra-options
+   (alist '())
+   "An association list of option symbols/strings to string values to be
+appended to the General settings group.")
+
+  (prefix iwd-))
+
+(define-configuration iwd-network-settings
+  (enable-ipv6?
+   (boolean #t)
+   "Sets the global default that tells @code{iwd} whether it should configure
+IPv6 addresses and routes")
+
+  (name-resolving-service
+   (resolving-service 'none)
+   "Configures a DNS resolution method used by the system.")
+
+  (extra-options
+   (alist '())
+   "An association list of option symbols/strings to string values to be
+appended to the Network settings group.")
+
+  (prefix iwd-))
+
+(define-configuration iwd-scan-settings
+  (disable-periodic-scan?
+   (boolean #f)
+   "Setting this option to @code{#t} will prevent @code{iwd} from issuing the
+periodic scans for the available networks while disconnected.")
+
+  (initial-periodic-scan-interval
+   (number 10)
+   "The initial periodic scan interval upon disconnect (in seconds).")
+
+  (maximum-periodic-scan-interval
+   (number 300)
+   "The maximum periodic scan interval (in seconds).")
+
+  (disable-roaming-scan?
+   (boolean #f)
+   "Setting this option to @code{#t} will prevent @code{iwd} from trying to scan
+when roaming decisions are activated.")
+
+  (extra-options
+   (alist '())
+   "An association list of option symbols/strings to string values to be
+appended to the Scan settings group.")
+
+  (prefix iwd-))
+
+(define-maybe iwd-network-settings)
+(define-maybe iwd-scan-settings)
+
+(define ((iwd-serialize-config-section fields) name cfg)
+  #~(format #f "[~a]\n~a\n"
+            #$(string-upcase (object->string name) 0 1)
+            #$(serialize-configuration cfg fields)))
+
+(define serialize-iwd-network-settings
+  (iwd-serialize-config-section iwd-network-settings-fields))
+
+(define serialize-iwd-scan-settings
+  (iwd-serialize-config-section iwd-scan-settings-fields))
+
+(define serialize-iwd-general-settings
+  (iwd-serialize-config-section iwd-general-settings-fields))
+
+(define-configuration iwd-settings
+  (general
+   (iwd-general-settings (iwd-general-settings))
+   "General settings.")
+
+  (network
+   maybe-iwd-network-settings
+   "Network settings.")
+
+  (scan
+   maybe-iwd-scan-settings
+   "Scan settings.")
+
+  (extra-config
+   (list-of-strings '())
+   "Extra configuration values to append to the IWD configuration file."
+   (serializer (lambda (_ value) (string-join value "\n" 'suffix)))))
+
+(define-configuration/no-serialization iwd-configuration
+  (iwd
+   (file-like iwd)
+   "The IWD package to use.")
+
+  (interfaces
+   (list-of-strings '())
+   "If this is set, it must specify @dfn{glob patterns} matching network
+interfaces that IWD will control.")
+
+  (ignored-interfaces
+   (list-of-strings '())
+   "If this is set, it must specify @dfn{glob patterns} matching network
+interfaces that IWD will not manage.")
+
+  (phys
+   (list-of-strings '())
+   "If this is set, it must specify @dfn{glob patterns} matching network
+PHYs names that IWD will control.")
+
+  (ignored-phys
+   (list-of-strings '())
+   "If this is set, it must specify @dfn{glob patterns} matching network
+PHYs names that IWD will not manage.")
+
+  (shepherd-requirement
+   (list-of-symbols '())
+   "Shepherd requirements the service should depend on.")
+
+  (shepherd-provision
+   (list-of-symbols '(iwd wireless-daemon))
+   "The name(s) of the service.")
+
+  (config
+   (iwd-settings (iwd-settings))
+   "Configuration settings."))
+
+(define (iwd-generate-documentation)
+  (configuration->documentation 'iwd-configuration)
+  (configuration->documentation 'iwd-settings)
+  (configuration->documentation 'iwd-general-settings)
+  (configuration->documentation 'iwd-network-settings)
+  (configuration->documentation 'iwd-scan-settings))
+
+(define (iwd-config-file config)
+  "Return an IWD configuration file."
+  (mixed-text-file "main.conf"
+                   (serialize-configuration
+                    (iwd-configuration-config config)
+                    iwd-settings-fields)))
+
+(define (iwd-environment config)
+  (let ((resolver
+         (and=> (iwd-settings-network (iwd-configuration-config config))
+                iwd-network-settings-name-resolving-service)))
+
+    (if (eq? resolver 'resolvconf)
+        #~(list (string-append "PATH=" #$openresolv "/sbin"))
+        '())))
+
+(define (iwd-shepherd-service config)
+  (match-record config <iwd-configuration>
+                (iwd interfaces ignored-interfaces
+                     phys ignored-phys
+                     shepherd-requirement shepherd-provision)
+
+    (list (shepherd-service
+           (documentation "Run Internet Wireless Daemon")
+           (provision shepherd-provision)
+           (requirement `(user-processes dbus-system loopback ,@shepherd-requirement))
+           (start #~(make-forkexec-constructor
+                     (list (string-append #$iwd "/libexec/iwd")
+                           "--logger=syslog"
+                           #$@(if (null? interfaces) '()
+                                  (list (string-append "--interfaces="
+                                                       (string-join interfaces ","))))
+                           #$@(if (null? ignored-interfaces) '()
+                                  (list (string-append "--nointerfaces="
+                                                       (string-join ignored-interfaces ","))))
+                           #$@(if (null? phys) '()
+                                  (list (string-append "--phys="
+                                                       (string-join phys ","))))
+                           #$@(if (null? ignored-phys) '()
+                                  (list (string-append "--nophys="
+                                                       (string-join ignored-phys ",")))))
+
+                     #:environment-variables
+                     #$(iwd-environment config)))
+           (stop #~(make-kill-destructor))))))
+
+(define (iwd-etc-service config)
+  `(("iwd/main.conf" ,(iwd-config-file config))))
+
+(define iwd-service-type
+  (let ((add-iwd-package (compose list iwd-configuration-iwd)))
+    (service-type (name 'iwd)
+                  (extensions
+                   (list (service-extension shepherd-root-service-type
+                                            iwd-shepherd-service)
+                         (service-extension etc-service-type
+                                            iwd-etc-service)
+                         (service-extension dbus-root-service-type
+                                            add-iwd-package)
+                         (service-extension profile-service-type
+                                            add-iwd-package)))
+                  (default-value (iwd-configuration))
+                  (description
+                   "Run @url{https://iwd.wiki.kernel.org/,Iwd},
+a network connection manager."))))
+
 
 ;;;
 ;;; Hostapd.
-- 
2.49.0