Message ID | bfd99f266c3fd749c6b24b386a7ac90dc89ce338.1707828643.git.soeren@soeren-tempel.net |
---|---|
State | New |
Headers | show |
Series | [bug#68675,v3,1/2] gnu: Add dhcpcd. | expand |
Hello, soeren@soeren-tempel.net skribis: > * gnu/services/networking.scm (dhcp-client-shepherd-service): Add > support for the dhcpcd client implementation. > * gnu/services/networking.scm (dhcp-client-account-service): New > procedure. > * gnu/services/networking.scm (dhcp-client-service-type): Add optional > account-service-type extensions (needed for dhcpcd). > * gnu/system.scm (%base-packages-networking): Remove isc-dhcp from > %base-packages (will be pulled in by dhcp-client-shepherd-service). [...] > + ;; Returns the execution configuration for the DHCP client > + ;; selected by the package field of dhcp-client-configuration. > + ;; The configuration is a pair of pidfile and execution command > + ;; where the latter is a list. > + (define exec-config > + (case (string->symbol #$client-name) > + ((isc-dhcp) > + (let ((pid-file "/var/run/dhclient.pid")) > + (cons > + (cons* (string-append #$package "/sbin/dhclient") > + "-nw" "-I" "-pf" pid-file ifaces) > + pid-file))) > + ((dhcpcd) > + ;; For dhcpcd, the utilized pid-file depends on the > + ;; command-line arguments. If multiple interfaces are > + ;; given, a different pid-file is returned. Hence, we > + ;; consult dhcpcd itself to determine the pid-file. > + (let* ((cmd (string-append #$package "/sbin/dhcpcd")) > + (arg (cons* cmd "-b" ifaces))) > + (cons arg > + (let* ((pipe (string-join (append arg '("-P")) " ")) > + (port (open-input-pipe pipe)) > + (path (read-line port))) > + (close-pipe port) > + path)))) > + (else > + (display > + "unknown 'package' value in dhcp-client-configuration" > + (current-error-port)) > + (newline (current-error-port)) > + #f))) > + I would very much like to avoid the ‘open-input-pipe’ dance here. Maybe there’s a way to ask it to not fork, in which case we don’t need to wait for a PID file? (I’ll give up if this really really can’t be avoided. :-)) > + (and > + exec-config > + (let ((pid-file (cdr exec-config)) > + (exec-cmd (car exec-config))) > + (false-if-exception (delete-file pid-file)) > + (let ((pid (fork+exec-command exec-cmd))) > + (and (zero? (cdr (waitpid pid))) > + (read-pid-file pid-file))))))) Two notes: I would find it clearer to call ‘fork+exec-command’ above instead of constructing ‘exec-config’. Ideally, we’d use: (start #~(if (file-exists? (string-append #$package "/bin/dhclient")) (make-forkexec-constructor …) ;ISC version, with #:pid-file (make-forkexec-constructor …))) ;dhcpcd, maybe without #:pid-file Maybe that is too naive, but at least we should get as close as possible to that, for instance by avoiding direct calls to ‘fork+exec-command’ + ‘waitpid’ + ‘read-pid-file’ as much as possible. HTH! Ludo’.
Hi, Ludovic Courtès <ludo@gnu.org> wrote: > Apologies for the late reply. No worries, I understand that you have a lot of other things to do :) > I would very much like to avoid the ‘open-input-pipe’ dance here. Maybe > there’s a way to ask it to not fork, in which case we don’t need to wait > for a PID file? When using a PID file, AFAIK the only thing we can do is replicate the C code that I referenced in my prior email for determining the PID file name [1]. Is there a specific reason why you want to avoid using open-input-pipe? > What do others do? I guess it’s not possible to have a systemd .service > file given those PID file semantics, is it? systemd and other service supervisor do not rely on PID files for services supervision as they are inherently racy. Therefore, as mentioned in my prior email, I also think it would be preferable to not use a PID file for supervising either dhclient or dhcpcd. I only ended up using a PID file here as dhclient already used one and I wanted to keep the service changes as minimal as possible to ensure a swift review since I believe this to be a security-critical issue (see Guix issue #68619). > Two notes: I would find it clearer to call ‘fork+exec-command’ above > instead of constructing ‘exec-config’. > > Ideally, we’d use: > > (start #~(if (file-exists? (string-append #$package "/bin/dhclient")) > (make-forkexec-constructor …) ;ISC version, with #:pid-file > (make-forkexec-constructor …))) ;dhcpcd, maybe without #:pid-file If we separate the dhclient and the dhcpcd code like this, then it may be worthwhile to have entirely separated services instead of combining them both in a single service. This would also ease providing a configuration for these services. > Maybe that is too naive, but at least we should get as close as possible > to that, for instance by avoiding direct calls to ‘fork+exec-command’ + > ‘waitpid’ + ‘read-pid-file’ as much as possible. Ideally, I would not want to refactor existing service code as much in this patchset. As mentioned above, I believe Guix using dhclient by default (which has been EOL for 2 years) has security implications. Therefore, I would want to migrate dhcp-client-service-type away from dhclient to dhcpcd as soon as possible. As part of this migration, I would strongly suggest a deprecation and removal of dhclient support from dhcp-client-service-type. As soon as dhclient support is removed, refactorings of dhcp-client-service-type can be conducted. Including a removal of PID files for supervision, instead supervising dhcpcd by spawning it as non-double-forking child process [2]. Greetings Sören [1]: https://github.com/NetworkConfiguration/dhcpcd/blob/v10.0.6/src/dhcpcd.c#L2144 [2]: https://github.com/nmeum/guix-channel/blob/b1b80697a9d35ca015ce56ccf3031f91f2bf554f/src/nmeum/services/networking.scm#L209-L211
diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm index 495d049728..4e058e1880 100644 --- a/gnu/services/networking.scm +++ b/gnu/services/networking.scm @@ -316,25 +316,21 @@ (define-record-type* <dhcp-client-configuration> (define dhcp-client-shepherd-service (match-lambda ((? dhcp-client-configuration? config) - (let ((package (dhcp-client-configuration-package config)) - (requirement (dhcp-client-configuration-shepherd-requirement config)) - (provision (dhcp-client-configuration-shepherd-provision config)) - (interfaces (dhcp-client-configuration-interfaces config)) - (pid-file "/var/run/dhclient.pid")) + (let* ((package (dhcp-client-configuration-package config)) + (client-name (package-name package)) + (requirement (dhcp-client-configuration-shepherd-requirement config)) + (provision (dhcp-client-configuration-shepherd-provision config)) + (interfaces (dhcp-client-configuration-interfaces config))) (list (shepherd-service (documentation "Set up networking via DHCP.") (requirement `(user-processes udev ,@requirement)) (provision provision) + (modules `((ice-9 popen) + (ice-9 rdelim) + ,@%default-modules)) - ;; XXX: Running with '-nw' ("no wait") avoids blocking for a minute when - ;; networking is unavailable, but also means that the interface is not up - ;; yet when 'start' completes. To wait for the interface to be ready, one - ;; should instead monitor udev events. (start #~(lambda _ - (define dhclient - (string-append #$package "/sbin/dhclient")) - - ;; When invoked without any arguments, 'dhclient' discovers all + ;; When invoked without any arguments, the client discovers all ;; non-loopback interfaces *that are up*. However, the relevant ;; interfaces are typically down at this point. Thus we perform ;; our own interface discovery here. @@ -355,17 +351,46 @@ (define dhcp-client-shepherd-service (_ #~'#$interfaces)))) - (false-if-exception (delete-file #$pid-file)) - (let ((pid (fork+exec-command - ;; By default dhclient uses a - ;; pre-standardization implementation of - ;; DDNS, which is incompatable with - ;; non-ISC DHCP servers; thus, pass '-I'. - ;; <https://kb.isc.org/docs/aa-01091>. - (cons* dhclient "-nw" "-I" - "-pf" #$pid-file ifaces)))) - (and (zero? (cdr (waitpid pid))) - (read-pid-file #$pid-file))))) + ;; Returns the execution configuration for the DHCP client + ;; selected by the package field of dhcp-client-configuration. + ;; The configuration is a pair of pidfile and execution command + ;; where the latter is a list. + (define exec-config + (case (string->symbol #$client-name) + ((isc-dhcp) + (let ((pid-file "/var/run/dhclient.pid")) + (cons + (cons* (string-append #$package "/sbin/dhclient") + "-nw" "-I" "-pf" pid-file ifaces) + pid-file))) + ((dhcpcd) + ;; For dhcpcd, the utilized pid-file depends on the + ;; command-line arguments. If multiple interfaces are + ;; given, a different pid-file is returned. Hence, we + ;; consult dhcpcd itself to determine the pid-file. + (let* ((cmd (string-append #$package "/sbin/dhcpcd")) + (arg (cons* cmd "-b" ifaces))) + (cons arg + (let* ((pipe (string-join (append arg '("-P")) " ")) + (port (open-input-pipe pipe)) + (path (read-line port))) + (close-pipe port) + path)))) + (else + (display + "unknown 'package' value in dhcp-client-configuration" + (current-error-port)) + (newline (current-error-port)) + #f))) + + (and + exec-config + (let ((pid-file (cdr exec-config)) + (exec-cmd (car exec-config))) + (false-if-exception (delete-file pid-file)) + (let ((pid (fork+exec-command exec-cmd))) + (and (zero? (cdr (waitpid pid))) + (read-pid-file pid-file))))))) (stop #~(make-kill-destructor)))))) (package (warning (G_ "'dhcp-client' service now expects a \ @@ -377,10 +402,27 @@ (define dhcp-client-shepherd-service (dhcp-client-configuration (package package)))))) +(define (dhcp-client-account-service config) + (let ((package (dhcp-client-configuration-package config))) + ;; Contrary to other DHCP clients (e.g. dhclient), dhcpcd supports + ;; privilege separation. Hence, we need to create an account here. + (if (string=? "dhcpcd" (package-name package)) + (list (user-group (name "dhcpcd") (system? #t)) + (user-account + (name "dhcpcd") + (group "dhcpcd") + (system? #t) + (comment "dhcpcd daemon user") + (home-directory "/var/empty") + (shell "/run/current-system/profile/sbin/nologin"))) + '()))) + (define dhcp-client-service-type (service-type (name 'dhcp-client) (extensions - (list (service-extension shepherd-root-service-type + (list (service-extension account-service-type + dhcp-client-account-service) + (service-extension shepherd-root-service-type dhcp-client-shepherd-service))) (default-value (dhcp-client-configuration)) (description "Run @command{dhcp}, a Dynamic Host Configuration