* configure.ac: Check the values of IPPROTO_IPV6 and IPV6_V6ONLY. * modules/shepherd/system.scm.in (ipv6-only): New procedure. * modules/shepherd/service.scm (endpoint->listening-socket): Call it if ADDRESS is AF_INET6. (define-as-needed): New macro. (IN6ADDR_LOOPBACK, IN6ADDR_ANY): New variables. * tests/inetd.sh: Add 'test-inetd6' and 'test-inetd-v6-only' services. Test them. --- NEWS | 11 +++++++ configure.ac | 12 +++++++ doc/shepherd.texi | 14 ++++++++ modules/shepherd/service.scm | 19 +++++++++++ modules/shepherd/system.scm.in | 11 +++++++ tests/inetd.sh | 58 ++++++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+)
diff --git a/NEWS b/NEWS index 4ce7a48..3798b31 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,17 @@ For compatibility with 0.9.0, if the second argument to list of endpoints. This behavior will be preserved for at least the whole 0.9.x series. +** ‘AF_INET6’ endpoints are now interpreted as IPv6-only + +In 0.9.0, using an ‘AF_INET6’ endpoint for ‘make-systemd-constructor’ would +usually have the effect of making the service available on both IPv6 and IPv4. +This is due to the default behavior of Linux, which is to bind IPv6 addresses +as IPv4 as well (the default behavior can be changed by running +‘sysctl net.ipv6.bindv6only 1’). + +‘AF_INET6’ endpoints are now interpreted as IPv6-only. Thus, if a service is +to be made available both as IPv6 and IPv4, two endpoints must be used. + ** ‘shepherd’ reports whether a service is transient ** ‘herd status’ shows whether a service is transient ** Fix possible file descriptor leak in ‘make-inetd-constructor’ diff --git a/configure.ac b/configure.ac index bf91560..b745813 100644 --- a/configure.ac +++ b/configure.ac @@ -141,6 +141,18 @@ AC_SUBST([SIG_BLOCK]) AC_SUBST([SIG_UNBLOCK]) AC_SUBST([SIG_SETMASK]) +dnl Check for constants not exported by Guile as of 3.0.8. +AC_MSG_CHECKING([<netinet/in.h> constants]) +AC_COMPUTE_INT([IPPROTO_IPV6], [IPPROTO_IPV6], [ + #include <sys/socket.h> + #include <netinet/in.h>]) +AC_COMPUTE_INT([IPV6_V6ONLY], [IPV6_V6ONLY], [ + #include <sys/socket.h> + #include <netinet/in.h>]) +AC_MSG_RESULT([done]) +AC_SUBST([IPPROTO_IPV6]) +AC_SUBST([IPV6_V6ONLY]) + AC_MSG_CHECKING([whether to build crash handler]) case "$host_os" in linux-gnu*) build_crash_handler=yes;; diff --git a/doc/shepherd.texi b/doc/shepherd.texi index 9efc48e..841b854 100644 --- a/doc/shepherd.texi +++ b/doc/shepherd.texi @@ -1093,6 +1093,20 @@ Return a new endpoint called @var{name} of @var{address}, an address as return by @code{make-socket-address}, with the given @var{style} and @var{backlog}. +When @var{address} is of type @code{AF_INET6}, the endpoint is +@emph{IPv6-only}. Thus, if you want a service available both on IPv4 +and IPv6, you need two endpoints. For example, below is a list of +endpoints to listen on port 4444 on all the network interfaces, both in +IPv4 and IPv6 (``0.0.0.0'' for IPv4 and ``::0'' for IPv6): + +@lisp +(list (endpoint (make-socket-address AF_INET INADDR_ANY 4444)) + (endpoint (make-socket-address AF_INET6 IN6ADDR_ANY 4444))) +@end lisp + +This is the list you would pass to @code{make-inetd-constructor} or +@code{make-systemd-constructor}---see below. + When @var{address} is of type @code{AF_UNIX}, @var{socket-owner} and @var{socket-group} are strings or integers that specify its ownership and that of its parent directory; @var{socket-directory-permissions} specifies the diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm index e93466a..6df550c 100644 --- a/modules/shepherd/service.scm +++ b/modules/shepherd/service.scm @@ -1251,6 +1251,10 @@ as argument, where SIGNAL defaults to `SIGTERM'." return by @code{make-socket-address}, with the given @var{style} and @var{backlog}. +When @var{address} is of type @code{AF_INET6}, the endpoint is +@emph{IPv6-only}. Thus, if you want a service available both on IPv4 and +IPv6, you need two endpoints. + When @var{address} is of type @code{AF_UNIX}, @var{socket-owner} and @var{socket-group} are strings or integers that specify its ownership and that of its parent directory; @var{socket-directory-permissions} specifies the @@ -1273,6 +1277,11 @@ permissions for its parent directory." group (group:gid (getgrnam group))))) (setsockopt sock SOL_SOCKET SO_REUSEADDR 1) + (when (= AF_INET6 (sockaddr:fam address)) + ;; Interpret AF_INET6 endpoints as IPv6-only. This is contrary to + ;; the Linux defaults where listening on an IPv6 address also listens + ;; on its IPv4 counterpart. + (ipv6-only sock)) (when (= AF_UNIX (sockaddr:fam address)) (mkdir-p (dirname (sockaddr:path address)) permissions) (chown (dirname (sockaddr:path address)) owner group) @@ -1309,6 +1318,16 @@ thrown an previously-opened sockets are closed." (apply throw args))))) (loop tail (cons sock result))))))) +(define-syntax-rule (define-as-needed name value) + (unless (defined? 'name) + (module-define! (current-module) 'name value) + (module-export! (current-module) '(name)))) + +;; These values are not defined as of Guile 3.0.8. Provide them as a +;; convenience. +(define-as-needed IN6ADDR_LOOPBACK 1) +(define-as-needed IN6ADDR_ANY 0) + ;;; ;;; Inetd-style services. diff --git a/modules/shepherd/system.scm.in b/modules/shepherd/system.scm.in index 2562764..0978c18 100644 --- a/modules/shepherd/system.scm.in +++ b/modules/shepherd/system.scm.in @@ -32,6 +32,7 @@ prctl PR_SET_CHILD_SUBREAPER getpgid + ipv6-only SFD_CLOEXEC signalfd consume-signalfd-siginfo @@ -141,6 +142,16 @@ ctrlaltdel(8) and see kernel/reboot.c in Linux." (list err)) result))))) +(define (ipv6-only port) + "Make PORT, a file port backed by a socket, IPv6-only (using the IPV6_V6ONLY +socket option) and return PORT. + +This is useful when willing to make a listening socket that operates on IPv6 +only (by default, Linux binds AF_INET6 addresses on IPv4 as well)." + ;; As of Guile 3.0.8, IPPROTO_IPV6 and IPV6_V6ONLY are not exported. + (setsockopt port @IPPROTO_IPV6@ @IPV6_V6ONLY@ 1) + port) + (define (allocate-sigset) (bytevector->pointer (make-bytevector @SIZEOF_SIGSET_T@))) diff --git a/tests/inetd.sh b/tests/inetd.sh index 83037bf..c05d6fe 100644 --- a/tests/inetd.sh +++ b/tests/inetd.sh @@ -48,6 +48,28 @@ cat > "$conf" <<EOF INADDR_LOOPBACK $PORT)))) #:stop (make-inetd-destructor)) + (make <service> + #:provides '(test-inetd6) + #:start (make-inetd-constructor %command + (list + (endpoint (make-socket-address + AF_INET + INADDR_LOOPBACK + $PORT)) + (endpoint (make-socket-address + AF_INET6 + IN6ADDR_LOOPBACK + $PORT)))) + #:stop (make-inetd-destructor)) + (make <service> + #:provides '(test-inetd-v6-only) + #:start (make-inetd-constructor %command + (list + (endpoint (make-socket-address + AF_INET6 + IN6ADDR_LOOPBACK + $PORT)))) + #:stop (make-inetd-destructor)) (make <service> #:provides '(test-inetd-unix) #:start (make-inetd-constructor %command @@ -81,6 +103,7 @@ test $($herd status | grep '\+' | wc -l) -eq 2 converse_with_echo_server () { guile -c "(use-modules (ice-9 match) (ice-9 rdelim)) + (define IN6ADDR_LOOPBACK 1) (define address $1) (define sock (socket (sockaddr:fam address) SOCK_STREAM 0)) (connect sock address) @@ -98,10 +121,45 @@ do "(make-socket-address AF_INET INADDR_LOOPBACK $PORT)" done +# Unavailable on IPv6. +! converse_with_echo_server \ + "(make-socket-address AF_INET6 IN6ADDR_LOOPBACK $PORT)" + $herd stop test-inetd ! converse_with_echo_server \ "(make-socket-address AF_INET INADDR_LOOPBACK $PORT)" +if guile -c '(socket AF_INET6 SOCK_STREAM 0)'; then + # Test IPv6 support. + $herd start test-inetd6 + + converse_with_echo_server \ + "(make-socket-address AF_INET6 IN6ADDR_LOOPBACK $PORT)" + converse_with_echo_server \ + "(make-socket-address AF_INET INADDR_LOOPBACK $PORT)" + + $herd stop test-inetd6 + + ! converse_with_echo_server \ + "(make-socket-address AF_INET6 IN6ADDR_LOOPBACK $PORT)" + ! converse_with_echo_server \ + "(make-socket-address AF_INET INADDR_LOOPBACK $PORT)" + + $herd start test-inetd-v6-only + + converse_with_echo_server \ + "(make-socket-address AF_INET6 IN6ADDR_LOOPBACK $PORT)" + ! converse_with_echo_server \ + "(make-socket-address AF_INET INADDR_LOOPBACK $PORT)" + + $herd stop test-inetd-v6-only + + ! converse_with_echo_server \ + "(make-socket-address AF_INET6 IN6ADDR_LOOPBACK $PORT)" + ! converse_with_echo_server \ + "(make-socket-address AF_INET INADDR_LOOPBACK $PORT)" +fi + # Now test inetd on a Unix-domain socket. $herd start test-inetd-unix -- 2.36.0