aboutsummaryrefslogtreecommitdiff
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.scm3424
1 files changed, 3424 insertions, 0 deletions
diff --git a/whispers/services/whispers/vpn.scm b/whispers/services/whispers/vpn.scm
new file mode 100644
index 0000000..55847ee
--- /dev/null
+++ b/whispers/services/whispers/vpn.scm
@@ -0,0 +1,3424 @@
+;;; 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
+ (make <service>
+ #:provides '(physical-dev-state)
+ #:start (lambda whatever #f)
+ #:stop (lambda whatever #t)
+ #:actions (make-actions
+ (physical-interface-name
+ "Return the name of the physical
+interface which supported
+the default route when
+the VPN was disconnected."
+ (lambda (running) phy-dev-name))
+ (physical-gateway-ip
+ "Return the ip of the gateway
+of the physical interface
+which supported the default
+route when the VPN was
+disconnected."
+ (lambda (running) router-ip)))
+ #:docstring "Queryable physical network device
+restoration state of a
+Whispers VPN."
+ #: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
+ (make <service>
+ #:provides '(want-connect-state)
+ #:start (lambda whatever #f)
+ #:stop (lambda whatever #t)
+ #:actions (make-actions
+ (want-connect?
+ "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."
+ (lambda (running) want?)))
+ #:docstring "Queryable connection status wish state of a
+Whispers VPN."
+ #: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
+ (make <service>
+ #:provides '(network-state)
+ #:start (lambda whatever #f)
+ #:stop (lambda whatever #t)
+ #:actions (make-actions
+ (vouchers
+ "Return the network state stored in the
+service."
+ (lambda (running) (car new-state))))
+ #:docstring "Queryable network state of a Whispers VPN."
+ #: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))))