diff options
| -rw-r--r-- | doc/guix.texi | 7 | ||||
| -rw-r--r-- | gnu/services/vpn.scm | 199 |
2 files changed, 108 insertions, 98 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index 8b09cd68673..a8c834efa66 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -35758,9 +35758,10 @@ IP address that no longer correspond to their freshly resolved host | |||
| 35758 | name. Set this to @code{#t} if one or more endpoints use host names | 35758 | name. Set this to @code{#t} if one or more endpoints use host names |
| 35759 | provided by a dynamic DNS service to keep the sessions alive. | 35759 | provided by a dynamic DNS service to keep the sessions alive. |
| 35760 | 35760 | ||
| 35761 | @item @code{monitor-ips-interval} (default: @code{'(next-minute (range 0 60 5))}) | 35761 | @item @code{monitor-ips-interval} (default: @code{"*/5 * * * *"}) |
| 35762 | The time interval at which the IP monitoring job should run, provided as | 35762 | This is the monitoring schedule, expressed as a string in traditional |
| 35763 | an mcron time specification (@pxref{Guile Syntax,,,mcron}). | 35763 | cron syntax or as a gexp evaluating to a Shepherd calendar event |
| 35764 | (@pxref{Timers,,, shepherd, The GNU Shepherd Manual}). | ||
| 35764 | 35765 | ||
| 35765 | @item @code{private-key} (default: @code{"/etc/wireguard/private.key"}) | 35766 | @item @code{private-key} (default: @code{"/etc/wireguard/private.key"}) |
| 35766 | The private key file for the interface. It is automatically generated | 35767 | The private key file for the interface. It is automatically generated |
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm index 3f1f8661d8f..f97cbac7bb3 100644 --- a/gnu/services/vpn.scm +++ b/gnu/services/vpn.scm | |||
| @@ -34,7 +34,6 @@ | |||
| 34 | #:use-module (gnu services) | 34 | #:use-module (gnu services) |
| 35 | #:use-module (gnu services configuration) | 35 | #:use-module (gnu services configuration) |
| 36 | #:use-module (gnu services dbus) | 36 | #:use-module (gnu services dbus) |
| 37 | #:use-module (gnu services mcron) | ||
| 38 | #:use-module (gnu services shepherd) | 37 | #:use-module (gnu services shepherd) |
| 39 | #:use-module (gnu system shadow) | 38 | #:use-module (gnu system shadow) |
| 40 | #:use-module (gnu packages admin) | 39 | #:use-module (gnu packages admin) |
| @@ -43,6 +42,7 @@ | |||
| 43 | #:use-module (guix packages) | 42 | #:use-module (guix packages) |
| 44 | #:use-module (guix records) | 43 | #:use-module (guix records) |
| 45 | #:use-module (guix gexp) | 44 | #:use-module (guix gexp) |
| 45 | #:use-module (guix diagnostics) | ||
| 46 | #:use-module (guix i18n) | 46 | #:use-module (guix i18n) |
| 47 | #:use-module (guix deprecation) | 47 | #:use-module (guix deprecation) |
| 48 | #:use-module (srfi srfi-1) | 48 | #:use-module (srfi srfi-1) |
| @@ -757,7 +757,7 @@ strongSwan."))) | |||
| 757 | (monitor-ips? wireguard-configuration-monitor-ips? ;boolean | 757 | (monitor-ips? wireguard-configuration-monitor-ips? ;boolean |
| 758 | (default #f)) | 758 | (default #f)) |
| 759 | (monitor-ips-interval wireguard-configuration-monitor-ips-interval | 759 | (monitor-ips-interval wireguard-configuration-monitor-ips-interval |
| 760 | (default '(next-minute (range 0 60 5)))) ;string | list | 760 | (default "*/5 * * * *")) ;string | list |
| 761 | (pre-up wireguard-configuration-pre-up ;list of strings | 761 | (pre-up wireguard-configuration-pre-up ;list of strings |
| 762 | (default '())) | 762 | (default '())) |
| 763 | (post-up wireguard-configuration-post-up ;list of strings | 763 | (post-up wireguard-configuration-post-up ;list of strings |
| @@ -919,117 +919,126 @@ public key, if any." | |||
| 919 | '() | 919 | '() |
| 920 | peers))) | 920 | peers))) |
| 921 | 921 | ||
| 922 | (define (wireguard-shepherd-service config) | 922 | (define (wireguard-monitoring-program config) |
| 923 | (match-record config <wireguard-configuration> | 923 | (match-record config <wireguard-configuration> |
| 924 | (wireguard interface shepherd-requirement) | 924 | (interface monitor-ips-interval peers) |
| 925 | (let ((host-names (endpoint-host-names peers))) | ||
| 926 | (when (null? host-names) | ||
| 927 | (warning (G_ "'monitor-ips?' is #t but no host name to monitor~%"))) | ||
| 928 | |||
| 929 | ;; Loosely based on WireGuard's own 'reresolve-dns.sh' shell script | ||
| 930 | ;; (see: https://raw.githubusercontent.com/WireGuard/wireguard-tools/ | ||
| 931 | ;; master/contrib/reresolve-dns/reresolve-dns.sh). | ||
| 932 | (program-file | ||
| 933 | (format #f "wireguard-~a-monitoring" interface) | ||
| 934 | (with-imported-modules (source-module-closure | ||
| 935 | '((gnu services herd) | ||
| 936 | (guix build utils))) | ||
| 937 | #~(begin | ||
| 938 | (use-modules (gnu services herd) | ||
| 939 | (guix build utils) | ||
| 940 | (ice-9 popen) | ||
| 941 | (ice-9 match) | ||
| 942 | (ice-9 textual-ports) | ||
| 943 | (srfi srfi-1) | ||
| 944 | (srfi srfi-26)) | ||
| 945 | |||
| 946 | (define (resolve-host name) | ||
| 947 | "Return the IP address resolved from NAME." | ||
| 948 | (let* ((ai (car (getaddrinfo name))) | ||
| 949 | (sa (addrinfo:addr ai))) | ||
| 950 | (inet-ntop (sockaddr:fam sa) | ||
| 951 | (sockaddr:addr sa)))) | ||
| 952 | |||
| 953 | (define wg #$(file-append wireguard-tools "/bin/wg")) | ||
| 954 | |||
| 955 | #$(procedure-source strip-port/maybe) | ||
| 956 | |||
| 957 | (define service-name | ||
| 958 | '#$(wireguard-service-name interface)) | ||
| 959 | |||
| 960 | (when (live-service-running | ||
| 961 | (current-service service-name)) | ||
| 962 | (let* ((pipe (open-pipe* OPEN_READ wg "show" | ||
| 963 | #$interface "endpoints")) | ||
| 964 | (lines (string-split (get-string-all pipe) | ||
| 965 | #\newline)) | ||
| 966 | ;; IPS is an association list mapping | ||
| 967 | ;; public keys to IP addresses. | ||
| 968 | (ips (map (match-lambda | ||
| 969 | ((public-key ip) | ||
| 970 | (cons public-key | ||
| 971 | (strip-port/maybe ip)))) | ||
| 972 | (map (cut string-split <> #\tab) | ||
| 973 | (remove string-null? | ||
| 974 | lines))))) | ||
| 975 | (close-pipe pipe) | ||
| 976 | (for-each | ||
| 977 | (match-lambda | ||
| 978 | ((key . host-name) | ||
| 979 | (let ((resolved-ip (resolve-host | ||
| 980 | (strip-port/maybe | ||
| 981 | host-name))) | ||
| 982 | (current-ip (assoc-ref ips key))) | ||
| 983 | (unless (string=? resolved-ip current-ip) | ||
| 984 | (format #t "resetting `~a' peer \ | ||
| 985 | endpoint to `~a' due to stale IP (`~a' instead of `~a')~%" | ||
| 986 | key host-name | ||
| 987 | current-ip resolved-ip) | ||
| 988 | (invoke wg "set" #$interface "peer" key | ||
| 989 | "endpoint" host-name))))) | ||
| 990 | '#$host-names))))))))) | ||
| 991 | |||
| 992 | (define (wireguard-shepherd-services config) | ||
| 993 | (match-record config <wireguard-configuration> | ||
| 994 | (wireguard interface monitor-ips? monitor-ips-interval shepherd-requirement) | ||
| 925 | (let ((wg-quick (file-append wireguard "/bin/wg-quick")) | 995 | (let ((wg-quick (file-append wireguard "/bin/wg-quick")) |
| 926 | (auto-start? (wireguard-configuration-auto-start? config)) | 996 | (auto-start? (wireguard-configuration-auto-start? config)) |
| 927 | (config (wireguard-configuration-file config))) | 997 | (config-file (wireguard-configuration-file config))) |
| 928 | (list (shepherd-service | 998 | (define monitoring-service |
| 999 | (and monitor-ips? | ||
| 1000 | (shepherd-service | ||
| 1001 | (provision (list (symbol-append | ||
| 1002 | (wireguard-service-name interface) | ||
| 1003 | '-monitoring))) | ||
| 1004 | (requirement (list 'user-processes | ||
| 1005 | (wireguard-service-name interface))) | ||
| 1006 | (modules '((shepherd service timer))) | ||
| 1007 | (start #~(make-timer-constructor | ||
| 1008 | #$(if (string? monitor-ips-interval) | ||
| 1009 | #~(cron-string->calendar-event | ||
| 1010 | #$monitor-ips-interval) | ||
| 1011 | monitor-ips-interval) | ||
| 1012 | (command '(#$(wireguard-monitoring-program config))) | ||
| 1013 | #:wait-for-termination? #t)) | ||
| 1014 | (stop #~(make-timer-destructor)) | ||
| 1015 | (documentation "Monitor the Wireguard VPN tunnel.") | ||
| 1016 | (actions (list shepherd-trigger-action))))) | ||
| 1017 | |||
| 1018 | (cons (shepherd-service | ||
| 929 | (requirement `(networking user-processes ,@shepherd-requirement)) | 1019 | (requirement `(networking user-processes ,@shepherd-requirement)) |
| 930 | (provision (list (wireguard-service-name interface))) | 1020 | (provision (list (wireguard-service-name interface))) |
| 931 | (start #~(lambda _ | 1021 | (start #~(lambda _ |
| 932 | (invoke #$wg-quick "up" #$config))) | 1022 | (invoke #$wg-quick "up" #$config-file))) |
| 933 | (stop #~(lambda _ | 1023 | (stop #~(lambda _ |
| 934 | (invoke #$wg-quick "down" #$config) | 1024 | (invoke #$wg-quick "down" #$config-file) |
| 935 | #f)) ;stopped! | 1025 | #f)) ;stopped! |
| 936 | (actions (list (shepherd-configuration-action config))) | 1026 | (actions (list (shepherd-configuration-action config-file))) |
| 937 | (auto-start? auto-start?) | 1027 | (auto-start? auto-start?) |
| 938 | (documentation "Run the Wireguard VPN tunnel")))))) | 1028 | (documentation "Run the Wireguard VPN tunnel")) |
| 939 | 1029 | (or (and=> monitoring-service list) | |
| 940 | (define (wireguard-monitoring-jobs config) | 1030 | '()))))) |
| 941 | ;; Loosely based on WireGuard's own 'reresolve-dns.sh' shell script (see: | ||
| 942 | ;; https://raw.githubusercontent.com/WireGuard/wireguard-tools/ | ||
| 943 | ;; master/contrib/reresolve-dns/reresolve-dns.sh). | ||
| 944 | (match-record config <wireguard-configuration> | ||
| 945 | (interface monitor-ips? monitor-ips-interval peers) | ||
| 946 | (let ((host-names (endpoint-host-names peers))) | ||
| 947 | (if monitor-ips? | ||
| 948 | (if (null? host-names) | ||
| 949 | (begin | ||
| 950 | (warn "monitor-ips? is #t but no host name to monitor") | ||
| 951 | '()) | ||
| 952 | ;; The mcron monitor job may be a string or a list; ungexp strips | ||
| 953 | ;; one quote level, which must be added back when a list is | ||
| 954 | ;; provided. | ||
| 955 | (list | ||
| 956 | #~(job | ||
| 957 | (if (string? #$monitor-ips-interval) | ||
| 958 | #$monitor-ips-interval | ||
| 959 | '#$monitor-ips-interval) | ||
| 960 | #$(program-file | ||
| 961 | (format #f "wireguard-~a-monitoring" interface) | ||
| 962 | (with-imported-modules (source-module-closure | ||
| 963 | '((gnu services herd) | ||
| 964 | (guix build utils))) | ||
| 965 | #~(begin | ||
| 966 | (use-modules (gnu services herd) | ||
| 967 | (guix build utils) | ||
| 968 | (ice-9 popen) | ||
| 969 | (ice-9 match) | ||
| 970 | (ice-9 textual-ports) | ||
| 971 | (srfi srfi-1) | ||
| 972 | (srfi srfi-26)) | ||
| 973 | |||
| 974 | (define (resolve-host name) | ||
| 975 | "Return the IP address resolved from NAME." | ||
| 976 | (let* ((ai (car (getaddrinfo name))) | ||
| 977 | (sa (addrinfo:addr ai))) | ||
| 978 | (inet-ntop (sockaddr:fam sa) | ||
| 979 | (sockaddr:addr sa)))) | ||
| 980 | |||
| 981 | (define wg #$(file-append wireguard-tools "/bin/wg")) | ||
| 982 | |||
| 983 | #$(procedure-source strip-port/maybe) | ||
| 984 | |||
| 985 | (define service-name '#$(wireguard-service-name | ||
| 986 | interface)) | ||
| 987 | |||
| 988 | (when (live-service-running | ||
| 989 | (current-service service-name)) | ||
| 990 | (let* ((pipe (open-pipe* OPEN_READ wg "show" | ||
| 991 | #$interface "endpoints")) | ||
| 992 | (lines (string-split (get-string-all pipe) | ||
| 993 | #\newline)) | ||
| 994 | ;; IPS is an association list mapping | ||
| 995 | ;; public keys to IP addresses. | ||
| 996 | (ips (map (match-lambda | ||
| 997 | ((public-key ip) | ||
| 998 | (cons public-key | ||
| 999 | (strip-port/maybe ip)))) | ||
| 1000 | (map (cut string-split <> #\tab) | ||
| 1001 | (remove string-null? | ||
| 1002 | lines))))) | ||
| 1003 | (close-pipe pipe) | ||
| 1004 | (for-each | ||
| 1005 | (match-lambda | ||
| 1006 | ((key . host-name) | ||
| 1007 | (let ((resolved-ip (resolve-host | ||
| 1008 | (strip-port/maybe | ||
| 1009 | host-name))) | ||
| 1010 | (current-ip (assoc-ref ips key))) | ||
| 1011 | (unless (string=? resolved-ip current-ip) | ||
| 1012 | (format #t "resetting `~a' peer \ | ||
| 1013 | endpoint to `~a' due to stale IP (`~a' instead of `~a')~%" | ||
| 1014 | key host-name | ||
| 1015 | current-ip resolved-ip) | ||
| 1016 | (invoke wg "set" #$interface "peer" key | ||
| 1017 | "endpoint" host-name))))) | ||
| 1018 | '#$host-names))))))))) | ||
| 1019 | '())))) ;monitor-ips? is #f | ||
| 1020 | 1031 | ||
| 1021 | (define wireguard-service-type | 1032 | (define wireguard-service-type |
| 1022 | (service-type | 1033 | (service-type |
| 1023 | (name 'wireguard) | 1034 | (name 'wireguard) |
| 1024 | (extensions | 1035 | (extensions |
| 1025 | (list (service-extension shepherd-root-service-type | 1036 | (list (service-extension shepherd-root-service-type |
| 1026 | wireguard-shepherd-service) | 1037 | wireguard-shepherd-services) |
| 1027 | (service-extension activation-service-type | 1038 | (service-extension activation-service-type |
| 1028 | wireguard-activation) | 1039 | wireguard-activation) |
| 1029 | (service-extension profile-service-type | 1040 | (service-extension profile-service-type |
| 1030 | (compose list | 1041 | (compose list |
| 1031 | wireguard-configuration-wireguard)) | 1042 | wireguard-configuration-wireguard)))) |
| 1032 | (service-extension mcron-service-type | ||
| 1033 | wireguard-monitoring-jobs))) | ||
| 1034 | (description "Set up Wireguard @acronym{VPN, Virtual Private Network} | 1043 | (description "Set up Wireguard @acronym{VPN, Virtual Private Network} |
| 1035 | tunnels."))) | 1044 | tunnels."))) |
