summary refs log tree commit diff
path: root/whispers/services/whispers/vpn.scm
diff options
context:
space:
mode:
Diffstat (limited to 'whispers/services/whispers/vpn.scm')
-rw-r--r--whispers/services/whispers/vpn.scm3419
1 files changed, 3419 insertions, 0 deletions
diff --git a/whispers/services/whispers/vpn.scm b/whispers/services/whispers/vpn.scm
new file mode 100644
index 0000000..4c324e8
--- /dev/null
+++ b/whispers/services/whispers/vpn.scm
@@ -0,0 +1,3419 @@
+;;; Whispers --- Stealth VPN and ssh tunneler
+;;; Copyright © 2023 Runciter <runciter@whispers-vpn.org>
+;;;
+;;; This file is part of Whispers.
+;;;
+;;; Whispers is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; Whispers is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Whispers.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (whispers services whispers vpn)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (gnu system shadow)
+  #:use-module (gnu services)
+  #:use-module (gnu services shepherd)
+  #:use-module (whispers services whispers)
+  #:use-module (whispers services ssh-tunneler)
+  #:use-module (gnu packages base)
+  #:use-module (gnu packages guile)
+  #:use-module (gnu packages ssh)
+  #:use-module (gnu packages linux)
+  #:use-module (gnu packages networking)
+  #:export (whispers-vpn-service-type
+            whispers-vpn-configuration
+            whispers-vpn-configuration?))
+
+(define-record-type* <whispers-vpn-configuration>
+  whispers-vpn-configuration make-whispers-vpn-configuration
+  whispers-vpn-configuration?
+  this-whispers-vpn-configuration
+  ;; A file-like object.
+  (ssh-package           whispers-vpn-configuration-ssh-package
+                         (default openssh))
+  ;; A file-like-object.
+  (procps-package       whispers-vpn-configuration-procps-package
+                        (default procps))
+  ;; A file-like-object.
+  (iproute-package      whispers-vpn-configuration-iproute-package
+                        (default iproute))
+  ;; A file-like-object.
+  (iptables-package      whispers-vpn-configuration-iptables-package
+                         (default iptables))
+  ;; A file-like-object.
+  (sed-package           whispers-vpn-configuration-sed-package
+                         (default sed))
+  ;; A network-constants record.
+  (constants             whispers-vpn-configuration-constants
+                         (default (network-constants)))
+  ;; A boolean value.
+  (masquerade?           whispers-vpn-configuration-masquerade?
+                         (default #t))
+  ;; A boolean value.
+  (ipv4-ip-forward?      whispers-vpn-configuration-ipv4-ip-forward?
+                         (default #t))
+  ;; A boolean value.
+  (client?               whispers-vpn-configuration-client?
+                         (default #f))
+  ;; A boolean value.
+  (manual-physical-if?   whispers-vpn-configuration-manual-physical-if?
+                         (default #f))
+  ;; A string.
+  (physical-if-override  whispers-vpn-configuration-physical-if-override
+                         (default "eth0"))
+  ;; A boolean value.
+  (manual-phy-gateway?   whispers-vpn-configuration-manual-phy-gateway?
+                         (default #f))
+  ;; A string.
+  (phy-gateway-override  whispers-vpn-configuration-phy-gateway-override
+                         (default "192.168.1.1"))
+  ;; An integer
+  (client-tun-device     whispers-vpn-configuration-client-tun-device
+                         (default 0))
+  ;; A string.
+  (server-sshd-host      whispers-vpn-configuration-server-sshd-host
+                         (default "127.0.0.1"))
+  ;; An integer.
+  (server-sshd-port      whispers-vpn-configuration-server-sshd-port
+                         (default 22))
+  ;; A string.
+  (proxy-sshd-user       whispers-vpn-configuration-proxy-sshd-user
+                         (default "root"))
+  ;; A string.
+  (proxy-sshd-host       whispers-vpn-configuration-proxy-sshd-host
+                         (default "127.0.0.1"))
+  ;; An integer.
+  (proxy-sshd-port       whispers-vpn-configuration-proxy-sshd-port
+                         (default 22))
+  ;; An integer.
+  (client-sshd-port      whispers-vpn-configuration-client-sshd-port
+                         (default 22))
+  ;; A boolean value.
+  (stealth?              whispers-vpn-configuration-stealth?
+                         (default #f))
+  ;; An integer.
+  (stealth-base-port     whispers-vpn-configuration-stealth-base-port
+                         (default 31273))
+  ;; A boolean value.
+  (stealth-clear-password?
+   whispers-vpn-configuration-stealth-clear-password?
+   (default #f))
+  ;; A string.
+  (stealth-user-password whispers-vpn-configuration-stealth-user-password
+                         (default "none"))
+  ;; An integer.
+  (forward-port          whispers-vpn-configuration-forward-port
+                         (default 36492))
+  ;; An integer.
+  ;; If there's a NIC, sshd-port can't be used, need an overridable default.
+  (forward-exit-port     whispers-vpn-configuration-forward-exit-port
+                         (default
+                           (whispers-vpn-configuration-server-sshd-port
+                            this-whispers-vpn-configuration))
+                         (thunked))
+  ;; A boolean value.
+  (vpn-server-clear-password?
+   whispers-vpn-configuration-vpn-server-clear-password?
+   (default #f))
+  ;; A string.
+  (vpn-server-user-password
+   whispers-vpn-configuration-vpn-server-user-password
+   (default "none"))
+  ;; A quoted cron job time specification.
+  (forward-resurrect-time-spec
+   whispers-vpn-configuration-forward-resurrect-time-spec
+   (default ''(next-minute '(34))))
+  ;; A boolean value.
+  (client-whispers-user-clear-password?
+   whispers-vpn-configuration-client-whispers-user-clear-password?
+   (default #f))
+  ;; A string.
+  (client-whispers-password
+   whispers-vpn-configuration-client-whispers-password
+   (default "none"))
+  ;; A boolean value.
+  (%auto-register?       whispers-vpn-configuration-auto-register?
+                         (default #f))
+  ;; A boolean value.
+  (%auto-connect?       whispers-vpn-configuration-auto-connect?
+                        (default #f)))
+
+(define-record-type* <network-constants>
+  network-constants make-network-constants
+  network-constants?
+  this-network-constants
+  ;; A string.
+  (ip-prefix             network-constants-ip-prefix
+                         (default "10.0.0"))
+  ;; An integer.
+  (lowest-ip             network-constants-lowest-ip
+                         (default 10))
+  ;; An integer.
+  (lowest-tun-number     network-constants-lowest-tun-number
+                         (default
+                           (quotient
+                            (network-constants-lowest-ip
+                             this-network-constants)
+                            2))
+                         (thunked))
+  ;; An integer.
+  (base-port-reverse     network-constants-base-port-reverse
+                         (default 37215))
+  ;; An integer.
+  (handshake-port        network-constants-handshake-port
+                         (default 38573)))
+
+(define (stealth-record config stealth-port)
+  (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+         (sshd-user (whispers-vpn-configuration-proxy-sshd-user config))
+         (sshd-host (whispers-vpn-configuration-proxy-sshd-host config))
+         (sshd-port (whispers-vpn-configuration-proxy-sshd-port config))
+         (forward-port (whispers-vpn-configuration-forward-port config))
+         (clear-password?
+          (whispers-vpn-configuration-stealth-clear-password? config))
+         (user-password
+          (whispers-vpn-configuration-stealth-user-password config))
+         (pid-folder  "/run/whispers/vpn/ssh-tunneler")
+         (log-folder  "/var/log/whispers/vpn/ssh-tunneler")
+         (auto-register? (whispers-vpn-configuration-auto-register? config)))
+    (ssh-connection-configuration (ssh-package ssh-package)
+                                  (sshd-user sshd-user)
+                                  (sshd-host sshd-host)
+                                  (known-hosts-files '("/dev/null"))
+                                  (strict-check "no")
+                                  (require-networking? #f)
+                                  (sshd-port sshd-port)
+                                  (forwards
+                                   (list (dynamic-forward-configuration
+                                          (entry-port stealth-port))))
+                                  (clear-password? clear-password?)
+                                  (sshd-user-password user-password)
+                                  (pid-folder-override? #t)
+                                  (pid-folder-override pid-folder)
+                                  (dedicated-log-file? #t)
+                                  (log-rotate? #t)
+                                  (log-folder-override? #t)
+                                  (log-folder-override log-folder)
+                                  ;; FIXME: auto registration not working in
+                                  ;; the demo script.
+                                  (%auto-start? auto-register?))))
+
+(define (forward-configuration config)
+  (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (socks-port (whispers-vpn-configuration-stealth-base-port config))
+         (stealth-config (stealth-record config socks-port))
+         (sshd-user "whispers")
+         (sshd-host (whispers-vpn-configuration-server-sshd-host config))
+         (sshd-port (whispers-vpn-configuration-server-sshd-port config))
+         (forward-port (whispers-vpn-configuration-forward-port config))
+         (exit-port (whispers-vpn-configuration-forward-exit-port config))
+        (clear-password?
+          (whispers-vpn-configuration-vpn-server-clear-password? config))
+         (user-password
+          (whispers-vpn-configuration-vpn-server-user-password config))
+         (pid-folder  "/run/whispers/vpn/ssh-tunneler")
+         (log-folder  "/var/log/whispers/vpn/ssh-tunneler")
+         (auto-register? (whispers-vpn-configuration-auto-register? config)))
+    (ssh-connection-configuration (ssh-package ssh-package)
+                                  (require-networking? #f)
+                                  (socks-proxy-config
+                                   (socks-proxy-configuration
+                                    (use-proxy? stealth?)
+                                    (dynamic-forward (if stealth?
+                                                         stealth-config
+                                                         #f))))
+                                  (sshd-user sshd-user)
+                                  (sshd-host sshd-host)
+                                  (sshd-port sshd-port)
+                                  (known-hosts-files '("/dev/null"))
+                                  (strict-check "no")
+                                  (forwards
+                                   (list (port-forward-configuration
+                                          (forward-type 'port)
+                                          (entry-port forward-port)
+                                          (exit-port exit-port))))
+                                  (clear-password? clear-password?)
+                                  (sshd-user-password user-password)
+                                  (pid-folder-override? #t)
+                                  (pid-folder-override pid-folder)
+                                  (dedicated-log-file? #t)
+                                  (log-rotate? #t)
+                                  (log-folder-override? log-folder)
+                                  (log-folder-override log-folder)
+                                  (%auto-start? auto-register?))))
+
+(define (handshake-forward-configuration config)
+  (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (base-port (whispers-vpn-configuration-stealth-base-port config))
+         (stealth-config (stealth-record config
+                                         (+ base-port
+                                            1)))
+         (sshd-user "whispers")
+         (sshd-host (whispers-vpn-configuration-server-sshd-host config))
+         (sshd-port (whispers-vpn-configuration-server-sshd-port config))
+         (constants (whispers-vpn-configuration-constants config))
+         (handshake-port (network-constants-handshake-port constants))
+         (exit-port (whispers-vpn-configuration-client-sshd-port config))
+         (clear-password?
+          (whispers-vpn-configuration-vpn-server-clear-password? config))
+         (user-password
+          (whispers-vpn-configuration-vpn-server-user-password config))
+         (herd-path "/run/current-system/profile/bin/herd")
+         (handshake (string-append herd-path
+                                   " "
+                                   "lieutenant-action"
+                                   " "
+                                   "whispers"
+                                   " "
+                                   "lieutenant-action"
+                                   " "
+                                   "vpn"
+                                   " "
+                                   "complete-handshake"
+                                   " "
+                                   "network-rw"))
+         (pid-folder  "/run/whispers/vpn/ssh-tunneler")
+         (log-folder  "/var/log/whispers/vpn/ssh-tunneler"))
+    (ssh-connection-configuration (ssh-package ssh-package)
+                                  (require-networking? #f)
+                                  (socks-proxy-config
+                                   (socks-proxy-configuration
+                                    (use-proxy? stealth?)
+                                    (dynamic-forward (if stealth?
+                                                         stealth-config
+                                                         #f))))
+                                  (sshd-user sshd-user)
+                                  (sshd-host sshd-host)
+                                  (sshd-port sshd-port)
+                                  (known-hosts-files '("/dev/null"))
+                                  (strict-check "no")
+                                  (forwards
+                                   (list (reverse-port-forward-configuration
+                                          (forward-type 'reverse-port)
+                                          (entry-port handshake-port)
+                                          (exit-port exit-port))))
+                                  (clear-password? clear-password?)
+                                  (sshd-user-password user-password)
+                                  (extra-local-commands `(,handshake))
+                                  (pid-folder-override? #t)
+                                  (pid-folder-override pid-folder)
+                                  (dedicated-log-file? #t)
+                                  (log-rotate? #t)
+                                  (log-folder-override? #t)
+                                  (log-folder-override log-folder)
+                                  (%auto-start? #f))))
+
+(define (reverse-stealth-conf config)
+  (let ((socks-port (whispers-vpn-configuration-stealth-base-port
+                     config)))
+    (stealth-record config
+                    (+ socks-port
+                       2))))
+
+(define (reverse-forward-configurations config)
+  (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (sshd-user "whispers")
+         (sshd-host (whispers-vpn-configuration-server-sshd-host config))
+         (sshd-port (whispers-vpn-configuration-server-sshd-port config))
+         (exit-port (whispers-vpn-configuration-client-sshd-port config))
+         (clear-password?
+          (whispers-vpn-configuration-vpn-server-clear-password? config))
+         (user-password
+          (whispers-vpn-configuration-vpn-server-user-password config))
+         (empty-net (empty-network (whispers-vpn-configuration-constants
+                                    config)))
+         (pid-folder  "/run/whispers/vpn/ssh-tunneler")
+         (log-folder  "/var/log/whispers/vpn/ssh-tunneler")
+         (herd-path "/run/current-system/profile/bin/herd")
+         (tunnel (string-append herd-path
+                                " "
+                                "lieutenant-action"
+                                " "
+                                "whispers"
+                                " "
+                                "lieutenant-action"
+                                " "
+                                "vpn"
+                                " "
+                                "tun-start-dev"
+                                " "
+                                "connecting"))
+         (net-constants (whispers-vpn-configuration-constants config))
+         (empty-net (empty-network net-constants)))
+    (map (lambda (empty-voucher)
+           (let* ((entr ((lambda (lst)
+                           (if (null? lst)
+                               #f
+                               (cadr (car lst)))) (filter (lambda (entry)
+                                                            (equal?
+                                                             (car entry)
+                                                             'reverse-port))
+                                                          empty-voucher)))
+                  (dyn (if stealth?
+                           (reverse-stealth-conf config)
+                           #f))
+                  (forw (list (reverse-port-forward-configuration
+                               (forward-type 'reverse-port)
+                               (entry-port entr)
+                               (exit-port exit-port)))))
+             (ssh-connection-configuration (ssh-package ssh-package)
+                                           (require-networking? #f)
+                                           (socks-proxy-config
+                                            (socks-proxy-configuration
+                                             (use-proxy? stealth?)
+                                             (dynamic-forward dyn)))
+                                           (sshd-user sshd-user)
+                                           (sshd-host sshd-host)
+                                           (sshd-port sshd-port)
+                                           (known-hosts-files '("/dev/null"))
+                                           (strict-check "no")
+                                           (forwards forw)
+                                           (clear-password? clear-password?)
+                                           (sshd-user-password user-password)
+                                           (extra-local-commands `(,tunnel))
+                                           (pid-folder-override? #t)
+                                           (pid-folder-override pid-folder)
+                                           (dedicated-log-file? #t)
+                                           (log-rotate? #t)
+                                           (log-folder-override? #t)
+                                           (log-folder-override log-folder)
+                                           (%auto-start? #f))))
+         empty-net)))
+
+(define (tun-stealth-conf config)
+  (let ((socks-port (whispers-vpn-configuration-stealth-base-port
+                     config)))
+    (stealth-record config
+                    (+ socks-port
+                       3))))
+
+(define (tun-device-forward-configurations config)
+  (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (socks-port (whispers-vpn-configuration-stealth-base-port config))
+         (sshd-user "whispers")
+         (sshd-host (whispers-vpn-configuration-server-sshd-host config))
+         (sshd-port (whispers-vpn-configuration-server-sshd-port config))
+         (exit-port (whispers-vpn-configuration-client-sshd-port config))
+         (clear-password?
+          (whispers-vpn-configuration-vpn-server-clear-password? config))
+         (user-password
+          (whispers-vpn-configuration-vpn-server-user-password config))
+         (pid-folder "/run/whispers/vpn/ssh-tunneler")
+         (log-folder "/var/log/whispers/vpn/ssh-tunneler")
+         (herd-path "/run/current-system/profile/bin/herd")
+         (tun-int (whispers-vpn-configuration-client-tun-device config))
+         (tun-str (number->string tun-int))
+         (complete-connect (string-append herd-path
+                                          " "
+                                          "lieutenant-action"
+                                          " "
+                                          "whispers"
+                                          " "
+                                          "lieutenant-action"
+                                          " "
+                                          "vpn"
+                                          " "
+                                          "set-connected-knock"
+                                          " "
+                                          "network-rw"
+                                          " "
+                                          "&&"
+                                          " "
+                                          herd-path
+                                          " "
+                                          "lieutenant-action"
+                                          " "
+                                          "whispers"
+                                          " "
+                                          "start-tun"
+                                          tun-str
+                                          " "
+                                          "vpn"))
+         (net-constants (whispers-vpn-configuration-constants config))
+         (empty-net (empty-network net-constants)))
+    (map (lambda (empty-voucher)
+           (let* ((entr ((lambda (lst)
+                           (if (null? lst)
+                               #f
+                               (cadr (car lst)))) (filter
+                                                   (lambda (entry)
+                                                     (equal?
+                                                      (car entry)
+                                                      'tun-device-number))
+                                                   empty-voucher)))
+                  (dyn (if stealth?
+                           (tun-stealth-conf config)
+                           #f))
+                  (forw  (list (tunnel-forward-configuration
+                                (entry-type 'preset)
+                                (exit-type 'preset)
+                                (entry-tun tun-int)
+                                (exit-tun entr)))))
+             (ssh-connection-configuration (ssh-package ssh-package)
+                                           (require-networking? #f)
+                                           (socks-proxy-config
+                                            (socks-proxy-configuration
+                                             (use-proxy? stealth?)
+                                             (dynamic-forward dyn)))
+                                           (sshd-user sshd-user)
+                                           (sshd-host sshd-host)
+                                           (sshd-port sshd-port)
+                                           (known-hosts-files '("/dev/null"))
+                                           (strict-check "no")
+                                           (forwards forw)
+                                           (clear-password? clear-password?)
+                                           (sshd-user-password user-password)
+                                           (extra-local-commands
+                                            `(,complete-connect))
+                                           (pid-folder-override? #t)
+                                           (pid-folder-override pid-folder)
+                                           (dedicated-log-file? #t)
+                                           (log-rotate? #t)
+                                           (log-folder-override? #t)
+                                           (log-folder-override log-folder)
+                                           (%auto-start? #f))))
+         empty-net)))
+
+(define (int-range start end)
+  (if (< end start)
+      '()
+      (cons start (int-range (+ start 1) end))))
+
+(define (empty-network constants)
+  "Return the network state of an empty network configurabale by
+CONSTANTS, a record of tne <network-constants> type."
+  (let ((ip-concat
+         (lambda (ip-prefix last-byte)
+           (string-append ip-prefix
+                          "."
+                          (number->string last-byte)))))
+    (map (lambda (voucher-index)
+           (let ((ip-prefix (network-constants-ip-prefix constants))
+                 (lowest-ip (network-constants-lowest-ip constants))
+                 (lowest-tun-number (network-constants-lowest-tun-number
+                                     constants))
+                 (lowest-port-number (network-constants-base-port-reverse
+                                      constants)))
+             `((voucher-number ,voucher-index)
+               (client-hostname #f)
+               (tun-device-number ,(+ lowest-tun-number voucher-index))
+               (reverse-port ,(+ lowest-port-number voucher-index))
+               (server-ip ,(ip-concat ip-prefix (+ lowest-ip
+                                                   (* voucher-index 2))))
+               (client-ip ,(ip-concat ip-prefix (+ lowest-ip
+                                                   (* voucher-index 2)
+                                                   1)))
+               (tun-request? #f)
+               (connected? #f))))
+         (int-range 0
+                    (- 127
+                       (quotient (network-constants-lowest-ip constants)
+                                 2))))))
+
+(define (tun-dev-str tun-int)
+  "Returns the string naming a TUN device whose number is the integer
+TUN-INT."
+  (string-append "tun"
+                 (number->string tun-int)))
+
+(define (tun-dev-sym tun-int)
+  "Returns a symbol cast from the string naming a TUN device whose
+number is the integer TUN-INT."
+  (string->symbol (tun-dev-str tun-int)))
+
+(define (ipv4-forward-actions config)
+  "Returns a list of <shepherd-action> records for the 'ipv4-ip-forward
+lieutenant of the 'tcp-ip service, used by the VPN server to turn IPv4
+forwarding on and off on demand as appropriate, configurable by CONFIG,
+a record of the <whispers-vpn-configuration> type."
+  (let* ((procps-pk (whispers-vpn-configuration-procps-package config))
+         (sysctl-exec (file-append procps-pk
+                                   "/sbin/sysctl")))
+    (list (shepherd-action
+           (name 'pre-start)
+           (documentation "Turn on IPv4 forwarding for this server.")
+           (procedure
+            #~(lambda (running)
+                (perform-service-action (lookup-service 'ipv4-ip-forward)
+                                        'ipv4-ip-forward-on))))
+          (shepherd-action
+           (name 'post-stop)
+           (documentation "Turn off IPv4 forwarding for this server.")
+           (procedure
+            #~(lambda (running)
+                (perform-service-action (lookup-service 'ipv4-ip-forward)
+                                        'ipv4-ip-forward-off))))
+          (shepherd-action
+           (name 'ipv4-ip-forward-on)
+           (documentation "Turn on IPv4 forwarding in the kernel
+run-time parameters.")
+           (procedure
+            #~(lambda (running)
+                (fork+exec-command (list #$sysctl-exec
+                                         "-w"
+                                         "net.ipv4.ip_forward=1")))))
+          (shepherd-action
+           (name 'ipv4-ip-forward-off)
+           (documentation "Turn off IPv4 forwarding in the kernel
+run-time parameters.")
+           (procedure
+            #~(lambda (running)
+                (fork+exec-command (list #$sysctl-exec
+                                         "-w"
+                                         "net.ipv4.ip_forward=0"))))))))
+
+(define (masquerade-actions config)
+  "Returns a list of <shepherd-action> records for the 'masquerade
+lieutenant of the 'tcp-ip service, used by the VPN servier to turn NAT
+masquerading on and off on demand as appropriate, configurable by
+CONFIG, a record of the <whispers-vpn-configuration> type."
+  (let* ((iptb-pk (whispers-vpn-configuration-iptables-package config))
+         (iptables-exec (file-append iptb-pk
+                                     "/sbin/iptables"))
+         (ip-package (whispers-vpn-configuration-iproute-package config))
+         (ip-exec (file-append ip-package
+                               "/sbin/ip"))
+         (sed-package (whispers-vpn-configuration-sed-package
+                       config))
+         (sed-exec (file-append sed-package
+                                "/bin/sed"))
+         (herd-path "/run/current-system/profile/bin/herd")
+         (socket-path "/run/whispers/vpn/unix-sockets/vpn.sock"))
+    (list (shepherd-action
+           (name 'pre-start)
+           (documentation "Turn on NAT masquerading for this server.")
+           (procedure
+            #~(lambda (running)
+                (perform-service-action (lookup-service 'masquerade)
+                                        'masquerade-on))))
+          (shepherd-action
+           (name 'post-stop)
+           (documentation "Turn off NAT masquerading for this server.")
+           (procedure
+            #~(lambda (running)
+                (perform-service-action (lookup-service 'masquerade)
+                                        'masquerade-off))))
+          (shepherd-action
+           (name 'masquerade-on)
+           (documentation "Turn on NAT masquerading.")
+           (procedure
+            #~(lambda (running)
+                (fork+exec-command (list
+                                    "/bin/sh"
+                                    "-c"
+                                    (string-append
+                                     #$herd-path
+                                     " "
+                                     "-s"
+                                     " "
+                                     #$socket-path
+                                     " "
+                                     "burn-state"
+                                     " "
+                                     "physical-dev-rw"
+                                     " "
+                                     "$("
+                                     #$ip-exec
+                                     " route | "
+                                     #$sed-exec
+                                     " -n '/^default/ "
+                                     "s=.*dev \\(.*\\)$=\\1=p'"
+                                     ") $("
+                                     #$ip-exec
+                                     " route | "
+                                     #$sed-exec
+                                     " -n '/^default/ "
+                                     "s=.*via "
+                                     "\\([^ ]\\+\\).*$=\\1=p'"
+                                     ")"
+                                     " "
+                                     "&&"
+                                     " "
+                                     #$iptables-exec
+                                     " "
+                                     "-v"
+                                     " "
+                                     "-t"
+                                     " "
+                                     "nat"
+                                     " "
+                                     "-A"
+                                     " "
+                                     "POSTROUTING"
+                                     " "
+                                     "-o"
+                                     " "
+                                     "$("
+                                     #$herd-path
+                                     " "
+                                     "-s"
+                                     " "
+                                     #$socket-path
+                                     " "
+                                     "display-physical-interface-name"
+                                     " "
+                                     "physical-dev-rw"
+                                     ")"
+                                     " "
+                                     "-j"
+                                     " "
+                                     "MASQUERADE"))))))
+          (shepherd-action
+           (name 'masquerade-off)
+           (documentation "Turn off NAT masquerading.")
+           (procedure
+            #~(lambda (running)
+                (fork+exec-command (list
+                                    "/bin/sh"
+                                    "-c"
+                                    (string-append
+                                     #$iptables-exec
+                                     " "
+                                     "-v"
+                                     " "
+                                     "-t"
+                                     " "
+                                     "nat"
+                                     " "
+                                     "-D"
+                                     " "
+                                     "POSTROUTING"
+                                     " "
+                                     "-o"
+                                     " "
+                                     "$("
+                                     #$herd-path
+                                     " "
+                                     "-s"
+                                     " "
+                                     #$socket-path
+                                     " "
+                                     "display-physical-interface-name"
+                                     " "
+                                     "physical-dev-rw"
+                                     ")"
+                                     " "
+                                     "-j"
+                                     " "
+                                     "MASQUERADE")))))))))
+
+(define (server-tun-actions config tun-int server-ip client-ip)
+  "Returns a list of shepherd actions for the whispers service
+reprensenting the tun device of a VPN server whose number is the integer
+TUN-INT, whose ip address is the string SERVER-IP and whose peer address
+is the string CLIENT-IP, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (let* ((tun-str (tun-dev-str tun-int))
+         (tun-sym (tun-dev-sym tun-int))
+         (ip-package (whispers-vpn-configuration-iproute-package config))
+         (ip-exec (file-append ip-package
+                               "/sbin/ip")))
+    (list (shepherd-action
+           (name 'start-tun)
+           (documentation (string-append "Start the TUN device service"
+                                         tun-str
+                                         "."))
+           (procedure
+            #~(lambda (running)
+                (start '#$tun-sym))))
+          (shepherd-action
+           (name 'stop-tun)
+           (documentation (string-append "Stop the TUN device service"
+                                         tun-str
+                                         "."))
+           (procedure
+            #~(lambda (running)
+                (stop-service (lookup-service '#$tun-sym)))))
+          (shepherd-action
+           (name 'pre-start)
+           (documentation (string-append "Create the TUN device service"
+                                         tun-str
+                                         "."))
+           (procedure
+            #~(lambda (running)
+                (fork+exec-command (list "/bin/sh"
+                                         "-c"
+                                         (string-append #$ip-exec
+                                                        " "
+                                                        "tuntap"
+                                                        " "
+                                                        "add"
+                                                        " "
+                                                        #$tun-str
+                                                        " "
+                                                        "mode"
+                                                        " "
+                                                        "tun"
+                                                        " "
+                                                        "user"
+                                                        " "
+                                                        "whispers"
+                                                        " "
+                                                        "group"
+                                                        " "
+                                                        "whispers"
+                                                        " "
+                                                        "&&"
+                                                        " "
+                                                        #$ip-exec
+                                                        " "
+                                                        "addr"
+                                                        " "
+                                                        "add"
+                                                        " "
+                                                        #$server-ip
+                                                        "/32"
+                                                        " "
+                                                        "peer"
+                                                        " "
+                                                        #$client-ip
+                                                        " "
+                                                        "dev"
+                                                        " "
+                                                        #$tun-str
+                                                        " "
+                                                        "&&"
+                                                        " "
+                                                        #$ip-exec
+                                                        " "
+                                                        "link"
+                                                        " "
+                                                        "set"
+                                                        " "
+                                                        #$tun-str
+                                                        " "
+                                                        "up"))))))
+          (shepherd-action
+           (name 'post-stop)
+           (documentation (string-append "Delete the TUN device service"
+                                         tun-str
+                                         "."))
+           (procedure
+            #~(lambda (running)
+                (fork+exec-command (list "/bin/sh"
+                                         "-c"
+                                         (string-append #$ip-exec
+                                                        " "
+                                                        "link"
+                                                        " "
+                                                        "set"
+                                                        " "
+                                                        #$tun-str
+                                                        " "
+                                                        "down"
+                                                        " "
+                                                        ";"
+                                                        " "
+                                                        #$ip-exec
+                                                        " "
+                                                        "addr"
+                                                        " "
+                                                        "del"
+                                                        " "
+                                                        #$server-ip
+                                                        "/32"
+                                                        " "
+                                                        "peer"
+                                                        " "
+                                                        #$client-ip
+                                                        " "
+                                                        "dev"
+                                                        " "
+                                                        #$tun-str
+                                                        " "
+                                                        ";"
+                                                        " "
+                                                        #$ip-exec
+                                                        " "
+                                                        "tuntap"
+                                                        " "
+                                                        "del"
+                                                        " "
+                                                        #$tun-str
+                                                        " "
+                                                        "mode"
+                                                        " "
+                                                        "tun")))))))))
+
+(define physical-dev-state-write-actions
+  (list (shepherd-action
+         (name 'burn-state)
+         (documentation "unload the existing instance of the
+'physical-dev-state service. Write a physical device state as a new
+service with the string PHY-DEV-NAME recorded in its
+'physical-interface-name action and the string ROUTER-IP recorded in its
+'physical-gateway-ip action.")
+         (procedure
+          #~(lambda (running phy-dev-name router-ip)
+              (perform-service-action (lookup-service 'root)
+                                      'unload
+                                      "physical-dev-state")
+              (display "Burning new 'physical-dev-state service.\n")
+              (register-services
+               (service
+                 '(physical-dev-state)
+                 #:start (lambda whatever #f)
+                 #:stop (lambda whatever #t)
+                 #:actions (list (action
+                                  'physical-interface-name
+                                  (lambda (running) phy-dev-name)
+                                  "Return the name of the physical)
+interface which supported
+the default route when
+the VPN was disconnected.")
+                                 (action
+                                  'physical-gateway-ip
+                                  (lambda (running) router-ip)
+                                  "Return the ip of the gateway
+of the physical interface
+which supported the default
+route when the VPN was
+disconnected."))
+                 #:one-shot? #t)))))))
+
+(define physical-dev-resolve-actions
+  (list (shepherd-action
+         (name 'display-physical-interface-name)
+         (documentation "For debugging purposes, display
+the name of the physical
+interface which supported
+the default route when
+the VPN was disconnected.")
+         (procedure
+          #~(lambda (running)
+              (display (perform-service-action (lookup-service
+                                                'physical-dev-rw)
+                                               'physical-interface-name)))))
+        (shepherd-action
+         (name 'physical-interface-name)
+         (documentation "Return the name of the physical
+interface which supported
+the default route when
+the VPN was disconnected.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'physical-dev-state)
+                                      'physical-interface-name))))
+        (shepherd-action
+         (name 'display-physical-gateway-ip)
+         (documentation "For debugging purposes, display the ip of the
+gateway of the physical interface
+which supported the default
+route when the VPN was
+disconnected.")
+         (procedure
+          #~(lambda (running)
+              (display (perform-service-action (lookup-service
+                                                'physical-dev-rw)
+                                               'physical-gateway-ip)))))
+        (shepherd-action
+         (name 'physical-gateway-ip)
+         (documentation "Return the ip of the gateway
+of the physical interface
+which supported the default
+route when the VPN was
+disconnected.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'physical-dev-state)
+                                      'physical-gateway-ip))))))
+
+(define (physical-dev-guessing-actions config)
+  "Return a list of shepherd actions used while the VPN is disconnected in
+order to guess the name of the default network interface that should
+support the VPN tunnel, and the IP address of its
+gateway. Afternatively, user configurations are used instead if the user
+has overriden the guessing by setting switches in CONFIG, a record of
+the <whispers-vpn-configuration> type."
+  (list (shepherd-action
+         (name 'guess-interface)
+         (documentation "Burn strings into the physical-dev-state
+service as the guesses of the name and gateway address of the physical
+interface which supports the default route when the VPN is
+disconnected. Then pass the hand to another shell through the VPN
+service to finish routing the connection.")
+         (procedure
+          (let* ((client-tun (whispers-vpn-configuration-client-tun-device
+                              config))
+                 (client-tun-str (tun-dev-str client-tun))
+                 (phy-override?
+                  (whispers-vpn-configuration-manual-physical-if? config))
+                 (phy-override
+                  (whispers-vpn-configuration-physical-if-override
+                   config))
+                 (ip-package (whispers-vpn-configuration-iproute-package
+                              config))
+                 (ip-exec (file-append ip-package
+                                       "/sbin/ip"))
+                 (sed-package (whispers-vpn-configuration-sed-package
+                               config))
+                 (sed-exec (file-append sed-package
+                                        "/bin/sed"))
+                 (herd-path "/run/current-system/profile/bin/herd")
+                 (socket-path "/run/whispers/vpn/unix-sockets/vpn.sock"))
+            #~(lambda (running)
+                (if #$phy-override?
+                    #$phy-override
+                    (fork+exec-command
+                     (list "/bin/sh"
+                           "-c"
+                           (string-append #$herd-path
+                                          " "
+                                          "-s"
+                                          " "
+                                          #$socket-path
+                                          " "
+                                          "burn-state"
+                                          " "
+                                          "physical-dev-rw"
+                                          " "
+                                          "$("
+                                          #$ip-exec
+                                          " route | "
+                                          #$sed-exec
+                                          " -n '/^default/ "
+                                          "s=.*dev \\(.*\\)$=\\1=p'"
+                                          ") $("
+                                          #$ip-exec
+                                          " route | "
+                                          #$sed-exec
+                                          " -n '/^default/ "
+                                          "s=.*via "
+                                          "\\([^ ]\\+\\).*$=\\1=p'"
+                                          ")"
+                                          " "
+                                          "&&"
+                                          " "
+                                          #$herd-path
+                                          " "
+                                          "-s"
+                                          " "
+                                          #$socket-path
+                                          " "
+                                          "tun-route"
+                                          " "
+                                          #$client-tun-str))))))))))
+
+(define (client-tun-actions config)
+  "Returns a list of shepherd actions for the whispers service
+reprensenting the tun device of a connected client, configurable by
+CONFIG, a record of the <whispers-vpn-configuration> type."
+  (let* ((client-tun (whispers-vpn-configuration-client-tun-device config))
+         (client-tun-str (tun-dev-str client-tun))
+         (client-tun-sym (tun-dev-sym client-tun))
+         (ip-package (whispers-vpn-configuration-iproute-package config))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (proxy-ip (whispers-vpn-configuration-proxy-sshd-host config))
+         (server-ip (whispers-vpn-configuration-server-sshd-host
+                     config))
+         (via-phy (if stealth?
+                      proxy-ip
+                      server-ip))
+         (phys-if #~(perform-service-action (lookup-service 'physical-dev-rw)
+                                            'physical-interface-name))
+         (gate #~(perform-service-action (lookup-service 'physical-dev-rw)
+                                         'physical-gateway-ip))
+         (ip-exec (file-append ip-package
+                               "/sbin/ip")))
+    (list (shepherd-action
+           (name 'start-tun)
+           (documentation (string-append "Start the TUN device "
+                                         client-tun-str
+                                         "."))
+           (procedure
+            #~(lambda (running)
+                (start '#$client-tun-sym))))
+          (shepherd-action
+           (name 'stop-tun)
+           (documentation (string-append "Stop the TUN device "
+                                         client-tun-str
+                                         "."))
+           (procedure
+            #~(lambda (running)
+                (stop-service (lookup-service '#$client-tun-sym)))))
+          (shepherd-action
+           (name 'tun-route)
+           (documentation "Establish network address and routing rules
+for a newly connected client.")
+           (procedure
+            #~(lambda (running)
+                (let* ((client-ip (perform-service-action (lookup-service
+                                                          'network-rw)
+                                                         'hostname->ip
+                                                         (gethostname)))
+                       (server-ip (perform-service-action (lookup-service
+                                                          'network-rw)
+                                                         'hostname->server-ip
+                                                         (gethostname)))
+                       (server-ip-stripped
+                        (regexp-substitute
+                         #f
+                         (string-match "([0-9]+\\.[0-9]+\\.[0-9]+)\\.[0-9]+"
+                                       server-ip)
+                         1
+                         ".0")))
+                  (fork+exec-command
+                   (list "/bin/sh"
+                         "-c"
+                         (string-append #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "save"
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "addr"
+                                        " "
+                                        "add"
+                                        " "
+                                        client-ip
+                                        " "
+                                        "peer"
+                                        " "
+                                        server-ip
+                                        " "
+                                        "dev"
+                                        " "
+                                        #$client-tun-str
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "link"
+                                        " "
+                                        "set"
+                                        " "
+                                        #$client-tun-str
+                                        " "
+                                        "up"
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "del"
+                                        " "
+                                        "default"
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "add"
+                                        " "
+                                        #$via-phy
+                                        " "
+                                        "via"
+                                        " "
+                                        #$gate
+                                        " "
+                                        "dev"
+                                        " "
+                                        #$phys-if
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "add"
+                                        " "
+                                        server-ip-stripped
+                                        "/24"
+                                        " "
+                                        "via"
+                                        " "
+                                        server-ip
+                                        " "
+                                        "dev"
+                                        " "
+                                        #$client-tun-str
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "add"
+                                        " "
+                                        "default"
+                                        " "
+                                        "via"
+                                        " "
+                                        server-ip
+                                        " "
+                                        "dev"
+                                        " "
+                                        #$client-tun-str)))))))
+          (shepherd-action
+           (name 'pre-start)
+           (documentation "Burn relevant data about the physical network
+interface, then establish network address and routing rules for a newly
+connected client.")
+           (procedure
+            #~(lambda (running)
+                (perform-service-action (lookup-service 'physical-dev-rw)
+                                          'guess-interface))))
+          (shepherd-action
+           (name 'post-stop)
+           (documentation "Restore default routing to its previous
+pre-connection state.")
+           (procedure
+            #~(lambda (running)
+                (let ((client-ip (perform-service-action (lookup-service
+                                                          'network-rw)
+                                                         'hostname->ip
+                                                         (gethostname)))
+                      (server-ip (perform-service-action (lookup-service
+                                                          'network-rw)
+                                                         'hostname->server-ip
+                                                         (gethostname))))
+                  (fork+exec-command
+                   (list "/bin/sh"
+                         "-c"
+                         (string-append
+                          ;; #$ip-exec
+                          ;;               " "
+                          ;;               "link"
+                          ;;               " "
+                          ;;               "set"
+                          ;;               " "
+                          ;;               #$client-tun-str
+                          ;;               " "
+                          ;;               "down"
+                          ;;               " "
+                          ;;               "&&"
+                          ;;               " "
+                          ;;               #$ip-exec
+                          ;;               " "
+                          ;;               "addr"
+                          ;;               " "
+                          ;;               "del"
+                          ;;               " "
+                          ;;               client-ip
+                          ;;               " "
+                          ;;               "peer"
+                          ;;               " "
+                          ;;               server-ip
+                          ;;               " "
+                          ;;               "dev"
+                          ;;               " "
+                          ;;               #$client-tun-str
+                          ;;               " "
+                          ;;               "&&"
+                          ;;               " "
+                                        ;; #$ip-exec
+                                        ;; " "
+                                        ;; "route"
+                                        ;; " "
+                                        ;; "del"
+                                        ;; " "
+                                        ;; "default"
+                                        ;; " "
+                                        ;; "&&"
+                                        ;; " "
+                                        #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "del"
+                                        " "
+                                        #$via-phy
+                                        " "
+                                        "via"
+                                        " "
+                                        #$gate
+                                        " "
+                                        "dev"
+                                        " "
+                                        #$phys-if
+                                        " "
+                                        "&&"
+                                        " "
+                                        #$ip-exec
+                                        " "
+                                        "route"
+                                        " "
+                                        "add"
+                                        " "
+                                        "default"
+                                        " "
+                                        "via"
+                                        " "
+                                        #$gate
+                                        " "
+                                        "dev"
+                                        " "
+                                        #$phys-if))))))))))
+
+(define (tcp-ip-actions config)
+  "Returns a list of shepherd actions for the lieutenants of the 'vpn
+service handling tun devices, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (let* ((client? (whispers-vpn-configuration-client? config))
+         (empty-net (empty-network (whispers-vpn-configuration-constants
+                                    config)))
+         (client-tun (whispers-vpn-configuration-client-tun-device config))
+         (client-tun-sym (tun-dev-sym client-tun))
+         (client-tun-str (symbol->string client-tun-sym))
+         (voucher->tun-int (lambda (voucher)
+                             (cadar (filter (lambda (field)
+                                              (equal? (car field)
+                                                      'tun-device-number))
+                                            voucher))))
+         (voucher->tun-str (lambda (voucher)
+                             (tun-dev-str (voucher->tun-int voucher))))
+         (masquerade? (whispers-vpn-configuration-masquerade? config)))
+    (if client?
+        (list (shepherd-action
+               (name (string->symbol (string-append "start-"
+                                                    client-tun-str)))
+               (documentation (string-append "Start the client-side TUN
+interface "
+                                             client-tun-str
+                                             "."))
+               (procedure
+                #~(lambda (running)
+                    (perform-service-action
+                     (lookup-service 'vpn)
+                     'lieutenant-action
+                     "start-tun"
+                     #$client-tun-str))))
+              (shepherd-action
+               (name (string->symbol
+                      (string-append "stop-"
+                                     client-tun-str)))
+               (documentation (string-append "Stop the client-side TUN
+interface "
+                                             client-tun-str
+                                             "."))
+               (procedure
+                #~(lambda (running)
+                    (perform-service-action
+                     (lookup-service 'vpn)
+                     'lieutenant-action
+                     "stop-tun"
+                     #$client-tun-str)))))
+        (append
+         (map (lambda (voucher)
+                (shepherd-action
+                 (name (string->symbol
+                        (string-append "start-"
+                                       (voucher->tun-str voucher))))
+                 (documentation (string-append "Start the server-side
+TUN interface "
+                                               (voucher->tun-str voucher)
+                                               "."))
+                 (procedure
+                  #~(lambda (running)
+                      (perform-service-action
+                       (lookup-service '#$(string->symbol
+                                           (voucher->tun-str voucher)))
+                       'start-tun)))))
+              empty-net)
+         (map (lambda (voucher)
+                (shepherd-action
+                 (name (string->symbol
+                        (string-append "stop-"
+                                       (voucher->tun-str voucher))))
+                 (documentation (string-append "Stop the server-side TUN
+interface "
+                                               (voucher->tun-str voucher)
+                                               "."))
+                 (procedure
+                  #~(lambda (running)
+                      (perform-service-action
+                       (lookup-service '#$(string->symbol
+                                           (voucher->tun-str voucher))))
+                      'stop-tun))))
+              empty-net)))))
+
+(define (physical-dev-rw-shepherd-services config)
+  "Returns a list of one <shepherd-service> object providing actions to
+infer, read and write the physical interface device state stored in the
+'physical-dev-state service in its scope, configurable by CONFIG, a
+record of the <whishpers-vpn-configuration> type.  Data about the
+physical interface default route is retrievable from this service to
+support the establishement of interface addresses and routing rules
+during connection and disconnection of a client from the VPN."
+  (list (shepherd-service
+         (documentation "Physical-Dev state read and write operations.")
+         (provision '(physical-dev-rw))
+         (requirement '())
+         (start #~(lambda config #f))
+         (actions (append physical-dev-state-write-actions
+                          physical-dev-resolve-actions
+                          (physical-dev-guessing-actions config)))
+         (stop #~(lambda config #t))
+         (one-shot? #t)
+         (auto-start? #f))))
+
+(define physical-dev-rw-service-type
+  (service-type
+   (name 'physical-dev-rw)
+   (description "Shepherd service used for read and write operations of
+the physical-dev state of a Whispers VPN.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             physical-dev-rw-shepherd-services)))
+   (default-value (whispers-vpn-configuration))))
+
+(define (physical-dev-state-shepherd-services whatever)
+  "Returns a list of one <shepherd-service> object doing nothing of
+interest in the state in which it is instanciated at guix's system
+reconfigure time, not configurable by WHATEVER. While a VPN client will
+be connecting at the OS run-time, the characteristics of the physical
+network interface will be inferred and burned into a modified
+re-instanciated version of this service."
+  (list (shepherd-service
+         (documentation "Queryable physical-dev state of a Whispers VPN.")
+         (provision '(physical-dev-state))
+         (requirement '())
+         (start #~(lambda whatever #f))
+         (actions
+          (list (shepherd-action
+                 (name 'physical-interface-name)
+                 (documentation "Does not return anything of interest at
+this time.")
+                 (procedure #~(lambda (running) "Unknown.")))
+                (shepherd-action
+                 (name 'physical-gateway-ip)
+                 (documentation "Does not return anything of interest at
+this time.")
+                 (procedure #~(lambda (running) "Unknown.")))))
+         (stop #~(lambda whatever #t))
+         (one-shot? #t)
+         (auto-start? #f))))
+
+(define physical-dev-state-service-type
+  (service-type
+   (name 'physical-dev-state)
+   (description "Shepherd service used for storage and retrieval of the
+physical-dev state of a Whispers VPN.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             physical-dev-state-shepherd-services)))
+   (default-value 'whatever)))
+
+(define (server-tcp-ip-lieutenant voucher config)
+  "Returns a whispers lieutenant handling a VPN-server-side tun device
+as defined by the voucher data VOUCHER, configurable by CONFIG, a record
+of the <whispers-vpn-configuration> type."
+  (let* ((tun-int (cadar (filter (lambda (field)
+                                   (equal? (car field)
+                                           'tun-device-number))
+                                 voucher)))
+         (server-ip (cadar (filter (lambda (field)
+                                     (equal? (car field)
+                                             'server-ip))
+                                   voucher)))
+         (client-ip (cadar (filter (lambda (field)
+                                     (equal? (car field)
+                                             'client-ip))
+                                   voucher)))
+         (tun-str (tun-dev-str tun-int))
+         (tun-sym (tun-dev-sym tun-int))
+         (masquerade? (whispers-vpn-configuration-masquerade? config))
+         (ipv4-forw? (whispers-vpn-configuration-ipv4-ip-forward? config)))
+    (service whispers-service-type
+             (whispers-configuration
+              (name tun-sym)
+              (requires (append (if masquerade?
+                                    '(masquerade)
+                                    '())
+                                (if ipv4-forw?
+                                    '(ipv4-ip-forward)
+                                    '())))
+              (extra-packages (list iproute
+                                    iptables))
+              (pre-start-action? #t)
+              (post-stop-action? #t)
+              (extra-actions (server-tun-actions config
+                                                 tun-int
+                                                 server-ip
+                                                 client-ip))
+              (%auto-start? #f)))))
+
+(define (tcp-ip-lieutenants config)
+  "Returns a list of lieutenants of the 'vpn service for handling the hair
+around tun devices, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (let* ((client? (whispers-vpn-configuration-client? config))
+         (client-tun (whispers-vpn-configuration-client-tun-device config))
+         (client-dev (tun-dev-sym client-tun))
+         (constants (whispers-vpn-configuration-constants config)))
+    (append (list (service physical-dev-rw-service-type config)
+                  (service physical-dev-state-service-type 'whatever))
+            (if client?
+                (list
+                 (service whispers-service-type
+                          (whispers-configuration
+                           (name client-dev)
+                           (requires '(registered))
+                           (pre-start-action? #t)
+                           (post-stop-action? #t)
+                           (extra-actions (client-tun-actions config))
+                           (%auto-start? #f))))
+                (append (map (lambda (voucher)
+                               (server-tcp-ip-lieutenant voucher config))
+                             (empty-network constants))
+                        (list (service whispers-service-type
+                                       (whispers-configuration
+                                        (name 'masquerade)
+                                        (requires '())
+                                        (pre-start-action? #t)
+                                        (post-stop-action? #t)
+                                        (extra-actions (masquerade-actions
+                                                        config))
+                                        (%auto-start? #f)))
+                              (service whispers-service-type
+                                       (whispers-configuration
+                                        (name 'ipv4-ip-forward)
+                                        (requires '())
+                                        (pre-start-action? #t)
+                                        (post-stop-action? #t)
+                                        (extra-actions (ipv4-forward-actions
+                                                        config))
+                                        (%auto-start? #f)))))))))
+
+(define want-connect-state-write-actions
+  (list (shepherd-action
+         (name 'want-connect-on)
+         (documentation "Activate the connection status wish of a VPN
+client by burning a new 'want-connect-state service with a #t action
+return value.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'want-connect-rw)
+                                      'burn-state
+                                      #t))))
+        (shepherd-action
+         (name 'want-connect-off)
+         (documentation "Disactivate the connection status wish of a VPN
+client by burning a new 'want-connect-state service with a #f action
+return value.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'want-connect-rw)
+                                      'burn-state
+                                      #f))))
+        (shepherd-action
+         (name 'burn-state)
+         (documentation "unload the existing instance of the
+'want-connect-state service. Write a connection status wish as a new
+service with the boolean value WANT? recorded in its 'want-connect?
+action.")
+         (procedure
+          #~(lambda (running want?)
+              (when (lookup-service 'want-connect-state)
+                (perform-service-action (lookup-service 'root)
+                                        'unload
+                                        "want-connect-state"))
+              (display "Burning new 'want-connect-state service.\n")
+              (register-services
+               (service
+                 '(want-connect-state)
+                 #:start (lambda whatever #f)
+                 #:stop (lambda whatever #t)
+                 #:actions (list (action
+                                  'want-connect?
+                                  (lambda (running) want?)
+                                  "Return the connection status wish of a
+VPN client. The returned boolean value is used only at the end of client
+registration, to determine if connection is to be performed
+auto-immedately."))
+                 #:one-shot? #t)))))))
+
+(define want-connect-resolve-actions
+  (list (shepherd-action
+         (name 'display-want-connect?)
+         (documentation "For debugging purposes, display the connection
+status wish of a VPN client.")
+         (procedure
+          #~(lambda (running)
+              (display (perform-service-action (lookup-service
+                                                'want-connect-rw)
+                                               'want-connect?)))))
+        (shepherd-action
+         (name 'want-connect?)
+         (documentation "Return the connection status wish of a
+VPN client.The returned boolean value is used only at the end of client
+registration, to determine if connection is to be performed
+auto-immedately.")
+         (procedure
+          #~(lambda (running)
+              (if (lookup-service 'want-connect-state)
+                  (perform-service-action (lookup-service
+                                           'want-connect-state)
+                                          'want-connect?)
+                  #f))))))
+
+(define voucher-resolve-actions
+  (list
+   (shepherd-action
+    (name 'field-value->voucher)
+    (documentation "Returns a voucher whose field symbol FIELD has the
+string value VALUE, #f if no voucher is found.")
+    (procedure
+     #~(lambda (running field value)
+         ((lambda (lst) (if (null? lst) #f (car lst)))
+          (filter (lambda (voucher)
+                    (and (not (null?
+                               (filter (lambda (entry)
+                                         (and (equal? (car entry)
+                                                      field)
+                                              (equal? (cadr entry)
+                                                      value)))
+                                       voucher)))))
+                  (perform-service-action (lookup-service 'network-rw)
+                                          'network-state))))))
+   (shepherd-action
+    (name 'voucher-field->value)
+    (documentation "Returns the string value of the field symbol FIELD
+of the voucher list of pairs VOUCHER, or #f if the voucher has no such
+field.")
+    (procedure
+     #~(lambda (running field voucher)
+         ((lambda (lst)
+            (if (null? lst)
+                #f
+                (cadr (car lst)))) (filter (lambda (entry)
+                                             (equal? (car entry)
+                                                     field))
+                                           voucher)))))
+   (shepherd-action
+    (name 'display-unknown-host?)
+    (documentation "For debugging purposes, display #f if the host named
+by the string HOSTNAME has a booked vouched, #t otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (display (perform-service-action (lookup-service 'network-rw)
+                                          'unknown-host?
+                                          hostname)))))
+   (shepherd-action
+    (name 'unknown-host?)
+    (documentation "Returns #f if the host named by the string HOSTNAME
+has a booked vouched, #t otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (not (perform-service-action (lookup-service 'network-rw)
+                                      'field-value->voucher
+                                      'client-hostname
+                                      hostname)))))
+   (shepherd-action
+    (name 'display-hostname->tun-request?)
+    (documentation "For debugging purposes, display a predicate true if
+the client named by the string HOSTNAME is a known connected client
+according to the current network state.")
+    (procedure
+     #~(lambda (running hostname)
+         (display (car (perform-service-action (lookup-service 'network-rw)
+                                               'hostname->tun-request?
+                                               hostname))))))
+   (shepherd-action
+    (name 'hostname->tun-request?)
+    (documentation "Returns a predicate true if the client named by the
+string HOSTNAME is a known connected client according to the current
+network state.")
+    (procedure
+     #~(lambda (running hostname)
+         (unless (perform-service-action (lookup-service 'network-rw)
+                                         'unknown-host?
+                                         hostname)
+           (display "known host voucher: ")
+           (display (perform-service-action (lookup-service 'network-rw)
+                                            'field-value->voucher
+                                            'client-hostname
+                                            hostname))
+           (display "\n")
+           (perform-service-action (lookup-service 'network-rw)
+                                   'voucher-field->value
+                                   'tun-request?
+                                   (perform-service-action
+                                    (lookup-service
+                                     'network-rw)
+                                    'field-value->voucher
+                                    'client-hostname
+                                    hostname))))))
+   (shepherd-action
+    (name 'display-hostname->connected?)
+    (documentation "For debugging purposes, display a predicate true if
+the client named by the string HOSTNAME is a known connected client
+according to the current network state.")
+    (procedure
+     #~(lambda (running hostname)
+         (display (perform-service-action (lookup-service 'network-rw)
+                                          'hostname->connected?
+                                          hostname)))))
+   (shepherd-action
+    (name 'hostname->connected?)
+    (documentation "Returns a predicate true if the client named by the
+string HOSTNAME is a known connected client according to the current
+network state.")
+    (procedure
+     #~(lambda (running hostname)
+         (unless (perform-service-action (lookup-service 'network-rw)
+                                         'unknown-host?
+                                         hostname)
+           (display "known host voucher: ")
+           (display (perform-service-action (lookup-service 'network-rw)
+                                            'field-value->voucher
+                                            'client-hostname
+                                            hostname))
+           (display "\n")
+           (perform-service-action (lookup-service 'network-rw)
+                                   'voucher-field->value
+                                   'connected?
+                                   (perform-service-action
+                                    (lookup-service 'network-rw)
+                                    'field-value->voucher
+                                    'client-hostname
+                                    hostname))))))
+   (shepherd-action
+    (name 'display-hostname->ip)
+    (documentation "Display the client IP address of the host HOSTNAME if
+it has booked a voucher, #f otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (display (perform-service-action (lookup-service 'network-rw)
+                                          'hostname->ip
+                                          hostname)))))
+   (shepherd-action
+    (name 'hostname->ip)
+    (documentation "Returns the client IP address of the host HOSTNAME if
+it has booked a voucher, #f otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             #f
+             (perform-service-action (lookup-service 'network-rw)
+                                     'voucher-field->value
+                                     'client-ip
+                                     (perform-service-action
+                                      (lookup-service
+                                       'network-rw)
+                                      'field-value->voucher
+                                      'client-hostname
+                                      hostname))))))
+   (shepherd-action
+    (name 'display-hostname->server-ip)
+    (documentation "Display the server IP address of the host HOSTNAME if
+it has booked a voucher, #f otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (display (perform-service-action (lookup-service 'network-rw)
+                                          'hostname->server-ip
+                                          hostname)))))
+   (shepherd-action
+    (name 'hostname->server-ip)
+    (documentation "Returns the server IP address of the host HOSTNAME if
+it has booked a voucher, #f otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             #f
+             (perform-service-action (lookup-service 'network-rw)
+                                     'voucher-field->value
+                                     'server-ip
+                                     (perform-service-action
+                                      (lookup-service
+                                       'network-rw)
+                                      'field-value->voucher
+                                      'client-hostname
+                                      hostname))))))
+   (shepherd-action
+    (name 'hostname->port)
+    (documentation "Returns the port number of the reverse port
+forwarding open to the ssh daemon of the host HOSTNAME if it has booked
+a voucher, #f otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             #f
+             (perform-service-action (lookup-service 'network-rw)
+                                     'voucher-field->value
+                                     'reverse-port
+                                     (perform-service-action
+                                      (lookup-service
+                                       'network-rw)
+                                      'field-value->voucher
+                                      'client-hostname
+                                      hostname))))))
+   (shepherd-action
+    (name 'hostname->tun)
+    (documentation "Returns the tun device number of the tunnel forward
+opened by the ssh daemon of the host HOSTNAME if it has booked a
+voucher, #f otherwise.")
+    (procedure
+     #~(lambda (running hostname)
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             #f
+             (perform-service-action (lookup-service 'network-rw)
+                                     'voucher-field->value
+                                     'tun-device-number
+                                     (perform-service-action
+                                      (lookup-service
+                                       'network-rw)
+                                      'field-value->voucher
+                                      'client-hostname
+                                      hostname))))))))
+
+(define (client->server-actions config)
+  "Returns a list of <shepherd-action> records defining actions that the
+VPN clients use to execute commands as the unpriviledged whispers user
+of the server, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (list
+   (shepherd-action
+    (name 'set-disconnected-knock)
+    (documentation "Set the 'connected? field of the local client's
+voucher to #f in the server's network states and knock-knock the
+server. The server then propagates to the whole network.")
+    (procedure
+     (let* ((herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (unpriv-sock (string-append
+                          base-socket-path
+                          "/unpriviledged/unix-sockets/unpriviledged.sock")))
+       #~(lambda (running)
+           (perform-service-action (lookup-service 'network-rw)
+                                   'server-command-knock
+                                   (string-append #$herd-path
+                                                  " "
+                                                  "-s"
+                                                  " "
+                                                  #$unpriv-sock
+                                                  " "
+                                                  "set-disconnected"
+                                                  " "
+                                                  "network-rw"
+                                                  " "
+                                                  (gethostname)))))))
+   (shepherd-action
+    (name 'set-connected-knock)
+    (documentation "Set the 'connected? field of the local client's
+voucher to #t in the server's unpriviledged network state, set the
+'tun-request? field of the local client's voucher to #f in the server's
+unpriviledged network state, and knock-knock the server. The server then
+propagates to the whole network.")
+    (procedure
+     (let* ((herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (unpriv-sock (string-append
+                          base-socket-path
+                          "/unpriviledged/unix-sockets/unpriviledged.sock")))
+       #~(lambda (running)
+           (perform-service-action (lookup-service 'network-rw)
+                                   'server-command-knock
+                                   (string-append #$herd-path
+                                                  " "
+                                                  "-s"
+                                                  " "
+                                                  #$unpriv-sock
+                                                  " "
+                                                  "set-connected"
+                                                  " "
+                                                  "network-rw"
+                                                  " "
+                                                  (gethostname)
+                                                  " "
+                                                  "&&"
+                                                  " "
+                                                  #$herd-path
+                                                  " "
+                                                  "-s"
+                                                  " "
+                                                  #$unpriv-sock
+                                                  " "
+                                                  "unset-tun-request"
+                                                  " "
+                                                  "network-rw"
+                                                  " "
+                                                  (gethostname)))))))
+   (shepherd-action
+    (name 'set-tun-request-knock)
+    (documentation "Set the 'tun-request? field of the local client's
+voucher to #t in the server's network states, then knock-knock the
+server. The server then creates a TUN interface for the local client.")
+    (procedure
+     (let* ((herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (unpriv-sock (string-append
+                          base-socket-path
+                          "/unpriviledged/unix-sockets/unpriviledged.sock")))
+       #~(lambda (running)
+           (perform-service-action (lookup-service 'network-rw)
+                                   'server-command-knock
+                                   (string-append #$herd-path
+                                                  " "
+                                                  "-s"
+                                                  " "
+                                                  #$unpriv-sock
+                                                  " "
+                                                  "set-tun-request"
+                                                  " "
+                                                  "network-rw"
+                                                  " "
+                                                  (gethostname)))))))
+   (shepherd-action
+    (name 'server-command-knock)
+    (documentation "Execute the string COMMAND as a shell command as the
+unpriviledges whispers user of the server, then knock-knock the server.")
+    (procedure
+     (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+            (forward-port (whispers-vpn-configuration-forward-port config))
+            (forward-port-str (number->string forward-port))
+            (herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (knocker-socket (string-append
+                             base-socket-path
+                             "/knocker/unix-sockets/knocker.sock")))
+       #~(lambda (running command)
+           (fork+exec-command (list #$(file-append ssh-package
+                                                   "/bin/ssh")
+                                    "-o"
+                                    "StrictHostKeyChecking=no"
+                                    "-o"
+                                    "UserKnownHostsFile=/dev/null"
+                                    "-p"
+                                    #$forward-port-str
+                                    "whispers@localhost"
+                                    (string-append command
+                                                   " "
+                                                   "&&"
+                                                   " "
+                                                   #$herd-path
+                                                   " "
+                                                   "-s"
+                                                   " "
+                                                   #$knocker-socket
+                                                   " "
+                                                   "stop"
+                                                   " "
+                                                   "root")))))))
+   (shepherd-action
+    (name 'book)
+    (documentation "Book a voucher for this client's hostname in the
+server's unpriviledge network state, then knock-knock the server to
+propagate to the whole network.")
+    (procedure
+     (let* ((herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (unpriv-sock (string-append
+                          base-socket-path
+                          "/unpriviledged/unix-sockets/unpriviledged.sock")))
+       #~(lambda (running)
+           (perform-service-action (lookup-service 'network-rw)
+                                   'server-command-knock
+                                   (string-append #$herd-path
+                                                  " "
+                                                  "-s"
+                                                  " "
+                                                  #$unpriv-sock
+                                                  " "
+                                                  "book-client"
+                                                  " "
+                                                  "network-rw"
+                                                  " "
+                                                  (gethostname)))))))
+   (shepherd-action
+    (name 'free)
+    (documentation "Free this client's hostname voucher in the server's
+unpriviledge network state, then knock-knock the server to propagate to
+the whole network.")
+    (procedure
+     (let* ((herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (unpriv-sock (string-append
+                          base-socket-path
+                          "/unpriviledged/unix-sockets/unpriviledged.sock")))
+       #~(lambda (running)
+           (perform-service-action (lookup-service 'network-rw)
+                                   'server-command-knock
+                                   (string-append #$herd-path
+                                                  " "
+                                                  "-s"
+                                                  " "
+                                                  #$unpriv-sock
+                                                  " "
+                                                  "free-client-booking"
+                                                  " "
+                                                  "network-rw"
+                                                  " "
+                                                  (gethostname)))))))
+   (shepherd-action
+    (name 'complete-handshake)
+    (documentation "Complete a handshake with the VPN server by
+registering, unregistering or disconnecting this machine as a client.")
+    (procedure
+     (let* ((herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (vpn-sock (string-append
+                       base-socket-path
+                       "/unix-sockets/vpn.sock")))
+       #~(lambda (running)
+           (cond ((service-running? (lookup-service 'registering))
+                  (perform-service-action (lookup-service 'network-rw)
+                                          'book))
+                 ((service-running? (lookup-service 'unregistering))
+                  (perform-service-action (lookup-service 'network-rw)
+                                          'free))
+                 ((service-running? (lookup-service 'disconnecting))
+                  (fork+exec-command (list #$herd-path
+                                           "-s"
+                                           #$vpn-sock
+                                           "set-disconnected-knock"
+                                           "network-rw"))))))))))
+
+(define (server->client-actions config)
+  "Returns a list of <shepherd-action> records defining actions that the
+VPN server uses to execute commands as the unpriviledged whispers user
+of its clients, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (list
+   (shepherd-action
+    (name 'knock-knock-client)
+    (documentation "Duplicate the server network state in the
+unpriviledged vpn/unpriviledged Whispers lieutenant the client defined
+by the string CLIENT-SPEC, then knock-knock this client.  CLIENT-SPEC is
+parsed as follows:
+- When CLIENT-SPEC is the string \"in-handshake\", the client is
+whichever provisional client has grabbed the handshake port of the
+server. Presumably this client has just booked a voucher in the server's
+network state.
+- When CLIENT-SPEC is a string of the form \"client/HOSTNAME\", the
+client is the connected client whose hostname is HOSTNAME.
+- Other CLIENT-SPEC strings have no effect")
+    (procedure
+     (let* ((ssh-package (whispers-vpn-configuration-ssh-package config))
+            (constants (whispers-vpn-configuration-constants config))
+            (handshake-port (network-constants-handshake-port constants))
+            (handshake-port-str (number->string handshake-port))
+            (herd-path "/run/current-system/profile/bin/herd")
+            (base-socket-path "/run/whispers/vpn")
+            (unpriv-sock (string-append
+                          base-socket-path
+                          "/unpriviledged/unix-sockets/unpriviledged.sock"))
+            (knocker-socket (string-append
+                             base-socket-path
+                             "/knocker/unix-sockets/knocker.sock")))
+       #~(lambda (running client-spec)
+           (let ((client-reverse-port
+                  (cond ((equal? client-spec "in-handshake")
+                         #$handshake-port-str)
+                        ((equal? (substring client-spec 0 7)
+                                 "client/")
+                         (number->string (perform-service-action
+                                          (lookup-service
+                                           'network-rw)
+                                          'hostname->port
+                                          (substring client-spec
+                                                     7))))
+                        (#t "ssh-please-fail"))))
+             (let ((new-state (perform-service-action (lookup-service
+                                                       'network-rw)
+                                                      'print-network-state)))
+               (fork+exec-command
+                (list #$(file-append ssh-package
+                                     "/bin/ssh")
+                      "-p"
+                      client-reverse-port
+                      "-o"
+                      "StrictHostKeyChecking=no"
+                      "-o"
+                      "UserKnownHostsFile=/dev/null"
+                      "whispers@localhost"
+                      (string-append #$herd-path
+                                     " "
+                                     "-s"
+                                     " "
+                                     #$unpriv-sock
+                                     " "
+                                     "read-network-state"
+                                     " "
+                                     "network-rw"
+                                     " "
+                                     "'"
+                                     new-state
+                                     "'"
+                                     " "
+                                     "&&"
+                                     " "
+                                     #$herd-path
+                                     " "
+                                     "-s"
+                                     " "
+                                     #$knocker-socket
+                                     " "
+                                     "stop"
+                                     " "
+                                     "root")))))))))
+   (shepherd-action
+    (name 'knock-knock-connected-clients)
+    (documentation "From the server, knock-knock all registered and
+connected clients.")
+    (procedure
+     #~(lambda (running)
+         (map (lambda (voucher)
+                (when (and (perform-service-action (lookup-service
+                                                    'network-rw)
+                                                   'voucher-field->value
+                                                   'client-hostname
+                                                   voucher)
+                           (perform-service-action (lookup-service
+                                                    'network-rw)
+                                                   'hostname->connected?
+                                                   (perform-service-action
+                                                    (lookup-service
+                                                     'network-rw)
+                                                    'voucher-field->value
+                                                    'client-hostname
+                                                    voucher)))
+                  (let ((client-host (perform-service-action
+                                      (lookup-service
+                                       'network-rw)
+                                      'voucher-field->value
+                                      'client-hostname
+                                      voucher)))
+                    (display (string-append "Client "
+                                            client-host
+                                            " is connected, "
+                                            "knock-knock.\n"))
+                    (perform-service-action (lookup-service 'network-rw)
+                                            'knock-knock-client
+                                            (string-append "client/"
+                                                           client-host)))))
+              (perform-service-action (lookup-service 'network-rw)
+                                      'network-state)))))
+   (shepherd-action
+    (name 'knock-knock-clients)
+    (documentation "From the server, knock-knock the whole accessible
+client network from. First knock-knock a prospective client performing a
+handshake, then knock-knock all registered and connected clients.")
+    (procedure
+     #~(lambda (running)
+         (display "knock-knock into the handshake reverse forward.\n")
+         (perform-service-action (lookup-service 'network-rw)
+                                 'knock-knock-client
+                                 "in-handshake")
+         (display "knock-knock all connected clients.\n")
+         (perform-service-action (lookup-service 'network-rw)
+                                 'knock-knock-connected-clients))))))
+
+(define voucher-actions
+  (list
+   (shepherd-action
+    (name 'book-lowest-voucher)
+    (documentation "Return a list of vouchers in which the string
+HOSTNAME has been booked the lowest voucher-number available voucher in
+the voucher list VOUCHERS. Using this action interactively can result in
+the same client being booked twice.")
+    (procedure
+     #~(lambda (running hostname vouchers)
+         (if (not (null? ((lambda (voucher)
+                            (filter (lambda (entry)
+                                      (and (equal? (car entry)
+                                                   'client-hostname)
+                                           (equal? (cadr entry)
+                                                   #f)))
+                                    voucher)) (car vouchers))))
+             (cons (map (lambda (entry)
+                          (if (equal? (car entry)
+                                      'client-hostname)
+                              `(client-hostname ,hostname)
+                              entry))
+                        (car vouchers))
+                   (cdr vouchers))
+             (cons (car vouchers)
+                   (perform-service-action (lookup-service 'network-rw)
+                                           'book-lowest-voucher
+                                           hostname
+                                           (cdr vouchers)))))))
+   (shepherd-action
+    (name 'add-unbooked-client)
+    (documentation "Return VOUCHERS if the string HOSTNAME is already
+booked in the list of vouchers VOUCHERS. Otherwise, return a list of
+vouchers in which HOSTNAME has been booked the lowest voucher-number
+available voucher in VOUCHERS.")
+    (procedure
+     #~(lambda (running hostname vouchers)
+         (display "Client: ")
+         (display hostname)
+         (display " is requesting a new voucher.\n")
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             (begin (display (string-append
+                              "Booking a new voucher for client "
+                              hostname
+                              ".\n"))
+                    (perform-service-action (lookup-service 'network-rw)
+                                            'book-lowest-voucher
+                                            hostname
+                                            vouchers))
+             (begin (display (string-append
+                              "Client "
+                              hostname
+                              " already booked, not booking.\n"))
+                    vouchers)))))
+   (shepherd-action
+    (name 'book-client)
+    (documentation "Book a new client by burning a new list of vouchers
+into a new 'network-state service, where a new voucher has been booked
+to HOSTNAME if HOSTNAME is not already booked.")
+    (procedure
+     #~(lambda (running hostname)
+         (perform-service-action (lookup-service 'network-rw)
+                                 'burn-state
+                                 (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'add-unbooked-client
+                                  hostname
+                                  (perform-service-action
+                                   (lookup-service 'network-rw)
+                                   'network-state))))))
+   (shepherd-action
+    (name 'free-voucher)
+    (documentation "Return a list of vouchers in which the voucher
+recording HOSTNAME as its 'client-hostname field has been replaced by a
+free voucher.")
+    (procedure
+     #~(lambda (running hostname vouchers)
+         (if (not (null? ((lambda (voucher)
+                            (filter (lambda (entry)
+                                      (and (equal? (car entry)
+                                                   'client-hostname)
+                                           (equal? (cadr entry)
+                                                   hostname)))
+                                    voucher)) (car vouchers))))
+             (cons (map (lambda (entry)
+                          (if (equal? (car entry)
+                                      'client-hostname)
+                              '(client-hostname #f)
+                              entry))
+                        (car vouchers))
+                   (cdr vouchers))
+             (cons (car vouchers)
+                   (perform-service-action (lookup-service 'network-rw)
+                                           'free-voucher
+                                           hostname
+                                           (cdr vouchers)))))))
+   (shepherd-action
+    (name 'remove-booked-client)
+    (documentation "Return VOUCHERS if the string HOSTNAME is not booked
+in the list of vouchers VOUCHERS. Otherwise, return a list of vouchers
+in which the voucher recording HOSTNAME as its 'client-hostname field
+has been replaced by a free voucher.")
+    (procedure
+     #~(lambda (running hostname vouchers)
+         (display "Client: ")
+         (display hostname)
+         (display " is requesting freeing his voucher.\n")
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             (begin (display (string-append
+                              "Client "
+                              hostname
+                              " is not booked, not freeing.\n"))
+                    vouchers)
+             (begin (display (string-append
+                              "Freeing voucher of client "
+                              hostname
+                              ".\n"))
+                    (perform-service-action (lookup-service 'network-rw)
+                                            'free-voucher
+                                            hostname
+                                            vouchers))))))
+   (shepherd-action
+    (name 'free-client-booking)
+    (documentation "Burn a new list of vouchers in which the voucher of
+the client whose hostname is the string HOSTNAME has been replaced by a
+unbooked voucher.")
+    (procedure
+     #~(lambda (running hostname)
+         (perform-service-action (lookup-service 'network-rw)
+                                 'burn-state
+                                 (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'remove-booked-client
+                                  hostname
+                                  (perform-service-action
+                                   (lookup-service 'network-rw)
+                                   'network-state))))))
+   (shepherd-action
+    (name 'tun-request!)
+    (documentation "Return a list of vouchers equal to the list of
+vouchers VOUCHERS in which the 'tun-request?  field of the voucher of the
+client whose hostname is the string HOSTNAME has been set to the boolean
+value NEW-STATUS.")
+    (procedure
+     #~(lambda (running hostname new-status vouchers)
+         (if (not (null? ((lambda (voucher)
+                            (filter (lambda (entry)
+                                      (and (equal? (car entry)
+                                                   'client-hostname)
+                                           (equal? (cadr entry)
+                                                   hostname)))
+                                    voucher)) (car vouchers))))
+             (cons (map (lambda (entry)
+                          (if (equal? (car entry)
+                                      'tun-request?)
+                              `(tun-request? ,new-status)
+                              entry))
+                        (car vouchers))
+                   (cdr vouchers))
+             (cons (car vouchers)
+                   (perform-service-action (lookup-service 'network-rw)
+                                           'tun-request!
+                                           hostname
+                                           new-status
+                                           (cdr vouchers)))))))
+   (shepherd-action
+    (name 'tun-request!-maybe)
+    (documentation "Return VOUCHERS if the string HOSTNAME is not booked
+in the list of vouchers VOUCHERS. Otherwise, return a list of vouchers
+in which the 'tun-request? field of the voucher recording HOSTNAME as its
+'client-hostname field has been set to the boolean value NEW-STATUS.")
+    (procedure
+     #~(lambda (running hostname new-status vouchers)
+         (display "Client: ")
+         (display hostname)
+         (display " is requesting a change of requestor status.\n")
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             (begin (display (string-append
+                              "Client "
+                              hostname
+                              " is not booked, not proceeding.\n"))
+                    vouchers)
+             (begin (display (string-append
+                              "Setting requestor status of client "
+                              hostname
+                              " to "
+                              (if new-status "true" "false")
+                              ".\n"))
+                    (perform-service-action (lookup-service 'network-rw)
+                                            'tun-request!
+                                            hostname
+                                            new-status
+                                            vouchers))))))
+   (shepherd-action
+    (name 'set-tun-request)
+    (documentation "Burn a new list of vouchers in which the
+'tun-request? field of the voucher of the client whose hostname is the
+string HOSTNAME has been set to #t.")
+    (procedure
+     #~(lambda (running hostname)
+         (perform-service-action (lookup-service 'network-rw)
+                                 'burn-state
+                                 (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'tun-request!-maybe
+                                  hostname
+                                  #t
+                                  (perform-service-action
+                                   (lookup-service 'network-rw)
+                                   'network-state))))))
+   (shepherd-action
+    (name 'unset-tun-request)
+    (documentation "Burn a new list of vouchers in which the
+'tun-request? field of the voucher of the client whose hostname is the
+string HOSTNAME has been set to #f.")
+    (procedure
+     #~(lambda (running hostname)
+         (perform-service-action (lookup-service 'network-rw)
+                                 'burn-state
+                                 (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'tun-request!-maybe
+                                  hostname
+                                  #f
+                                  (perform-service-action
+                                   (lookup-service 'network-rw)
+                                   'network-state))))))
+   (shepherd-action
+    (name 'connected!)
+    (documentation "Return a list of vouchers equal to the list of
+vouchers VOUCHERS in which the 'connected?  field of the voucher of the
+client whose hostname is the string HOSTNAME has been set to the boolean
+value NEW-STATUS.")
+    (procedure
+     #~(lambda (running hostname new-status vouchers)
+         (if (not (null? ((lambda (voucher)
+                            (filter (lambda (entry)
+                                      (and (equal? (car entry)
+                                                   'client-hostname)
+                                           (equal? (cadr entry)
+                                                   hostname)))
+                                    voucher)) (car vouchers))))
+             (cons (map (lambda (entry)
+                          (if (equal? (car entry)
+                                      'connected?)
+                              `(connected? ,new-status)
+                              entry))
+                        (car vouchers))
+                   (cdr vouchers))
+             (cons (car vouchers)
+                   (perform-service-action (lookup-service 'network-rw)
+                                           'connected!
+                                           hostname
+                                           new-status
+                                           (cdr vouchers)))))))
+   (shepherd-action
+    (name 'connected!-maybe)
+    (documentation "Return VOUCHERS if the string HOSTNAME is not booked
+in the list of vouchers VOUCHERS. Otherwise, return a list of vouchers
+in which the 'connected? field of the voucher recording HOSTNAME as its
+'client-hostname field has been set to the boolean value NEW-STATUS.")
+    (procedure
+     #~(lambda (running hostname new-status vouchers)
+         (display "Client: ")
+         (display hostname)
+         (display " is requesting a change of connection status.\n")
+         (if (perform-service-action (lookup-service 'network-rw)
+                                     'unknown-host?
+                                     hostname)
+             (begin (display (string-append
+                              "Client "
+                              hostname
+                              " is not booked, not proceeding.\n"))
+                    vouchers)
+             (begin (display (string-append
+                              "Setting connection status of client "
+                              hostname
+                              " to "
+                              (if new-status "true" "false")
+                              ".\n"))
+                    (perform-service-action (lookup-service 'network-rw)
+                                            'connected!
+                                            hostname
+                                            new-status
+                                            vouchers))))))
+   (shepherd-action
+    (name 'set-connected)
+    (documentation "Burn a new list of vouchers in which the 'conencted?
+field of the voucher of the client whose hostname is the string HOSTNAME
+has been set to #t.")
+    (procedure
+     #~(lambda (running hostname)
+         (perform-service-action (lookup-service 'network-rw)
+                                 'burn-state
+                                 (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'connected!-maybe
+                                  hostname
+                                  #t
+                                  (perform-service-action
+                                   (lookup-service
+                                    'network-rw)
+                                   'network-state))))))
+   (shepherd-action
+    (name 'set-disconnected)
+    (documentation "Burn a new list of vouchers in which the 'connected
+field of the voucher of the client whose hostname is the string HOSTNAME
+has been set to #f.")
+    (procedure
+     #~(lambda (running hostname)
+         (perform-service-action (lookup-service 'network-rw)
+                                 'burn-state
+                                 (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'connected!-maybe
+                                  hostname
+                                  #f
+                                  (perform-service-action
+                                   (lookup-service 'network-rw)
+                                   'network-state))))))))
+
+(define (knocker-actions config)
+  "Returns a list of <shepherd-actions> objects for the 'knocker
+lieutenant of the 'vpn whispers lieutenant, configurable by CONFIG, a
+record of the <whispers-vpn-configuration> type."
+  (list (shepherd-action
+         (name 'pre-start)
+         (documentation "Perform the 'knock-knock action of the 'knocker
+service.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'knocker)
+                                      'knock-knock))))
+        (shepherd-action
+         (name 'knock-knock)
+         (documentation "Burn the network state recorded in lieutenant
+service into the 'network-state service at this level of the whispers
+tree. Then:
+- VPN clients perform the following in order:
+    - Stop the 'handshake-port-forward persistent ssh connection.
+    - Check if the client's own hostname has a connected status in the
+local network state and perform the following:
+        - If yes, start the 'connected lieutenant of the 'vpn service
+and stop the 'disconnected lieutenant of the 'vpn service.
+        - if no, start the 'disconnected lieutenant of the 'vpn service
+and stop the 'connected lieutenant of the 'vpn service.
+    - Check if the client's own hostname is a known host in the
+local network state and perform the following:
+        - If yes, start the 'registered lieutenant of the 'vpn service,
+stop the 'unregistered lieutenant of the 'vpn service and chain connect
+auto-immediately if appropriate per the boolean value recorded in the
+'want-connect-state lieutenant.
+        - if no, start the 'unregistered lieutenant of the 'vpn service
+and stop the 'registered lieutenant of the 'vpn service.
+    - If the local client has no voucher associated to its host name or
+if it has a voucher whose 'tun-request? field is value is #f, stop the
+'connecting lieutenant of the 'vpn service and stop the 'disconnecting
+lieutenant of the 'vpn service.
+    - If the local client's voucher's 'tun-request? field evaluates to a
+true value, start the persistent ssh connection supporting the tunnel
+forward of the local client.
+    - Stop the 'registering and 'unregistering lieutenants
+of the 'vpn service.
+- The VPN server performs either of the following:
+    - If the network state contains one or more vouchers whose
+'tun-request? field evaluates to a true value, perform either one of the
+following for each of the clients whose hostname is resolved by the
+aforementioned vouchers:
+       - If the 'connected? field of the voucher evaluates to a true
+value, perform the following in order:
+            - Destroy the server-side TUN interface of this voucher by
+stopping the corresponding lieutenant of the 'vpn service.
+            - Knock-knock the client of this voucher.
+            - Set the 'tun-request? field of this voucher to #f in the
+network state.
+       - If the 'connected? field of the voucher evaluates to #f value,
+perform the following in order:
+            - Create the server-side TUN interface of this voucher by
+starting the corresponding lieutenant of the 'vpn service.
+            - Knock-knock the client of this voucher.
+            - Set the 'tun-request? field of this voucher to #f in the
+network state.
+   - Otherwise, knock-knock all clients, including a prospective
+client through the handshake reverse forward.")
+         (procedure
+          (let* ((client? (whispers-vpn-configuration-client? config))
+                 (herd-path "/run/current-system/profile/bin/herd")
+                 (handshake-conf (handshake-forward-configuration config))
+                 (handshake-name (persistent-ssh-name handshake-conf)))
+            #~(lambda (running)
+                (display "Who's there?\n")
+                (perform-service-action (lookup-service 'network-rw)
+                                        'burn-state
+                                        (perform-service-action
+                                         (lookup-service 'network-rw)
+                                         'lieutenant-network-state))
+                (let* ((state (perform-service-action (lookup-service
+                                                       'network-rw)
+                                                      'network-state))
+                       (req-field? (lambda (field)
+                                     (equal? (car field)
+                                             'tun-request?)))
+                       (requester? (lambda (voucher)
+                                     (cadar (filter req-field?
+                                                    voucher))))
+                       (requesters (filter requester?
+                                           state))
+                       (con-field? (lambda (field)
+                                     (equal? (car field)
+                                             'connected?)))
+                       (con? (lambda (voucher)
+                               (cadar (filter con-field?
+                                              voucher))))
+                       (host-field? (lambda (field)
+                                      (equal? (car field)
+                                              'client-hostname)))
+                       (host (lambda (voucher)
+                               (cadar (filter host-field?
+                                              voucher))))
+                       (tun-num-f? (lambda (field)
+                                     (equal? (car field)
+                                             'tun-device-number)))
+                       (tun-num (lambda (voucher)
+                                  (cadar (filter tun-num-f?
+                                                 voucher))))
+                       (tun-str (lambda (voucher)
+                                  (string-append
+                                   "tun"
+                                   (number->string (tun-num
+                                                    voucher)))))
+                       (tun-sym (lambda (voucher)
+                                  (string->symbol (tun-str
+                                                   voucher))))
+                       (tun-act (lambda (voucher)
+                                  (if (con? voucher)
+                                      'stop-tun
+                                      'start-tun))))
+                  (if (not #$client?)
+                      (if (null? requesters)
+                          (perform-service-action (lookup-service
+                                                   'network-rw)
+                                                  'knock-knock-clients)
+                          (begin (map (lambda (voucher)
+                                        (perform-service-action
+                                         (lookup-service (tun-sym voucher))
+                                         (tun-act voucher)))
+                                      requesters)
+                                 (map (lambda (voucher)
+                                        (perform-service-action
+                                         (lookup-service 'network-rw)
+                                         'knock-knock-client
+                                         (string-append "client/"
+                                                        (host
+                                                         voucher))))
+                                      requesters)
+                                 (map (lambda (voucher)
+                                        (perform-service-action
+                                         (lookup-service 'network-rw)
+                                         'unset-tun-request
+                                         (host voucher)))
+                                      requesters)))
+                      (begin (stop-service (lookup-service
+                                            '#$handshake-name))
+                             (if (perform-service-action
+                                  (lookup-service 'network-rw)
+                                  'hostname->connected?
+                                  (gethostname))
+                                 (begin (start-service (lookup-service
+                                                        'connected))
+                                        (stop-service (lookup-service
+                                                       'disconnected)))
+                                 (begin (start-service (lookup-service
+                                                        'disconnected))
+                                        (stop-service (lookup-service
+                                                       'connected))))
+                             (if (perform-service-action (lookup-service
+                                                          'network-rw)
+                                                         'unknown-host?
+                                                         (gethostname))
+                                 (begin (start-service (lookup-service
+                                                        'unregistered))
+                                        (stop-service (lookup-service
+                                                       'registered)))
+                                 (begin (start-service (lookup-service
+                                                        'registered))
+                                        (stop-service (lookup-service
+                                                       'unregistered))
+                                        (perform-service-action
+                                         (lookup-service 'registering)
+                                         'maybe-chain-conn)))
+                             (when (or (perform-service-action
+                                        (lookup-service 'network-rw)
+                                        'unknown-host?
+                                        (gethostname))
+                                       (not (perform-service-action
+                                             (lookup-service 'network-rw)
+                                             'hostname->tun-request?
+                                             (gethostname))))
+                               (stop-service (lookup-service 'connecting))
+                               (stop-service (lookup-service
+                                              'disconnecting)))
+                             (when (and (not (perform-service-action
+                                              (lookup-service 'network-rw)
+                                              'unknown-host?
+                                              (gethostname)))
+                                        (perform-service-action
+                                         (lookup-service
+                                          'network-rw)
+                                         'hostname->tun-request?
+                                         (gethostname)))
+                               (perform-service-action (lookup-service
+                                                        'connecting)
+                                                       'tun-start-ssh))
+                             (stop-service (lookup-service 'registering))
+                             (stop-service (lookup-service
+                                            'unregistering)))))))))))
+
+(define network-state-rw-actions
+  (list (shepherd-action
+         (name 'burn-state)
+         (documentation "unload the existing instance of the
+'network-state service. Write a network state NEW-STATE as the content
+of a new instance of the 'network-state service, from there on queryable
+through its 'vouchers action.")
+         (procedure
+          #~(lambda (running . new-state)
+              (perform-service-action (lookup-service 'root)
+                                      'unload
+                                      "network-state")
+              (display "Burning new 'network-state service.\n")
+              (register-services
+               (service
+                '(network-state)
+                #:start (lambda whatever #f)
+                #:stop (lambda whatever #t)
+                #:actions (list (action
+                                 'vouchers
+                                 (lambda (running) (car new-state))
+                                 "Return the network state stored in the
+service."))
+                #:one-shot? #t)))))
+        (shepherd-action
+         (name 'network-state)
+         (documentation "Return the list of vouchers returned by the
+'vouchers action of the 'network-state service in scope at the current
+level of the whispers tree.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'network-state)
+                                      'vouchers))))
+        (shepherd-action
+         (name 'read-network-state)
+         (documentation "Burn the string NEW-STATE as it is read by the
+read syntax as the new network state of the 'network-state service.")
+         (procedure
+          #~(lambda (running . new-state)
+              (perform-service-action (lookup-service 'network-rw)
+                                      'burn-state
+                                      (with-input-from-string (car new-state)
+                                        read)))))
+        (shepherd-action
+         (name 'print-network-state)
+         (documentation "Return the network state as a string which can
+be read by the 'read-network-state action of another shepherd's
+'network-rw Whispers lieutenant service's read-network-state action.")
+         (procedure
+          #~(lambda (running)
+              (with-output-to-string (lambda ()
+                                       (write (perform-service-action
+                                               (lookup-service 'network-rw)
+                                               'network-state)))))))
+        (shepherd-action
+         (name 'display-network-state)
+         (documentation "For debugging puposes, display to standard
+output the list of vouchers returned by the 'vouchers action of the
+'network-state service in scope at current level of the whispers tree.")
+         (procedure
+          #~(lambda (running)
+              (map (lambda (voucher)
+                     (display voucher)
+                     (display "\n"))
+                   (perform-service-action (lookup-service 'network-rw)
+                                           'network-state)))))
+        (shepherd-action
+         (name 'lieutenant-network-state)
+         (documentation "Return the list of vouchers returned by the
+'vouchers action of the 'network-state service in scope in scope of the
+'unpriviledged lieutenant whispers shepherd.")
+         (procedure
+          #~(lambda (running)
+              (perform-service-action (lookup-service 'unpriviledged)
+                                      'lieutenant-action
+                                      "network-state"
+                                      "network-rw"))))
+        (shepherd-action
+         (name 'display-lieutenant-network-state)
+         (documentation "For debugging puposes, display to standard
+output the list of vouchers returned by the 'vouchers action of the
+'network-state service in scope of the 'unpriviledged lieutenant
+whispers shepherd.")
+         (procedure
+          #~(lambda (running)
+              (map (lambda (voucher)
+                     (display voucher)
+                     (display "\n"))
+                   (perform-service-action
+                    (display-network-state 'network-rw)
+                    'lieutenant-network-state)))))))
+
+(define (want-connect-rw-shepherd-services whatever)
+  "Returns a list of one <shepherd-service> object providing actions to
+read and write the connection status wish of a client under registration.
+from a 'want-connect-state service in its scope, not configurable by
+WHATEVER."
+  (list (shepherd-service
+         (documentation "connection status wish state read and write
+operations.")
+         (provision '(want-connect-rw))
+         (requirement '())
+         (start #~(lambda whatever #f))
+         (actions (append want-connect-state-write-actions
+                          want-connect-resolve-actions))
+         (stop #~(lambda whatever #t))
+         (one-shot? #t)
+         (auto-start? #f))))
+
+(define want-connect-rw-service-type
+  (service-type
+   (name 'want-connect-rw)
+   (description "Shepherd service used for read and write operations of
+the want-connect state of a Whispers VPN.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             want-connect-rw-shepherd-services)))
+   (default-value (whispers-vpn-configuration))))
+
+(define (want-connect-state-shepherd-services whatever)
+  "Returns a list of one <shepherd-service> object doing nothing of
+interest in the state in which it is instanciated at guix's system
+reconfigure time, not configurable by WHATEVER. While a VPN client will
+be registering at the OS run-time, the final connection status wished by
+the client (connected or registered only) will be stored in and
+retrievable from the service."
+  (list (shepherd-service
+         (documentation "Queryable connection status wish state of a
+Whispers VPN.")
+         (provision '(want-connect-state))
+         (requirement '())
+         (start #~(lambda whatever #f))
+         (actions
+          (list (shepherd-action
+                 (name 'want-connect?)
+                 (documentation "Does not return anything of interest at
+this time, nor have any side effects.")
+                 (procedure #~(lambda (running) #f)))))
+         (stop #~(lambda whatever #t))
+         (one-shot? #t)
+         (auto-start? #f))))
+
+(define want-connect-state-service-type
+  (service-type
+   (name 'want-connect-state)
+   (description "Shepherd service used for storage and retrieval of the
+current status wish of a VPN client under registration: connected or
+registered only.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             want-connect-state-shepherd-services)))
+   (default-value 'whatever)))
+
+(define (network-rw-shepherd-services config)
+  "Returns a list of one <shepherd-service> object providing actions to
+read and write the network state stored in the 'network-state service in
+its scope,configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (let ((client? (whispers-vpn-configuration-client? config)))
+    (list (shepherd-service
+           (documentation "Network state read and write operations.")
+           (provision '(network-rw))
+           (requirement '())
+           (start #~(lambda whatever #f))
+           (actions (append network-state-rw-actions
+                            voucher-resolve-actions
+                            (if client?
+                                (client->server-actions config)
+                                (append voucher-actions
+                                        (server->client-actions config)))))
+           (stop #~(lambda whatever #t))
+           (one-shot? #t)
+           (auto-start? #f)))))
+
+(define network-rw-service-type
+  (service-type
+   (name 'network-rw)
+   (description "Shepherd service used for read and write operations of
+the network state of a Whispers VPN.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             network-rw-shepherd-services)))
+   (default-value (whispers-vpn-configuration))))
+
+(define (network-state-shepherd-services network-state)
+  "Returns a list of one <shepherd-service> object storing the list of
+vouchers NETWORK-STATE in the return value of its 'vouchers shepherd
+action."
+  (list (shepherd-service
+         (documentation "Queryable network state of a Whispers VPN.")
+         (provision '(network-state))
+         (requirement '())
+         (start #~(lambda whatever #f))
+         (actions
+          (list (shepherd-action
+                 (name 'vouchers)
+                 (documentation "Return the network state stored in the
+service.")
+                 (procedure #~(lambda (running) '#$network-state)))))
+         (stop #~(lambda whatever #t))
+         (one-shot? #t)
+         (auto-start? #f))))
+
+(define network-state-service-type
+  (service-type
+   (name 'network-state)
+   (description "Shepherd service used for storage and retrieval of the
+network state of a Whispers VPN.")
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             network-state-shepherd-services)))
+   (default-value (network-constants))))
+
+(define (state-services config)
+  "Returns a list of 2 shepherd root Guix services which do not
+daemonize any process, a service to perform read and write action on a
+network state, and a service to store a mutbable network state,
+configurable by CONFIG, a record of the <whispers-vpn-configuration>
+type."
+  (list (service network-state-service-type
+                 (empty-network (whispers-vpn-configuration-constants
+                                 config)))
+        (service network-rw-service-type config)))
+
+(define (vpn-lieutenants config)
+  "Returns the list of the lieutenants of the 'vpn service sitting at
+the top of the whispers sub-tree of a Whispers VPN, configurable by
+CONFIG, a record of the <whispers-vpn-configuration> type."
+  (let* ((client? (whispers-vpn-configuration-client? config))
+         (auto-register? (whispers-vpn-configuration-auto-register? config)))
+    (append
+     (if client?
+         (let* ((forward-conf (forward-configuration config))
+                (forward-name (persistent-ssh-name forward-conf)))
+           (append
+            (map (lambda (service-config)
+                   (service persistent-ssh-service-type service-config))
+                 (reverse-forward-configurations config))
+            (map (lambda (service-config)
+                   (service persistent-ssh-service-type service-config))
+                 (tun-device-forward-configurations config))
+            (list (service persistent-ssh-service-type
+                           (forward-configuration config))
+                  (service persistent-ssh-service-type
+                           (handshake-forward-configuration config))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'registering)
+                            (requires (list forward-name))
+                            (pre-start-action? #t)
+                            (extra-actions (registering-actions config))
+                            (%auto-start? auto-register?)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'unregistering)
+                            (requires (list forward-name))
+                            (pre-start-action? #t)
+                            (extra-actions (unregistering-actions config))
+                            (%auto-start? #f)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'registered)
+                            (requires (list forward-name))
+                            (%auto-start? #f)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'unregistered)
+                            (requires (list forward-name))
+                            (%auto-start? #f)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'connecting)
+                            (requires (list forward-name
+                                            'registered))
+                            (pre-start-action? #t)
+                            (extra-actions
+                             (connecting-actions config))
+                            (%auto-start? #f)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'disconnecting)
+                            (requires (list forward-name))
+                            (pre-start-action? #t)
+                            (extra-actions
+                             (disconnecting-actions config))
+                            (%auto-start? #f)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'connected)
+                            (requires (list forward-name
+                                            'registered))
+                            (extra-actions connected-actions)
+                            (%auto-start? #f)))
+                  (service whispers-service-type
+                           (whispers-configuration
+                            (name 'disconnected)
+                            (requires (list forward-name))
+                            (%auto-start? #f)))
+                  (service want-connect-rw-service-type
+                           'whatever)
+                  (service want-connect-state-service-type
+                           'whatever))))
+         (list))
+     (state-services config)
+          (list (service whispers-service-type
+                         (whispers-configuration
+                          (name 'unpriviledged)
+                          (lieutenants (state-services config))
+                          (user "whispers")
+                          (extend-user? #t)
+                          (group "whispers")
+                          (extend-group? #t)))
+                (service whispers-service-type
+                         (whispers-configuration
+                          (name 'knocker)
+                          (requires '(unpriviledged))
+                          (pre-start-action? #t)
+                          (user "whispers")
+                          (group "whispers")
+                          (extra-actions (knocker-actions config)))))
+          (tcp-ip-lieutenants config))))
+
+(define (vpn-actions config)
+  "Return the list of actions of the 'vpn service, configurable by
+CONFIG, a record of the <whispers-vpn-configuration> type."
+  (append
+   (list (shepherd-action
+          (name 'pre-start)
+          (documentation "Create the directories and tmpfs mounts used
+by the persistent ssh connections of the 'vpn service.")
+          (procedure
+           #~(lambda (running)
+               (perform-service-action (lookup-service 'vpn)
+                                       'make-directory
+                                       "/var/log/whispers/vpn/ssh-tunneler"
+                                       "root"
+                                       "root"
+                                       #$(number->string #o755 8))
+               (perform-service-action (lookup-service 'vpn)
+                                       'make-tmpfs
+                                       "/run/whispers/vpn/ssh-tunneler"
+                                       "root"
+                                       "root"
+                                       #$(number->string #o755 8)))))
+         (shepherd-action
+          (name 'post-stop)
+          (documentation "Unmount the tmpfs mounts used by the
+persistent ssh connections of the VPN service.")
+          (procedure
+           #~(lambda (running)
+               (perform-service-action (lookup-service 'vpn)
+                                       'clear-tmpfs
+                                       "/run/whispers/vpn/ssh-tunneler"))))
+         (shepherd-action
+          (name 'register)
+          (documentation "Register this host in the VPN network.")
+          (procedure
+           #~(lambda (running)
+               (display (string-append "Client "
+                                       (gethostname)
+                                       " initiating register "
+                                       "sequence.\n"))
+               (if (perform-service-action (lookup-service 'vpn)
+                                           'lieutenant-action
+                                           "running?"
+                                           "connected")
+                   (begin (display "'connected lieutenant is ")
+                          (display "started, register ")
+                          (display "sequence canceled.\n"))
+                   (begin (display "'connected lieutenant is ")
+                          (display "not started, register ")
+                          (display "sequence confirmed.\n")
+                          (perform-service-action (lookup-service 'vpn)
+                                                  'lieutenant-action
+                                                  "start-registering"
+                                                  "registering"))))))
+         (shepherd-action
+          (name 'unregister)
+          (documentation "Unregister this host from the VPN network.")
+          (procedure
+           #~(lambda (running)
+               (display (string-append "Client "
+                                       (gethostname)
+                                       " initiating unregister "
+                                       "sequence.\n"))
+               (if (perform-service-action (lookup-service 'vpn)
+                                           'lieutenant-action
+                                           "running?"
+                                           "connected")
+                   (begin (display "'connected lieutenant is ")
+                          (display "started, unregister ")
+                          (display "sequence canceled.\n"))
+                   (begin (display "'connected lieutenant is ")
+                          (display "not started, unregister ")
+                          (display "sequence confirmed.\n")
+                          (perform-service-action (lookup-service 'vpn)
+                                                  'lieutenant-action
+                                                  "start-unregistering"
+                                                  "unregistering"))))))
+         (shepherd-action
+          (name 'connect)
+          (documentation "Connect this host in the VPN network.")
+          (procedure
+           #~(lambda (running)
+               (if (perform-service-action (lookup-service 'vpn)
+                                           'lieutenant-action
+                                           "unknown-host?"
+                                           "network-rw"
+                                           (gethostname))
+                   (begin (display (string-append "Client "
+                                                  (gethostname)
+                                                  " is not registered.\n"
+                                                  "Initiating "
+                                                  "regiseter-connect "
+                                                  "sequence.\n"))
+                          (perform-service-action (lookup-service 'vpn)
+                                                  'lieutenant-action
+                                                  "want-connect-on"
+                                                  "want-connect-rw")
+                          (perform-service-action (lookup-service 'vpn)
+                                                  'register))
+                   (begin (display (string-append "Client "
+                                                  (gethostname)
+                                                  " is already registered.\n"
+                                                  "Initiating connect "
+                                                  "sequence.\n"))
+                          (if (perform-service-action (lookup-service 'vpn)
+                                                      'lieutenant-action
+                                                      "running?"
+                                                      "connected")
+                              (begin (display "'connected lieutenant is ")
+                                     (display "started, connect ")
+                                     (display "sequence canceled.\n"))
+                              (begin (display "'connected lieutenant is ")
+                                     (display "not started, connect ")
+                                     (display "sequence confirmed.\n")
+                                     (perform-service-action
+                                      (lookup-service 'vpn)
+                                      'lieutenant-action
+                                      "start-connecting"
+                                      "connecting"))))))))
+         (shepherd-action
+          (name 'disconnect)
+          (documentation "Disconnect this host from the VPN network.")
+          (procedure
+           #~(lambda (running)
+               (display (string-append "Client "
+                                       (gethostname)
+                                       " initiating disconnect "
+                                       "sequence.\n"))
+               (if (perform-service-action (lookup-service 'vpn)
+                                           'lieutenant-action
+                                           "running?"
+                                           "connected")
+                   (begin (display "'connected lieutenant is ")
+                          (display "started, disconnect ")
+                          (display "sequence confirmed.\n")
+                          (perform-service-action (lookup-service 'vpn)
+                                                  'lieutenant-action
+                                                  "start-disconnecting"
+                                                  "disconnecting"))
+                   (begin (display "'connected lieutenant is ")
+                          (display "not started, disconnect ")
+                          (display "sequence canceled.\n")))))))
+   (tcp-ip-actions config)))
+
+(define (registering-actions config)
+  "Returns the list of actions of the 'registering lieutenant of the
+'vpn service, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (list (shepherd-action
+         (name 'pre-start)
+         (documentation "Connect the handshake reverse port forward to
+the server, book a voucer in the server network state, knock-knock the
+server to propagate to the whole network, then terminate the handshake
+reverse port forward.")
+         (procedure
+          (let* ((handshake-conf (handshake-forward-configuration config))
+                 (handshake-name (persistent-ssh-name handshake-conf)))
+            #~(lambda (running)
+                (start-in-the-background (list '#$handshake-name))))))
+        (shepherd-action
+         (name 'start-registering)
+         (documentation "Start the 'registering service")
+         (procedure
+          #~(lambda (running)
+              (start-service (lookup-service 'registering)))))
+        (shepherd-action
+         (name 'maybe-chain-conn)
+         (documentation "If the want-connect connection status wish is
+#t, reset it to #f and start connecting auto-immediately.")
+         (procedure
+          (let* ((herd-path "/run/current-system/profile/bin/herd")
+                 (whisp-sock "/run/whispers/unix-sockets/whispers.sock"))
+            #~(lambda (running)
+                (if (or (not (lookup-service 'want-connect-rw))
+                        (not (perform-service-action
+                              (lookup-service 'want-connect-rw)
+                              'want-connect?))
+                        (perform-service-action (lookup-service 'network-rw)
+                                                'hostname->tun-request?
+                                                (gethostname)))
+                    (display (string-append "Client "
+                                            (gethostname)
+                                            " not chain-connecting.\n"))
+                    (begin (display (string-append "Client "
+                                                   (gethostname)
+                                                   " chain-connecting.\n"))
+                           (perform-service-action (lookup-service
+                                                    'want-connect-rw)
+                                                   'want-connect-off)
+                           ;; FIXME: action up the whispers tree, deadlock?
+                           (fork+exec-command (list #$herd-path
+                                                    "-s"
+                                                    #$whisp-sock
+                                                    "connect"
+                                                    "vpn"))))))))))
+
+(define (unregistering-actions config)
+  "Returns the list of actions of the 'unregistering lieutenant of the
+'vpn service, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (list (shepherd-action
+         (name 'pre-start)
+         (documentation "Connect the handshake reverse port forward to
+the server, book a voucer in the server network state, knock-knock the
+server to propagate to the whole network, then terminate the handshake
+reverse port forward.")
+         (procedure
+          (let* ((handshake-conf (handshake-forward-configuration config))
+                 (handshake-name (persistent-ssh-name handshake-conf)))
+
+            #~(lambda (running)
+                (start-in-the-background (list '#$handshake-name))))))
+        (shepherd-action
+         (name 'start-unregistering)
+         (documentation "Start the 'unregistering service")
+         (procedure
+          #~(lambda (running)
+              (start-service (lookup-service 'unregistering)))))))
+
+(define connected-actions
+  (list (shepherd-action
+         (name 'running?)
+         (documentation "Return #t if the 'connected service is running,
+return #f it is stopped.")
+         (procedure
+          #~(lambda (running)
+              (service-running? (lookup-service 'connected)))))))
+
+(define (connecting-actions config)
+  "Return the list of actions of the 'connecting lieutenant of the 'vpn
+service, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (let* ((herd-path "/run/current-system/profile/bin/herd")
+         (base-socket-path "/run/whispers/vpn")
+         (vpn-sock (string-append base-socket-path
+                                  "/unix-sockets/vpn.sock"))
+         (exit-port (whispers-vpn-configuration-client-sshd-port config))
+         (exit-str (number->string exit-port))
+         (client-tun (whispers-vpn-configuration-client-tun-device config))
+         (client-tun-str (number->string client-tun))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (rev-stealth-conf (reverse-stealth-conf config))
+         (rev-stealth-forward (car (ssh-connection-configuration-forwards
+                                    rev-stealth-conf)))
+         (rev-stealth-port (ssh-forward-configuration-entry-port
+                            rev-stealth-forward))
+         (rev-stealth-port-str (number->string rev-stealth-port))
+         (rev-stealth-stance (if stealth?
+                                 ;; FIXME: not future-proof
+                                 (string-append "_proxy-port_"
+                                                rev-stealth-port-str)
+                                 ""))
+         (tun-stealth-conf (tun-stealth-conf config))
+         (tun-stealth-forward (car (ssh-connection-configuration-forwards
+                                    tun-stealth-conf)))
+         (tun-stealth-port (ssh-forward-configuration-entry-port
+                            tun-stealth-forward))
+         (tun-stealth-port-str (number->string tun-stealth-port))
+         (tun-stealth-stance (if stealth?
+                                 ;; FIXME: not future-proof
+                                 (string-append "_proxy-port_"
+                                                tun-stealth-port-str)
+                                 "")))
+    (list (shepherd-action
+           (name 'pre-start)
+           (documentation "Connect the handshake reverse port forward to
+the server, book a voucer in the server network state, knock-knock the
+server to propagate to the whole network, then terminate the handshake
+reverse port forward.")
+           (procedure
+            #~(lambda (running)
+                (let* ((server-port (perform-service-action
+                                     (lookup-service 'network-rw)
+                                     'hostname->port
+                                     (gethostname)))
+                       (server-port-str (number->string server-port))
+                       ;; FIXME: not future-proof
+                       (stance (string-append server-port-str
+                                              ":"
+                                              "127.0.0.1"
+                                              ":"
+                                              #$exit-str))
+                       ;; FIXME: not future-proof
+                       (name-str (string-append "ssh-forwards_reverse-port@"
+                                                stance
+                                                #$rev-stealth-stance))
+                       (name-sym (string->symbol name-str)))
+                  (display "Starting reverse port forwarding.\n")
+                  (start-in-the-background (list name-sym))))))
+          (shepherd-action
+           (name 'tun-start-dev)
+           (documentation "Knock-knock the server to create the TUN
+network device on the server side.")
+           (procedure
+            #~(lambda (running)
+                (let* ((server-tun (perform-service-action
+                                    (lookup-service 'network-rw)
+                                    'hostname->tun
+                                    (gethostname)))
+                       (tun-str (number->string server-tun)))
+                  (fork+exec-command (list #$herd-path
+                                           "-s"
+                                           #$vpn-sock
+                                           "set-tun-request-knock"
+                                           "network-rw"))))))
+          (shepherd-action
+           (name 'tun-start-ssh)
+           (documentation "Start the ssh persistent connection
+supporting the VPN tun device on client and server sides.")
+           (procedure
+            #~(lambda (running)
+                (let* ((server-tun (perform-service-action
+                                    (lookup-service 'network-rw)
+                                    'hostname->tun
+                                    (gethostname)))
+                       (server-tun-str (number->string server-tun))
+                       (tun-str (number->string server-tun))
+                       (stance (string-append #$client-tun-str
+                                              ":"
+                                              server-tun-str))
+                       ;; FIXME: not future-proof
+                       (name-str (string-append "ssh-forwards_tunnel@"
+                                                stance
+                                                #$tun-stealth-stance))
+                       (name-sym (string->symbol name-str)))
+                  (display (string-append "Starting "
+                                          name-str
+                                          " in the background.\n"))
+                  (start-in-the-background (list name-sym))))))
+          (shepherd-action
+           (name 'start-connecting)
+           (documentation "Start the 'connecting service")
+           (procedure
+            #~(lambda (running)
+                (start-service (lookup-service 'connecting))))))))
+
+(define (disconnecting-actions config)
+  "Return the list of actions of the 'disconnecting lieutenant of the
+'vpn service, configurable by CONFIG, a record of the
+<whispers-vpn-configuration> type."
+  (let* ((client-tun (whispers-vpn-configuration-client-tun-device config))
+         (client-tun-str (number->string client-tun))
+         (exit-port (whispers-vpn-configuration-client-sshd-port config))
+         (exit-str (number->string exit-port))
+         (handshake-conf (handshake-forward-configuration config))
+         (handshake-name (persistent-ssh-name handshake-conf))
+         (stealth? (whispers-vpn-configuration-stealth? config))
+         (rev-stealth-conf (reverse-stealth-conf config))
+         (rev-stealth-forward  (car (ssh-connection-configuration-forwards
+                                     rev-stealth-conf)))
+         (rev-stealth-port (ssh-forward-configuration-entry-port
+                            rev-stealth-forward))
+         (rev-stealth-port-str (number->string rev-stealth-port))
+         (rev-stealth-stance (if stealth?
+                                 ;; FIXME: not future-proof
+                                 (string-append "_proxy-port_"
+                                                rev-stealth-port-str)
+                                 ""))
+         (tun-stealth-conf (tun-stealth-conf config))
+         (tun-stealth-forward (car (ssh-connection-configuration-forwards
+                                    tun-stealth-conf)))
+         (tun-stealth-port (ssh-forward-configuration-entry-port
+                            tun-stealth-forward))
+         (tun-stealth-port-str (number->string tun-stealth-port))
+         (tun-stealth-stance (if stealth?
+                                 ;; FIXME: not future-proof
+                                 (string-append "_proxy-port_"
+                                                tun-stealth-port-str)
+                                 "")))
+    (list (shepherd-action
+           (name 'pre-start)
+           (documentation "Stop the 'device-device-forward lieutenant of
+the 'vpn service, then set the 'connected? flag of this client's voucher
+to #f in the server's unpriviledged network state and knock-knock the
+server. The disconnection sequence will complete for this client after
+receiving the server's knock-knock.")
+           (procedure
+            #~(lambda (running)
+                (let* ((server-tun (perform-service-action
+                                    (lookup-service 'network-rw)
+                                    'hostname->tun
+                                    (gethostname)))
+                       (tun-str (number->string server-tun))
+                       (stance (string-append #$client-tun-str
+                                              ":"
+                                              tun-str))
+                       ;; FIXME: not future-proof
+                       (name-str (string-append "ssh-forwards_tunnel@"
+                                                stance
+                                                #$tun-stealth-stance))
+                       (name-sym (string->symbol name-str)))
+                  (stop-service (lookup-service name-sym)))
+
+                (start-in-the-background (list '#$handshake-name))
+                (let* ((server-port (perform-service-action (lookup-service
+                                                             'network-rw)
+                                                            'hostname->port
+                                                            (gethostname)))
+                       (server-port-str (number->string server-port))
+                       ;; FIXME: not future-proof
+                       (stance (string-append server-port-str
+                                              ":"
+                                              "127.0.0.1"
+                                              ":"
+                                              #$exit-str))
+                       ;; FIXME: not future-proof
+                       (name-str (string-append "ssh-forwards_reverse-port@"
+                                                stance
+                                                #$rev-stealth-stance))
+                       (name-sym (string->symbol name-str)))
+                  (stop-service (lookup-service name-sym)))
+                (perform-service-action (lookup-service '#$(tun-dev-sym
+                                                            client-tun))
+                                        'stop-tun))))
+          (shepherd-action
+           (name 'start-disconnecting)
+           (documentation "Start the 'disconnecting service")
+           (procedure
+            #~(lambda (running)
+                (start-service (lookup-service 'disconnecting))))))))
+
+(define (whispers-vpn-tree config)
+  "Returns a whispers service tree for a whispers VPN sub-tree,
+configurable by CONFIG, a record of the <whispers-vpn-configuration>
+type."
+  (let ((client? (whispers-vpn-configuration-client? config)))
+    (list (service whispers-service-type
+                   (whispers-configuration
+                    (name 'vpn)
+                    (lieutenants (vpn-lieutenants config))
+                    (pre-start-action? client?)
+                    (post-stop-action? client?)
+                    (extra-actions (vpn-actions config)))))))
+
+(define whispers-vpn-service-type
+  (service-type
+   (name '(whispers-vpn))
+   (description "ssh-tttt!!!")
+   (extensions (list (service-extension whispers-service-type
+                                        whispers-vpn-tree)))
+   (default-value (whispers-vpn-configuration))))