I have tried to make the cupsd socket-activation reliable on Debian through systemd.socket configuration. It works for the most parts besides failing on ipv4-only hosts, see https://bugs.debian.org/747073 .
I think I've pretty much tried all combinations of ListenStream, BindIPv6Only and BindToDevice stanzas and ended up quick-patching a workaround in CUPS, see https://www.cups.org/str.php?L4491 .
I've settled on the following statements:
My understanding of the problem is that there is no way to write a systemd.socket unit file that will reliably bind a port in the correct mode on all available localhost IPs no matter if ipv6 is enabled on the host or not. As I've phrased in the CUPS bug above, it seems to me that either you get an AF_INET6 on the IPv6 addresses (with BindIPv6Only=ipv6-only) or you get AF_INET6 on both the IPv6 addresses and the IPv4 addresses (with BindIPv6Only=both). It feels wrong to bind as IPv6 on IPv4 addresses indeed.
What am I doing wrong? Shouldn't systemd bind (and hand) AF_INET sockets on IPv4 addresses? Where should this "disagreement" be fixed (is CUPS right to refuse AF_INET6 on ipv4 addresses; is systemd right in binding these)? Am I totally off in the above analysis (I'd really like to understand the core issue… and get this fixed!).
Sorry, but I cannot parse what yuou are actually trying to do? Can you precisely describe what you want to do, and what exactly doesn't work?
(For clarity, I'm the current CUPS maintainer for Debian).
Let me try to rephrase then: CUPS got patched in Debian using a variation of your patch  to support socket activation. I'm trying to find a socket configuration that enables CUPS to be activated on both ipv6-enabled and ipv4-only hosts on all local IPs (that's the subject of https://bugs.debian.org/747073), as well as on all public-facing IPs
Now, with the following unit file:
… on ipv6-enabled hosts, this will let CUPS be activated when accessing http://[::1]:631/, http://localhost:631/ or http://127.0.0.1:631/. In the two latter cases though, CUPS will not allow the connection (with 'Bad Request' and 'Forbidden' respectively) with messages like these in the error_log (debug2 level):
Accepted from [v1.::127.0.0.1]:631 (IPv6)
Request from "[v1.::127.0.0.1]" using invalid Host: field "localhost:631"
With BindIPv6Only=ipv6-only, CUPS will not get activated on localhost or 127.0.0.1, of course.
On ipv4-only hosts, BindIPv6Only will get discarded and CUPS will get activated _and_ accept the connections on localhost or 127.0.0.1 with "Accepted from 127.0.0.1:631 (IPv4).
Now, as I understand it, on ipv6-enabled hosts, systemd will only create IPv6 (AF_INET6 ?) sockets, even on IPv4 addresses, which then causes confusion on the CUPS side (hence the funny [v1.::127.0.0.1] pseudo-IPv6 address coming from httpAddrString ), which doesn't expect IPv4 addresses over AF_INET6 (see the https://www.cups.org/str.php?L4491 ), thereby refusing the connection.
Now, a way around is to specify specifically ListenStream=127.0.0.1:631, but then adding ListenStream=[::1]:631, which will (of course) fail the unit activation on ipv4-only hosts. There doesn't seem to have a way to ask systemd to bind the socket if the address exists.
So, if I understand it correctly, I think either systemd is wrong to bind sockets on IPv4 addresses with AF_INET6 and handing IPv4 addresses to CUPS or CUPS is wrong when checking the address over AF_INET6 sockets. The CUPS upstream author seems to think the problem is in systemd (see the CUPS bug linked above).
(By the way, this is nowhere near my expertise field, so feel free to correct any of my misconceptions…)
Friendly ping? Did my explanation make any sense?
I'm not aware of it being possible to do what you're requesting with a single bind (that is, resulting in a single socket file descriptor).
I believe you need two different binds, and systemd socket units support that if you're prepared to get both fd=3 and fd=4. systemd is simply doing the best it can through one bind.
So, systemd currently doesn't support this scheme nicely. We should however.
The ipv6only compat stuff is you really should set to "on" for cups. CUPS appears to require that, and that's probably a really good idea for it.
Now, to achieve what you want, you should use:
both lines listed in the same .socket file. The first line will listen on the IPv4 port 631 on all interfaces, the second line on the IPv6 port 631. Now, there's one problem with this: the latter line will cause the .socket unit to fail on kernels where ipv6 is not compiled in. The question now is what to do about this.
We have two options:
1) introduce a syntax "ListenStream=-[::]:631" (i.e. note the "-"), which would tell systemd that failure to listen on that port shall not be considered fatal. This would then be similar to our "ExectStart=-/bin/false" syntax, where the dash encodes that the exit cause for the process shall be ignored.
2) change the logic of .socket units to succeed if we managed to listen on at least *one* of all of its specified ports. Currently we fail if at least one listening fails, and we'd turn this around so that we'd succeed if at least one listening succeeds.
I think option #1 is preferable here though, simply because we normally should guarantee stability of the order of fds we pass to the processes we invoke. By specifying the dash, the user tells us that it is OK if an fd might be missing when passed to the process. But just doing that by default sounds really ill advised.
Does that make sense?
Renaming the bug accordingly.
It does very much, thanks! I had come to the same conclusion earlier this year: https://bugs.debian.org/747073#44 . I see this feature as blocking the proper socket-activation of CUPS, which I will then disable for the next Debian release, unless this gets into systemd fast enough. :)