[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[shepherd] 03/03: Interpret AF_INET6 endpoints as IPv6-only.
From: |
Ludovic Courtès |
Subject: |
[shepherd] 03/03: Interpret AF_INET6 endpoints as IPv6-only. |
Date: |
Wed, 18 May 2022 10:00:22 -0400 (EDT) |
civodul pushed a commit to branch wip-inetd-ipv6
in repository shepherd.
commit 959b78a597c56ca73e519981d19f18bbe97529b5
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Wed May 18 15:36:20 2022 +0200
Interpret AF_INET6 endpoints as IPv6-only.
* 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