openssh-service no longer listens on IPv6

DoneSubmitted by Christopher Baines.
Details
4 participants
  • Jack Hill
  • Ludovic Courtès
  • Christopher Baines
  • Simon Streit
Owner
unassigned
Severity
important
C
C
Christopher Baines wrote on 9 May 12:39 +0200
(address . bug-guix@gnu.org)
87r153q913.fsf@cbaines.net
This looks to be a recent regression, probably connected with the
shepherd now doing the listening, rather than sshd itself.

Previously, you could use both IPv4 and IPv6.

netstat -tlnp | grep sshd
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 26683/sshd: /gnu/st
tcp6 0 0 :::22 :::* LISTEN 26683/sshd: /gnu/st

Now though, it looks like with shepherd doing the listening, you can
only use IPv4.

netstat -tlnp | grep 22
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1/guile


On an affected machine, you can reproduce this by trying to SSH over v6.

cbaines@lakeside ~$ ssh 127.0.0.1
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ED25519 key fingerprint is SHA256:1wV7w84awrGv5ilP5e8k5ygIvSkXSJ6LIy3MnqZG2Jw.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? ^C

cbaines@lakeside ~$ ssh ::1
ssh: connect to host ::1 port 22: Connection refused


This isn't an issue if you're not using IPv6, but if you have a machine
only accessible via IPv6, then you can't ssh in. The main workaround
I've found is getting access via other means, then starting sshd
listening on a different port (as the shepherd is using 22).
-----BEGIN PGP SIGNATURE-----

iQKlBAEBCgCPFiEEPonu50WOcg2XVOCyXiijOwuE9XcFAmJ48MhfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDNF
ODlFRUU3NDU4RTcyMEQ5NzU0RTBCMjVFMjhBMzNCMEI4NEY1NzcRHG1haWxAY2Jh
aW5lcy5uZXQACgkQXiijOwuE9Xej5BAAuTPOUHID47ahLzs49HfFGQzGKPWFZoOj
RbneFfm27U3/NXsHY19To/yj/OtVK9L5hWtWiLF93nHYWUQDHvS/XXEGWHOCD0jM
ToVBfvGRu+KFuGsB3au9vZHGoiZprQWOhKw1r8rfQ3Cs2rKy/QCfTAcAa7Ie7vtB
G/sQyFhbH8i6+pJaGifkdvMaX01vdlIyFfhIXuKvmcOexHvneN0jXEnbQR8sWzD9
hhKPMAw3NJGrrB/eJgkKE9rcoeXRo3SAESBDdj6ZTyePaJRxXf/enpkCMUy8+nJr
a/KWl7h8EOJnGSzI55Fltk2K+MKqHURSp2JTuin6CinNLIAzRcROlrDFh5aCMLFh
lE57sGvuccxVCWBOxVEwiZg2dWWuSXxWI8FiKzYXDWDrEwy4vphA4Ed7fo9nzPP8
e49iEPQveoQ4vOfXVQSHwDnuTt0yFMQH929OAr0nzJS4AaLkjos2KZfawsdPCO67
Ngwpf03gCPeDaoXU7Pf8qO9DudvR+0GM2WOvrp4hwTobyVGTnl8kfFPHg0lrLWSU
lhSplkvGmXaAiCLQKqB2DFCV5X7C/9Dt2/pcLWtmbg1u2l5J16slAx2TJaoPga5t
K5zTMJy0TpHslkuW2yLxNb/y/l7jWgq8ZuXlCuTnusshbVjASoNrDz0TzLRfPRvw
SNcfJV0Sofg=
=55Mq
-----END PGP SIGNATURE-----

L
L
Ludovic Courtès wrote on 12 May 14:37 +0200
control message for bug #55335
(address . control@debbugs.gnu.org)
87a6bnj59x.fsf@gnu.org
severity 55335 important
quit
C
C
Christopher Baines wrote on 13 May 14:21 +0200
Re: bug#55335: openssh-service no longer listens on IPv6
(address . 55335@debbugs.gnu.org)
87ilq9qzxg.fsf@cbaines.net
Christopher Baines <mail@cbaines.net> writes:

Toggle quote (33 lines)
> This looks to be a recent regression, probably connected with the
> shepherd now doing the listening, rather than sshd itself.
>
> Previously, you could use both IPv4 and IPv6.
>
> netstat -tlnp | grep sshd
> tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 26683/sshd: /gnu/st
> tcp6 0 0 :::22 :::* LISTEN 26683/sshd: /gnu/st
>
> Now though, it looks like with shepherd doing the listening, you can
> only use IPv4.
>
> netstat -tlnp | grep 22
> tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1/guile
>
>
> On an affected machine, you can reproduce this by trying to SSH over v6.
>
> cbaines@lakeside ~$ ssh 127.0.0.1
> The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
> ED25519 key fingerprint is SHA256:1wV7w84awrGv5ilP5e8k5ygIvSkXSJ6LIy3MnqZG2Jw.
> This key is not known by any other names
> Are you sure you want to continue connecting (yes/no/[fingerprint])? ^C
>
> cbaines@lakeside ~$ ssh ::1
> ssh: connect to host ::1 port 22: Connection refused
>
>
> This isn't an issue if you're not using IPv6, but if you have a machine
> only accessible via IPv6, then you can't ssh in. The main workaround
> I've found is getting access via other means, then starting sshd
> listening on a different port (as the shepherd is using 22).

I've had another look at how this might be fixed.

One workaround that seems to work is having the service just listen on
an IPv6 socket as I believe Linux translates IPv4 connections to
IPv6. The openssh system test seems to pass, and I believe this would
fix not being able to connect over IPv6, although it seems likely that
this would break things relying on IPv4 usage, like configuration based
on specific IP addresses.

I think the more rigerous approach would be to have shepherd listen on
two sockets, one for IPv4 and another for IPv6. That's currently
difficult though because of the above behaviour, the IPv6 socket blocks
opening the IPv4 one. I've got a patch [1] to Guile that adds the
constants needed for the setsockopt call and once that's possible, I
believe the setsockopt call would need to happen in
make-inetd-constructor.


Without reverting to the previous behaviour, maybe the best way forward
is to at least allow having the service listen via IPv6. That would mean
those affected by the loss of IPv6 support could enable it, and would
hopefully avoid breaking anyones configuration where they're relying on
native IPv4 connections. I'll send a patch for this shortly.

Chris
-----BEGIN PGP SIGNATURE-----

iQKlBAEBCgCPFiEEPonu50WOcg2XVOCyXiijOwuE9XcFAmJ+aJtfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDNF
ODlFRUU3NDU4RTcyMEQ5NzU0RTBCMjVFMjhBMzNCMEI4NEY1NzcRHG1haWxAY2Jh
aW5lcy5uZXQACgkQXiijOwuE9XeZZg/+P1lC9/Uv7yOFmkjyydmCWbRp5oCbG1pE
HM08CzQWuzflKvmNYHj/yUoCMdEksW1HCbhax7P/63putmmtlE1mWShbH6+uyY5R
cX4JJOmVcGfj3zmOXTkqezUBJOvCBt90vrrrvgA4380CP0oNQBwpaKDXBzDYMGCX
DVGXmrUE+XEeDr4RQXOG4Jut/uOM6Aq786ebw7yQlxIfTQLYrJfV9TpnX/LX9zdL
eoImRPsIxVD+b5PPBGfa2X5yuhViiCHn9DpUQ2chojkGVgUw9WmCrn/EyPR/WhDQ
0j+KrCOowcrP6m7L4Wh56/9OH+5FKGXz+jJa6wrMXAXHR9YqlT4D1fj20TuIAJ/X
C7hkJDrCP3jabNYvHQPaY+uuY/BVQnqhEUDA/wVSbGU+FMKl5rKibRfdXFbOxZ7/
tCafzI+u51K1GeWaZuyporme08E7srCWMfRMCAqOj9ZkUdB/eUV6zf2az6wrqovK
CxUWYEANYC2kpBKwoIbtfXKxniJoto6U3SISyNBVuvbfCTkkseRprn9TBPxOFBiQ
2FsI4byadec/yPhxGLmaILhyBWmf8B4S9nT96DxanOwtT7r45DshdlxtKOt3XWHx
Q5cMn/Vdf/EIk64MHAmwIbKhqdlBjBMSz4TwEveBK8JS+YWlUkJi2CbKgo012wXw
9QkM2tQrOos=
=PRe5
-----END PGP SIGNATURE-----

C
C
Christopher Baines wrote on 13 May 16:23 +0200
[PATCH] services: Allow shepherd to listen for IPv6 connections to openssh.
(address . 55335@debbugs.gnu.org)
20220513142312.21382-1-mail@cbaines.net
Prior to the switch to the openssh service using inetd, you could connect over
IPv4 or IPv6. With inetd, you can only connect over IPv4, meaning for machines
with just IPv6 connectivity, you can't connect.

Switching to listing via IPv6 should support IPv4 connections, as Linux is
capable of translating IPv4 connections to IPv6. I think there's a risk that
switching to this approach will affect some uses of the openssh
service. Therefore, this commit makes this a configuration option, which is #f
by default.

In the future, once it's easy to do so via Guile and the shepherd, it would be
good if two sockets were used, one for IPv4 and one for IPv6. That's not easy
at the moment, as the IPv6 socket conflicts with the IPv4 one, due to the
translation behaviour described above.

* gnu/services/ssh.scm (openssh-listen-via-ipv6?): New procedure.
(openssh-shepherd-service): Factor in listen-via-ipv6? when constructing the
socket address.
* doc/guix.texi (Networking Services): Document the new listen-via-ipv6?
field.
---
doc/guix.texi | 9 +++++++++
gnu/services/ssh.scm | 13 +++++++++++--
2 files changed, 20 insertions(+), 2 deletions(-)

Toggle diff (53 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index c168a66072..b168cb379e 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -19119,6 +19119,15 @@ Match Address 192.168.0.1
   PermitRootLogin yes"))
 @end lisp
 
+@item @code{listen-via-ipv6?} (default: @code{#f})
+When listening via a inetd-style Shepherd service, connections will only
+be accepted via IPv4.
+
+To have the shepherd listen instead via IPv6, set this option to
+#t. Depending on how network connections are handled, this will either
+enable connecting via IPv6 and translated IPv4, or just enable IPv6
+connections only.
+
 @end table
 @end deftp
 
diff --git a/gnu/services/ssh.scm b/gnu/services/ssh.scm
index 7fbbe383e5..427f0e4739 100644
--- a/gnu/services/ssh.scm
+++ b/gnu/services/ssh.scm
@@ -363,7 +363,13 @@ (define-record-type* <openssh-configuration>
   ;; proposed in <https://bugs.gnu.org/27155>.  Keep it internal/undocumented
   ;; for now.
   (%auto-start?          openssh-auto-start?
-                         (default #t)))
+                         (default #t))
+
+  ;; Boolean
+  ;; XXX: The service should really listen via IPv4 and IPv6 by default, but
+  ;; this is a little tricky. See https://issues.guix.gnu.org/55335
+  (listen-via-ipv6?      openssh-listen-via-ipv6?
+                         (default #f)))
 
 (define %openssh-accounts
   (list (user-group (name "sshd") (system? #t))
@@ -535,7 +541,10 @@ (define openssh-command
          (start #~(if (defined? 'make-inetd-constructor)
                       (make-inetd-constructor
                        (append #$openssh-command '("-i"))
-                       (make-socket-address AF_INET INADDR_ANY
+                       (make-socket-address #$(if (openssh-listen-via-ipv6? config)
+                                                  #~AF_INET6
+                                                  #~AF_INET)
+                                            INADDR_ANY
                                             #$port-number)
                        #:max-connections #$max-connections)
                       (make-forkexec-constructor #$openssh-command
-- 
2.34.0
J
J
Jack Hill wrote on 13 May 17:23 +0200
(name . Christopher Baines)(address . mail@cbaines.net)(address . 55335@debbugs.gnu.org)
alpine.DEB.2.21.2205131110120.11587@marsh.hcoop.net
Thanks for looking into this! Does this fix work for you (I assume so)? I
tried a simpler patch to use a v6 socket:


--- a/gnu/services/ssh.scm
+++ b/gnu/services/ssh.scm
@@ -535,7 +535,7 @@ (define openssh-command
(start #~(if (defined? 'make-inetd-constructor)
(make-inetd-constructor
(append #$openssh-command '("-i"))
- (make-socket-address AF_INET INADDR_ANY
+ (make-socket-address AF_INET6 INADDR_ANY
#$port-number)
#:max-connections #$max-connections)
(make-forkexec-constructor #$openssh-command

and that does indeed produce a v6 socket that also accepts v4 connection.
The output of `ss -tulpen`:

tcp LISTEN 0 10
*:22 *:* users:(("shepherd",pid=1,fd=29)) ino:1522146 sk:2001 cgroup:/ v6only:0 <->

However, while ssh is now able to connect to the socket, something is
going wrong in the handoff to sshd. I see the following message printed on
the console when trying to connect:

Uncaught exception in task:
In fibers.scm:
150:8 4 (_)
In shepherd/service.scm:
1435:21 3 (_)
1280:30 2 (socket-address->string #(10 # 37896 0 0))
In unknown file:
1 (inet-ntop 2 42540578165178177408896616697074944157)
In ice-9/boot-9.scm:
1685:16 0 (raise-exception _ #:continualbe? _)
ice-9/boot-9.scm:1685:16: In procecure raise-exception:
Value our of range 0 to 18446744073709551615: 42540578165178177408896616697074944157

Best,
Jack
J
J
Jack Hill wrote on 13 May 17:25 +0200
(name . Christopher Baines)(address . mail@cbaines.net)(address . 55335@debbugs.gnu.org)
alpine.DEB.2.21.2205131125260.11587@marsh.hcoop.net
On Fri, 13 May 2022, Jack Hill wrote:

Toggle quote (43 lines)
> Thanks for looking into this! Does this fix work for you (I assume so)? I
> tried a simpler patch to use a v6 socket:
>
>
> --- a/gnu/services/ssh.scm
> +++ b/gnu/services/ssh.scm
> @@ -535,7 +535,7 @@ (define openssh-command
> (start #~(if (defined? 'make-inetd-constructor)
> (make-inetd-constructor
> (append #$openssh-command '("-i"))
> - (make-socket-address AF_INET INADDR_ANY
> + (make-socket-address AF_INET6 INADDR_ANY
> #$port-number)
> #:max-connections #$max-connections)
> (make-forkexec-constructor #$openssh-command
>
> and that does indeed produce a v6 socket that also accepts v4 connection. The
> output of `ss -tulpen`:
>
> tcp LISTEN 0 10 *:22 *:*
> users:(("shepherd",pid=1,fd=29)) ino:1522146 sk:2001 cgroup:/ v6only:0 <->
>
> However, while ssh is now able to connect to the socket, something is going
> wrong in the handoff to sshd. I see the following message printed on the
> console when trying to connect:
>
> Uncaught exception in task:
> In fibers.scm:
> 150:8 4 (_)
> In shepherd/service.scm:
> 1435:21 3 (_)
> 1280:30 2 (socket-address->string #(10 # 37896 0 0))
> In unknown file:
> 1 (inet-ntop 2 42540578165178177408896616697074944157)
> In ice-9/boot-9.scm:
> 1685:16 0 (raise-exception _ #:continualbe? _)
> ice-9/boot-9.scm:1685:16: In procecure raise-exception:
> Value our of range 0 to 18446744073709551615:
> 42540578165178177408896616697074944157
>
> Best,
> Jack

I should have specified: now neither v4 or v6 work.

Best,
Jack
L
L
Ludovic Courtès wrote on 14 May 10:42 +0200
Re: bug#55335: openssh-service no longer listens on IPv6
(name . Jack Hill)(address . jackhill@jackhill.us)
87r14wh5eb.fsf_-_@gnu.org
Hi,

Jack Hill <jackhill@jackhill.us> skribis:

Toggle quote (17 lines)
> However, while ssh is now able to connect to the socket, something is
> going wrong in the handoff to sshd. I see the following message
> printed on the console when trying to connect:
>
> Uncaught exception in task:
> In fibers.scm:
> 150:8 4 (_)
> In shepherd/service.scm:
> 1435:21 3 (_)
> 1280:30 2 (socket-address->string #(10 # 37896 0 0))
> In unknown file:
> 1 (inet-ntop 2 42540578165178177408896616697074944157)
> In ice-9/boot-9.scm:
> 1685:16 0 (raise-exception _ #:continualbe? _)
> ice-9/boot-9.scm:1685:16: In procecure raise-exception:
> Value our of range 0 to 18446744073709551615: 42540578165178177408896616697074944157

Oops, another embarrassing bug, now fixed in Shepherd commit
27dd4df9d83e9c59668bd9e6ca05a3a4983e10d2.

Thanks,
Ludo’.
L
L
Ludovic Courtès wrote on 14 May 16:16 +0200
(name . Christopher Baines)(address . mail@cbaines.net)(address . 55335@debbugs.gnu.org)
87zgjkfbcl.fsf_-_@gnu.org
Hi,

Christopher Baines <mail@cbaines.net> skribis:

Toggle quote (15 lines)
> Prior to the switch to the openssh service using inetd, you could connect over
> IPv4 or IPv6. With inetd, you can only connect over IPv4, meaning for machines
> with just IPv6 connectivity, you can't connect.
>
> Switching to listing via IPv6 should support IPv4 connections, as Linux is
> capable of translating IPv4 connections to IPv6. I think there's a risk that
> switching to this approach will affect some uses of the openssh
> service. Therefore, this commit makes this a configuration option, which is #f
> by default.
>
> In the future, once it's easy to do so via Guile and the shepherd, it would be
> good if two sockets were used, one for IPv4 and one for IPv6. That's not easy
> at the moment, as the IPv6 socket conflicts with the IPv4 one, due to the
> translation behaviour described above.

Yes, I was going to suggest turning the ‘address’ argument of
‘make-inetd-constructor’ into ‘addresses’ (plural), with backward
compatibility. For sshd, we’d do:

(make-inetd-constructor
(append #$openssh-command '("-i"))
(list (make-socket-address AF_INET INADDR_ANY #$port-number)
(make-socket-address AF_INET6 INADDR_ANY #$port-number)))

It’s not that simple, due to the v6-to-v4 translation you mention:

Toggle snippet (12 lines)
scheme@(guile-user)> (define v4 (make-socket-address AF_INET INADDR_ANY 5555))
scheme@(guile-user)> (define v6 (make-socket-address AF_INET6 INADDR_ANY 5555))
scheme@(guile-user)> (define s4 (socket AF_INET SOCK_STREAM 0))
scheme@(guile-user)> (define s6 (socket AF_INET6 SOCK_STREAM 0))
scheme@(guile-user)> (bind s4 v4)
scheme@(guile-user)> (bind s6 v6)
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure bind: Address already in use

Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.

… but it can be made to work:

Toggle snippet (9 lines)
scheme@(guile-user)> (define s4 (socket AF_INET SOCK_STREAM 0))
scheme@(guile-user)> (define s6 (socket AF_INET6 SOCK_STREAM 0))
scheme@(guile-user)> (define IPPROTO_IPV6 41)
scheme@(guile-user)> (define IPV6_V6ONLY 26)
scheme@(guile-user)> (setsockopt s6 IPPROTO_IPV6 IPV6_V6ONLY 1)
scheme@(guile-user)> (bind s4 v4)
scheme@(guile-user)> (bind s6 v6)

So ‘make-inetd-constructor’ would interpret v6 addresses as v6-only,
with the understanding that the caller has to explicitly pass all the
relevant addresses.

Thoughts?

We could release Shepherd shortly with the fixes that have accumulated.
The service in Guix would be able to use it, but only if PID 1 is recent
enough.

Thanks,
Ludo’.
L
L
Ludovic Courtès wrote on 14 May 17:49 +0200
(name . Christopher Baines)(address . mail@cbaines.net)(address . 55335@debbugs.gnu.org)
87lev4f71w.fsf_-_@gnu.org
Hi,

Christopher Baines <mail@cbaines.net> skribis:

Toggle quote (6 lines)
> Switching to listing via IPv6 should support IPv4 connections, as Linux is
> capable of translating IPv4 connections to IPv6. I think there's a risk that
> switching to this approach will affect some uses of the openssh
> service. Therefore, this commit makes this a configuration option, which is #f
> by default.

[...]

Toggle quote (6 lines)
> + (make-socket-address #$(if (openssh-listen-via-ipv6? config)
> + #~AF_INET6
> + #~AF_INET)
> + INADDR_ANY
> #$port-number)

Thinking about it, what do you think is the risk of using AF_INET6
unconditionally?

AFAICS it just works. Is there a switch somewhere that might affect
that behavior?

(I still think that changing ‘make-inetd-constructor’ to accept multiple
addresses is a better fix longer-term, but if we can have this quick
fix, that’s great.)

Ludo’.
J
J
Jack Hill wrote on 14 May 21:09 +0200
(name . Ludovic Courtès)(address . ludo@gnu.org)
alpine.DEB.2.21.2205141505570.11587@marsh.hcoop.net
On Sat, 14 May 2022, Ludovic Courtès wrote:

Toggle quote (8 lines)
> Hi,
>
> Thinking about it, what do you think is the risk of using AF_INET6
> unconditionally?
>
> AFAICS it just works. Is there a switch somewhere that might affect
> that behavior?

Yes, I beleive that it's in sysctl:

```
$ sysctl net.ipv6.bindv6only
net.ipv6.bindv6only = 0
```

If enabled, the v6 socket wouldn't work for v4. Disabled is the default on
Guix System. I don't know what would happen if v6 were disabled entirely.
Hopefully that's not something we have to worry about in 2022.

HTH,
Jack
C
C
Christopher Baines wrote on 17 May 23:33 +0200
(name . Ludovic Courtès)(address . ludo@gnu.org)(address . 55335@debbugs.gnu.org)
877d6jonb4.fsf@cbaines.net
Ludovic Courtès <ludo@gnu.org> writes:

Toggle quote (21 lines)
> Hi,
>
> Christopher Baines <mail@cbaines.net> skribis:
>
>> Switching to listing via IPv6 should support IPv4 connections, as Linux is
>> capable of translating IPv4 connections to IPv6. I think there's a risk that
>> switching to this approach will affect some uses of the openssh
>> service. Therefore, this commit makes this a configuration option, which is #f
>> by default.
>
> [...]
>
>> + (make-socket-address #$(if (openssh-listen-via-ipv6? config)
>> + #~AF_INET6
>> + #~AF_INET)
>> + INADDR_ANY
>> #$port-number)
>
> Thinking about it, what do you think is the risk of using AF_INET6
> unconditionally?

I'm assuming that configuration that looks at the IP addresses will be
affected, e.g. things like:

Match Address 127.0.0.*
PubkeyAuthentication yes

But this is just a guess.

Toggle quote (7 lines)
> AFAICS it just works. Is there a switch somewhere that might affect
> that behavior?
>
> (I still think that changing ‘make-inetd-constructor’ to accept multiple
> addresses is a better fix longer-term, but if we can have this quick
> fix, that’s great.)

I'm also interested in a quick fix. I'd like to either make the switch
to using AF_INET6 unconditionally, or push the patch I sent for allowing
it to be used through a configuration option.
-----BEGIN PGP SIGNATURE-----

iQKlBAEBCgCPFiEEPonu50WOcg2XVOCyXiijOwuE9XcFAmKEFP9fFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDNF
ODlFRUU3NDU4RTcyMEQ5NzU0RTBCMjVFMjhBMzNCMEI4NEY1NzcRHG1haWxAY2Jh
aW5lcy5uZXQACgkQXiijOwuE9XdS6A/+JZQVoML9XUz6m4UV10FlF88x9jyVqk4P
Ikfq7S6Ure3rdskN6nFGeDMqkIpxdvRsfW2BkwczVCNcdzHm+olJyoj1+VNYvdF6
vqQe52X1hIkiK82SbxSXjiB1jOqsRGdpGGBHEcJC4UcWz/LvJM1ciEf9ocnISnXL
vHU871TRzTpZPouZHeCiefg1hZ453X8Rky+9qSP6iv+Cm+7dRgToCwIwW50Bp84V
2N73aFohLTYTtq65tWqx8szvLHlAp1V8k4vwQtcfiSK5UtUU+snJPXGkXZkhD2HB
LQ2hEOZVnWHVW/PqxaReqn3bxGn1wy64B0OypMWNLkpZJ7RoBmLf1RU5VlccdUDV
BVbE1BV03XSBSjNiVuOKTny5NCSCvrL6orHJMW6asjVaJDdWf5FqI7FgBQTzSam/
ZkKcEyivNVvY0E+rNYGYxGIwjaOz1GCyWjUap0kjNRElvxNSRd/34UJdTVUvWSWk
lTJnFnOq4Uh2EbYNEgjmCwVh54iPXgmux0khT/2gqqUaA1W1EToY9tUSz5Exr8pj
bqsQpBUWdeA7ixpxY7wriMtZ8f/H7xzRnpOTVzT/FyM1O5lgl2yMyphyOx4jCyd+
k2+3xH3nkI0jzP8Y+Y5kr9UF2nzmQeb07zX663B0ol7AQ8TMUCELEp3KXvOIjgLA
o0xp2vaSExo=
=wDqi
-----END PGP SIGNATURE-----

L
L
Ludovic Courtès wrote on 18 May 11:30 +0200
(name . Christopher Baines)(address . mail@cbaines.net)(address . 55335@debbugs.gnu.org)
871qwr427t.fsf@gnu.org
Hi Chris,

Christopher Baines <mail@cbaines.net> skribis:

Toggle quote (4 lines)
> I'm also interested in a quick fix. I'd like to either make the switch
> to using AF_INET6 unconditionally, or push the patch I sent for allowing
> it to be used through a configuration option.

How about going with unconditional AF_INET6 for now? That way we
wouldn’t have that new option that will likely become a no-op
afterwards.

I’ll propose changes to the Shepherd soon, so we can fix it for good.

Thanks,
Ludo’.
L
L
Ludovic Courtès wrote on 18 May 16:06 +0200
[PATCH Shepherd 2/3] tests: Update inetd tests to pass a list of endpoints.
(address . 55335@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20220518140645.17144-3-ludo@gnu.org
* tests/inetd.sh: Pass 'make-inetd-constructor' a list of endpoints.
---
tests/inetd.sh | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)

Toggle diff (30 lines)
diff --git a/tests/inetd.sh b/tests/inetd.sh
index ef18800..83037bf 100644
--- a/tests/inetd.sh
+++ b/tests/inetd.sh
@@ -42,15 +42,18 @@ cat > "$conf" <<EOF
  (make <service>
    #:provides '(test-inetd)
    #:start (make-inetd-constructor %command
-                                   (make-socket-address AF_INET
-                                                        INADDR_LOOPBACK
-                                                        $PORT))
+                                   (list
+                                    (endpoint (make-socket-address
+                                               AF_INET
+                                               INADDR_LOOPBACK
+                                               $PORT))))
    #:stop  (make-inetd-destructor))
  (make <service>
    #:provides '(test-inetd-unix)
    #:start (make-inetd-constructor %command
-                                   (make-socket-address AF_UNIX
-                                                        "$service_socket")
+                                   (list
+                                    (endpoint (make-socket-address
+                                               AF_UNIX "$service_socket")))
                                    #:max-connections 5)
    #:stop  (make-inetd-destructor)))
 
-- 
2.36.0
L
L
Ludovic Courtès wrote on 18 May 16:06 +0200
[PATCH Shepherd 1/3] service: 'make-inetd-constructor' accepts a list of endpoints.
(address . 55335@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20220518140645.17144-2-ludo@gnu.org
* modules/shepherd/service.scm (endpoint->listening-socket)
(open-sockets): New procedures.
(make-inetd-constructor): Change 'address' parameter to 'endpoints'.
Mark #:socket-style, #:socket-owner, #:socket-group, #:socket-directory-permissions,
and #:listen-backlog as deprecated.
[spawn-child-service, accept-clients]: Take 'server-address' parameter
and use it. Update callers.
Add compatibility later for when ENDPOINTS is an address.
(make-inetd-destructor): Adjust.
(make-systemd-destructor)[endpoint->listening-socket, open-sockets]:
Remove.
Adjust to new return value of 'open-sockets'.
* NEWS: Mention it.
---
NEWS | 13 ++
doc/shepherd.texi | 54 ++++----
modules/shepherd/service.scm | 255 +++++++++++++++++------------------
3 files changed, 161 insertions(+), 161 deletions(-)

Toggle diff (450 lines)
diff --git a/NEWS b/NEWS
index c51e8e2..4ce7a48 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,19 @@ Copyright © 2013-2014, 2016, 2018-2020, 2022 Ludovic Courtès <ludo@gnu.org>
 Please send Shepherd bug reports to bug-guix@gnu.org.
 
 * Changes in version 0.9.1
+** ‘make-inetd-constructor’ now accepts a list of endpoints
+
+In 0.9.0, ‘make-inetd-constructor’ would take a single address as returned by
+‘make-socket-address’.  This was insufficiently flexible since it didn’t let
+you have an inetd service with multiple endpoints.  ‘make-inetd-constructor’
+now takes a list of endpoints, similar to what ‘make-systemd-constructor’
+already did.
+
+For compatibility with 0.9.0, if the second argument to
+‘make-systemd-constructor’ is an address, it is automatically converted to a
+list of endpoints.  This behavior will be preserved for at least the whole
+0.9.x series.
+
 ** ‘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/doc/shepherd.texi b/doc/shepherd.texi
index 3d01186..9efc48e 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -1082,11 +1082,28 @@ services, specifically those in @code{nowait} mode where the daemon is
 passed the newly-accepted socket connection while @command{shepherd} is
 in charge of listening.
 
-@deffn {procedure} make-inetd-constructor @var{command} @var{address}
-  [#:service-name-stem _] [#:requirements '()] @
-  [#:socket-style SOCK_STREAM] [#:listen-backlog 10] @
+Listening endpoints for such services are described as records built
+using the @code{endpoint} procedure:
+
+@deffn {procedure} endpoint @var{address} [#:name "unknown"] @
+  [#:style SOCK_STREAM] [#:backlog 128] @
   [#:socket-owner (getuid)] [#:socket-group (getgid)] @
-  [#:socket-directory-permissions #o755] @
+  [#:socket-directory-permissions #o755]
+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_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
+permissions for its parent directory.
+@end deffn
+
+The inetd service constructor takes a command and a list of such
+endpoints:
+
+@deffn {procedure} make-inetd-constructor @var{command} @var{endpoints}
+  [#:service-name-stem _] [#:requirements '()] @
   [#:max-connections (default-inetd-max-connections)] @
   [#:user #f] @
   [#:group #f] @
@@ -1095,14 +1112,9 @@ in charge of listening.
   [#:file-creation-mask #f] [#:create-session? #t] @
   [#:resource-limits '()] @
   [#:environment-variables (default-environment-variables)]
-Return a procedure that opens a socket listening to @var{address}, an
-object as returned by @code{make-socket-address}, and accepting connections in
-the background; the @var{listen-backlog} argument is passed to @var{accept}.
-
-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
-permissions for its parent directory.
+Return a procedure that opens sockets listening to @var{endpoints}, a list
+of objects as returned by @code{endpoint}, and accepting connections in the
+background.
 
 Upon a client connection, a transient service running @var{command} is
 spawned.  Only up to @var{max-connections} simultaneous connections are
@@ -1133,24 +1145,6 @@ environment (see below), which usually checks them using the libsystemd
 or libelogind
 @uref{https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html,
 client library helper functions}.
-
-Listening endpoints for such services are described as records built
-using the @code{endpoint} procedure:
-
-@deffn {procedure} endpoint @var{address} [#:name "unknown"] @
-  [#:style SOCK_STREAM] [#:backlog 128] @
-  [#:socket-owner (getuid)] [#:socket-group (getgid)] @
-  [#:socket-directory-permissions #o755]
-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_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
-permissions for its parent directory.
-@end deffn
-
 The constructor and destructor for systemd-style daemons are described
 below.
 
diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index ded8283..e93466a 100644
--- a/modules/shepherd/service.scm
+++ b/modules/shepherd/service.scm
@@ -1225,6 +1225,90 @@ as argument, where SIGNAL defaults to `SIGTERM'."
   (lambda (ignored . args)
     (not (zero? (status:exit-val (system (apply string-append command)))))))
 
+
+;;;
+;;; Server endpoints.
+;;;
+
+;; Endpoint of a systemd-style or inetd-style service.
+(define-record-type <endpoint>
+  (make-endpoint name address style backlog owner group permissions)
+  endpoint?
+  (name        endpoint-name)                          ;string
+  (address     endpoint-address)                       ;socket address
+  (style       endpoint-style)                         ;SOCK_STREAM, etc.
+  (backlog     endpoint-backlog)                       ;integer
+  (owner       endpoint-socket-owner)                  ;integer
+  (group       endpoint-socket-group)                  ;integer
+  (permissions endpoint-socket-directory-permissions)) ;integer
+
+(define* (endpoint address
+                   #:key (name "unknown") (style SOCK_STREAM)
+                   (backlog 128)
+                   (socket-owner (getuid)) (socket-group (getgid))
+                   (socket-directory-permissions #o755))
+  "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_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
+permissions for its parent directory."
+  (make-endpoint name address style backlog
+                 socket-owner socket-group
+                 socket-directory-permissions))
+
+(define (endpoint->listening-socket endpoint)
+  "Return a listening socket for ENDPOINT."
+  (match endpoint
+    (($ <endpoint> name address style backlog
+                   owner group permissions)
+     (let* ((sock    (non-blocking-port
+                      (socket (sockaddr:fam address) style 0)))
+            (owner   (if (integer? owner)
+                         owner
+                         (passwd:uid (getpwnam owner))))
+            (group   (if (integer? group)
+                         group
+                         (group:gid (getgrnam group)))))
+       (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
+       (when (= AF_UNIX (sockaddr:fam address))
+         (mkdir-p (dirname (sockaddr:path address)) permissions)
+         (chown (dirname (sockaddr:path address)) owner group)
+         (catch-system-error (delete-file (sockaddr:path address))))
+
+       (bind sock address)
+       (listen sock backlog)
+
+       (when (= AF_UNIX (sockaddr:fam address))
+         (chown sock owner group)
+         (chmod sock #o666))
+
+       sock))))
+
+(define (open-sockets endpoints)
+  "Return a list of listening sockets corresponding to ENDPOINTS, in the same
+order as ENDPOINTS.  If opening of binding one of them fails, an exception is
+thrown an previously-opened sockets are closed."
+  (let loop ((endpoints endpoints)
+             (result   '()))
+    (match endpoints
+      (()
+       (reverse result))
+      ((head tail ...)
+       (let ((sock (catch 'system-error
+                     (lambda ()
+                       (endpoint->listening-socket head))
+                     (lambda args
+                       ;; When opening one socket fails, abort the whole
+                       ;; process.
+                       (for-each (match-lambda
+                                   ((_ . socket) (close-port socket)))
+                                 result)
+                       (apply throw args)))))
+         (loop tail (cons sock result)))))))
+
 
 ;;;
 ;;; Inetd-style services.
@@ -1311,18 +1395,13 @@ as argument, where SIGNAL defaults to `SIGTERM'."
   ;; service.
   (make-parameter 100))
 
-(define* (make-inetd-constructor command address
+(define* (make-inetd-constructor command endpoints
                                  #:key
                                  (service-name-stem
                                   (match command
                                     ((program . _)
                                      (basename program))))
                                  (requirements '())
-                                 (socket-style SOCK_STREAM)
-                                 (socket-owner (getuid))
-                                 (socket-group (getgid))
-                                 (socket-directory-permissions #o755)
-                                 (listen-backlog 10)
                                  (max-connections
                                   (default-inetd-max-connections))
                                  (user #f)
@@ -1333,15 +1412,17 @@ as argument, where SIGNAL defaults to `SIGTERM'."
                                  (create-session? #t)
                                  (environment-variables
                                   (default-environment-variables))
-                                 (resource-limits '()))
-  "Return a procedure that opens a socket listening to @var{address}, an
-object as returned by @code{make-socket-address}, and accepting connections in
-the background; the @var{listen-backlog} argument is passed to @var{accept}.
+                                 (resource-limits '())
 
-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
-permissions for its parent directory.
+                                 ;; Deprecated.
+                                 (socket-style SOCK_STREAM)
+                                 (socket-owner (getuid))
+                                 (socket-group (getgid))
+                                 (socket-directory-permissions #o755)
+                                 (listen-backlog 10))
+  "Return a procedure that opens sockets listening to @var{endpoints}, a list
+of objects as returned by @code{endpoint}, and accepting connections in the
+background.
 
 Upon a client connection, a transient service running @var{command} is
 spawned.  Only up to @var{max-connections} simultaneous connections are
@@ -1370,7 +1451,7 @@ The remaining arguments are as for @code{make-forkexec-constructor}."
                   connection-count (canonical-name service))
     (default-service-termination-handler service status))
 
-  (define (spawn-child-service connection client-address)
+  (define (spawn-child-service connection server-address client-address)
     (let* ((name    (child-service-name))
            (service (make <service>
                       #:provides (list name)
@@ -1387,7 +1468,7 @@ The remaining arguments are as for @code{make-forkexec-constructor}."
                                #:file-creation-mask file-creation-mask
                                #:create-session? create-session?
                                #:environment-variables
-                               (append (inetd-variables address
+                               (append (inetd-variables server-address
                                                         client-address)
                                    environment-variables)
                                #:resource-limits resource-limits)
@@ -1396,7 +1477,7 @@ The remaining arguments are as for @code{make-forkexec-constructor}."
       (register-services service)
       (start service)))
 
-  (define (accept-clients sock)
+  (define (accept-clients server-address sock)
     ;; Return a thunk that accepts client connections from SOCK.
     (lambda ()
       (let loop ()
@@ -1407,7 +1488,7 @@ The remaining arguments are as for @code{make-forkexec-constructor}."
                  (local-output
                   (l10n "Maximum number of ~a clients reached; \
 rejecting connection from ~:[~a~;~*local process~].")
-                  (socket-address->string address)
+                  (socket-address->string server-address)
                   (= AF_UNIX (sockaddr:fam client-address))
                   (socket-address->string client-address))
                  (close-port connection))
@@ -1415,46 +1496,35 @@ rejecting connection from ~:[~a~;~*local process~].")
                  (set! connection-count (+ 1 connection-count))
                  (local-output
                   (l10n "Accepted connection on ~a from ~:[~a~;~*local process~].")
-                  (socket-address->string address)
+                  (socket-address->string server-address)
                   (= AF_UNIX (sockaddr:fam client-address))
                   (socket-address->string client-address))
-                 (spawn-child-service connection client-address)))))
+                 (spawn-child-service connection
+                                      server-address client-address)))))
         (loop))))
 
   (lambda args
-    (let ((owner (if (integer? socket-owner)
-                     socket-owner
-                     (passwd:uid (getpwnam socket-owner))))
-          (group (if (integer? socket-group)
-                     socket-group
-                     (group:gid (getgrnam socket-group))))
-          (sock  (socket (sockaddr:fam address) socket-style 0)))
-      (catch #t
-        (lambda ()
-          (non-blocking-port sock)
-          (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
-
-          (when (= AF_UNIX (sockaddr:fam address))
-            (mkdir-p (dirname (sockaddr:path address))
-                     socket-directory-permissions)
-            (chown (dirname (sockaddr:path address)) owner group)
-            (catch-system-error (delete-file (sockaddr:path address))))
-          (bind sock address)
-          (when (= AF_UNIX (sockaddr:fam address))
-            (chown sock owner group)
-            (chmod sock #o666))
-
-          (listen sock listen-backlog)
-          (spawn-fiber (accept-clients sock))
-          sock)
-        (lambda args
-          (close-port sock)
-          (apply throw args))))))
+    (let* ((endpoints (match endpoints
+                        (((? endpoint?) ...) endpoints)
+                        (address (list (endpoint address
+                                                 #:style socket-style
+                                                 #:backlog listen-backlog
+                                                 #:socket-owner socket-owner
+                                                 #:socket-group socket-group
+                                                 #:socket-directory-permissions
+                                                 socket-directory-permissions)))))
+           (sockets   (open-sockets endpoints)))
+      (for-each (lambda (endpoint socket)
+                  (spawn-fiber
+                   (accept-clients (endpoint-address endpoint)
+                                   socket)))
+                endpoints sockets)
+      sockets)))
 
 (define (make-inetd-destructor)
   "Return a procedure that terminates an inetd service."
-  (lambda (sock)
-    (close-port sock)
+  (lambda (sockets)
+    (for-each close-port sockets)
     #f))
 
 
@@ -1462,35 +1532,6 @@ rejecting connection from ~:[~a~;~*local process~].")
 ;;; systemd-style services.
 ;;;
 
-;; Endpoint of a systemd-style service.
-(define-record-type <endpoint>
-  (make-endpoint name address style backlog owner group permissions)
-  endpoint?
-  (name        endpoint-name)                          ;string
-  (address     endpoint-address)                       ;socket address
-  (style       endpoint-style)                         ;SOCK_STREAM, etc.
-  (backlog     endpoint-backlog)                       ;integer
-  (owner       endpoint-socket-owner)                  ;integer
-  (group       endpoint-socket-group)                  ;integer
-  (permissions endpoint-socket-directory-permissions)) ;integer
-
-(define* (endpoint address
-                   #:key (name "unknown") (style SOCK_STREAM)
-                   (backlog 128)
-                   (socket-owner (getuid)) (socket-group (getgid))
-                   (socket-directory-permissions #o755))
-  "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_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
-permissions for its parent directory."
-  (make-endpoint name address style backlog
-                 socket-owner socket-group
-                 socket-directory-permissions))
-
 (define (wait-for-readable ports)
   "Suspend the current task until one of @var{ports} is available for
 reading."
@@ -1538,58 +1579,10 @@ The colon-separated list of endpoint names.
 
 This must be paired with @code{make-systemd-destructor}."
   (lambda args
-    (define (endpoint->listening-socket endpoint)
-      ;; Return a listening socket for ENDPOINT.
-      (match endpoint
-        (($ <endpoint> name address style backlog
-                       owner group permissions)
-         (let* ((sock    (non-blocking-port
-                          (socket (sockaddr:fam address) style 0)))
-                (owner   (if (integer? owner)
-                             owner
-                             (passwd:uid (getpwnam owner))))
-                (group   (if (integer? group)
-                             group
-                             (group:gid (getgrnam group)))))
-           (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
-           (when (= AF_UNIX (sockaddr:fam address))
-             (mkdir-p (dirname (sockaddr:path address)) permissions)
-             (chown (dirname (sockaddr:path address)) owner group)
-             (catch-system-error (delete-file (sockaddr:path address))))
-
-           (bind sock address)
-           (listen sock backlog)
-
-           (when (= AF_UNIX (sockaddr:fam address))
-             (chown sock owner group)
-             (chmod sock #o666))
-
-           sock))))
-
-    (define (open-sockets addresses)
-      (let loop ((endpoints endpoints)
-                 (result   '()))
-        (match endpoints
-          (()
-           (reverse result))
-          ((head tail ...)
-           (let ((sock (catch 'system-error
-                         (lambda ()
-                           (endpoint->listening-socket head))
-                         (lambda args
-                           ;; When opening one socket fails, abort the whole
-                           ;; process.
-                           (for-each (match-lambda
-                                       ((_ . socket) (close-port socket)))
-                                     result)
-                           (apply throw args)))))
-             (loop tail
-                   `((,(endpoint-name head) . ,sock) ,@result)))))))
-
-    (let* ((sockets   (open-sockets endpoints))
-           (ports     (match sockets
-                        (((names . ports) ...)
-                         ports)))
+    (let* ((ports     (open-sockets endpoints))
+           (sockets   (map (lambda (endpoint socket)
+                             (cons (endpoint-name endpoint) socket))
+                           endpoints ports))
            (variables (list (string-append "LISTEN_FDS="
                                            (number->string (length sockets)))
                             (string-append "LISTEN_FDNAMES="
-- 
2.36.0
L
L
Ludovic Courtès wrote on 18 May 16:06 +0200
[PATCH Shepherd 3/3] Interpret AF_INET6 endpoints as IPv6-only.
(address . 55335@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20220518140645.17144-4-ludo@gnu.org
* 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(+)

Toggle diff (232 lines)
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
L
L
Ludovic Courtès wrote on 18 May 16:06 +0200
[PATCH Shepherd 0/3] Endpoints for inetd services + IPv6-only endpoints
(address . 55335@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20220518140645.17144-1-ludo@gnu.org
Hi!

Here’s a couple of changes to the Shepherd addressing the concerns

• ‘make-inetd-constructor’ now accepts a list of endpoints, like
‘make-systemd-constructor’, instead of a single address.

• AF_INET6 endpoints are now interpreted as IPv6-only.

I’ve pushed this in the Shepherd repo as ‘wip-inetd-ipv6’. You’re
welcome to test that branch in Guix System VMs or something.

Lemme know what you think! If it’s good, we can merge it and
release the Shepherd 0.9.1 with this and other fixes that have
accumulated.

Ludo’.

Ludovic Courtès (3):
service: 'make-inetd-constructor' accepts a list of endpoints.
tests: Update inetd tests to pass a list of endpoints.
Interpret AF_INET6 endpoints as IPv6-only.

NEWS | 24 +++
configure.ac | 12 ++
doc/shepherd.texi | 68 ++++----
modules/shepherd/service.scm | 274 +++++++++++++++++----------------
modules/shepherd/system.scm.in | 11 ++
tests/inetd.sh | 71 ++++++++-
6 files changed, 294 insertions(+), 166 deletions(-)


base-commit: 05f169e896ea6520a8daebee68e5844e605526c4
--
2.36.0
L
L
Ludovic Courtès wrote on 18 May 16:28 +0200
Re: bug#55335: openssh-service no longer listens on IPv6
(address . 55335@debbugs.gnu.org)
87h75m3of8.fsf_-_@gnu.org
Ludovic Courtès <ludo@gnu.org> skribis:

Toggle quote (23 lines)
> + (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))

I should point out that this new test hangs with Fibers 1.1.0; we need
this fix:


I’ve contacted Aleix to see if we could release Fibers 1.1.1. Otherwise
we’ll use a snapshot in Guix.

Ludo’.
L
L
Ludovic Courtès wrote 5 days ago
87a6b9qqi9.fsf@gnu.org
Hello!

With Shepherd 0.9.1 released, I believe Guix commit
d2b3400f79ffaed3357650307376ab69a7ec3b1b fixes this bug for good, also
adding a system test for SSH access over IPv6 (both with OpenSSH and
Dropbear).

Let me know if anything’s amiss!

Thanks,
Ludo’.
Closed
J
J
Jack Hill wrote 5 days ago
(name . Ludovic Courtès)(address . ludo@gnu.org)
alpine.DEB.2.21.2205221832510.11587@marsh.hcoop.net
On Sun, 22 May 2022, Ludovic Courtès wrote:

Toggle quote (12 lines)
> Hello!
>
> With Shepherd 0.9.1 released, I believe Guix commit
> d2b3400f79ffaed3357650307376ab69a7ec3b1b fixes this bug for good, also
> adding a system test for SSH access over IPv6 (both with OpenSSH and
> Dropbear).
>
> Let me know if anything’s amiss!
>
> Thanks,
> Ludo’.

It's working well for me, allowing connections over both v4 and v6. I have
another host that I can only access with a v6 via wireguard address, which
I haven't been able to upgrade yet. I don't anticipate any problems there
though.

Many thanks!
Jack
Closed
L
L
Ludovic Courtès wrote 4 days ago
(name . Jack Hill)(address . jackhill@jackhill.us)
878rqs74wr.fsf@gnu.org
Hi Jack,

Jack Hill <jackhill@jackhill.us> skribis:

Toggle quote (5 lines)
> It's working well for me, allowing connections over both v4 and v6. I
> have another host that I can only access with a v6 via wireguard
> address, which I haven't been able to upgrade yet. I don't anticipate
> any problems there though.

Good, thanks for reporting back!

Ludo’.
Closed
S
S
Simon Streit wrote 4 days ago
(address . 55335@debbugs.gnu.org)
yguilpw2rp0.fsf@netpanic.org
Ludovic Courtès <ludo@gnu.org> writes:

Toggle quote (2 lines)
> Let me know if anything’s amiss!

Looking all good. v4 and v6 connections are working now.
?