caddy: ipv6: does not bind on ipv4 and ipv6 for sites that resolve to both

1. What version of Caddy are you running (caddy -version)?

0.8.3

2. What are you trying to do?

Serve a site that can be accessed using ipv4 and ipv6.

3. What is your entire Caddyfile?

http://ip6.nl:80 {
  bind ip6.nl
  root /var/www
}

4. How did you run Caddy (give the full command and describe the execution environment)?

ip6.nl resolves to 193.200.132.187 as well as [2a02:2308:10::c:19].

The server has both addresses configured (output of ip addr confirms this).

5. What did you expect to see?

$ netstat -lnp --inet6 | grep -F caddy

tcp6  0  0  2a02:2308:10::c:19:80  :::*  LISTEN  8888/caddy

6. What did you see instead (give full error messages and/or log)?

Caddy does not listen on tcp6, only on tcp (--inet).

7. How can someone who is starting from scratch reproduce this behavior as minimally as possible?

#!/bin/bash
# Linux

ip -6 addr add 2a02:2308:10::c:19 dev lo
ip -4 addr add 193.200.132.187 dev lo

# or add something like this to your *home router's* nameserver
cat >>/etc/hosts <<<EOF
193.200.132.187 ip6.nl
2a02:2308:10::c:19 ip6.nl
EOF

cat >/tmp/Caddyfile-ds <<EOF
http://ip6.nl:80 {
  bind ip6.nl
  root /var/www
}
EOF

caddy -conf Caddyfile-ds &
sleep 1
netstat -lnpa | grep -F caddy
# tcp6, or only tcp?

# ----
kill $!
sed -i -e '/ip6.nl/d' /etc/hosts
ip -6 addr del 2a02:2308:10::c:19 dev lo
ip -4 addr del 193.200.132.187 dev lo

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 4
  • Comments: 21 (7 by maintainers)

Commits related to this issue

Most upvoted comments

So you’re now suggesting that for only $9,000 US, we’d improve our chances of getting a very basic and expected feature of a web server in Caddy, despite the fact that (to the best of my knowledge) Go itself still doesn’t support multi-binding, in an open-source and community-contributed-and-supported project.

Wow. I have no words. I’m not certain any longer who the target audience for Caddy is, but it would certainly seem that anyone with more complex requirements than a basic, single-homed, IPv4-only development web server isn’t part of it.

The resolver returns two addresses, the ipv4 and the ipv6 one. Caddy goes with the first one (ipv4) and ignores the ipv6-one. (It’s really a flaw in Golang.)(Just listening on [::]:80 is a no-go on multi-homed servers, or several sites.)

Without this Caddy is not fully »ipv6 compliant«, and falling behind Nginx or Apache httpd.

Yes, I am suggesting using http.Serve with two or more Listener. Due to possible shared state I recommend not just starting two servers in two goroutines. (Yes: 1←L, No: L×(1←1))

This would most probably be achieved by an implementation along these lines:

  1. If directive bind gets more than one parameter, and/or…
  2. If and when a hostname resolves to more than one address, and:
    • for ipv4 addresses: for every such address that is in net.InterfaceAddrs()
    • for ipv6: for every such address that falls under the configured prefixes … create a new Listener, or reuse an existing one.
  3. Implement pattern fan in: Another MultiListener encapsultes them all, acts as the sole Listener we then pass to http.* (or any other server).

Primer on ops:

  • It’s a common scenario in datacenters that your server has network devices such as ext0, intra0, storage0 and so on. (It’s a good practice to rename a ext0 or en0p2 and the such according to their function.)
  • Unless a daemon can bind to a specific network interface (good!) you just avoid 0.0.0.0:PPPP and [::]:PPPP for that reason.
  • (Then, it’s not uncommon that you have multiple ipv4 addresses assigned to said ext0: For example, a machine-dependent one, and some free-floating ones (like, one for every customer).)
  • Instead of several /112 or /128 ipv6 addresses (which is sometimes done for free-floating ones (the largest DC operator in Austria does this)) the server gets an entire ipv6 /64 subnet. Therefore the subnet check in step (2) above.

Websites for ipv4+ipv6:

# zone — ip6.nl

@ IN A 193.200.132.187
@ IN AAAA 2a02:2308:10::c:19
www IN A 193.200.132.187
www IN AAAA 2a02:2308:10::c:19
# nginx.conf

listen 193.200.132.187:80;
listen [2a02:2308:10::c:19]:80 ipv6only=on;

(Yes, one-trick–pony installations can get away with listen [::]:80 ipv6only=off;.)

@rhester72

So you’re now suggesting that for only $9,000 US

It’s $900 monthly – roughly the value of one day’s consulting/contracting work if you count hours. That’s totally appropriate to get a feature like this.

we’d improve our chances

If you go this route, what you’re also paying for is a re-adjustment of the authors’ priorities. I’ve already said this is something Caddy will get, it’s just a matter of when.

getting a very basic and expected feature of a web server in Caddy despite the fact that (to the best of my knowledge) Go itself still doesn’t support multi-binding,

Then it’s not “very basic” 😉 There be dragons in network stacks. But this feature is not super hard either. I suspect an extra loop will be needed in the function that groups site configs by listen address to create multiple listeners. Go may not support multi-binding in a single call to Listen() in the way you/we think about it, but there’s nothing stopping us from creating multiple listeners each listening on a different bind address.

in an open-source and community-contributed-and-supported project.

Exactly. I would love to have your contributions to make it better (and the only cost then is your time). Honestly, I would prefer this. 👍

I’m not certain any longer who the target audience for Caddy is

That’s why we’re in charge of it – we have the vision and we know where Caddy is going, because I and the other maintainers try to keep close tabs on what the community is doing with it. We can’t do everything at once. That’s why we appreciate the feedback, the interaction, and your involvement!

We use bind (and have to use bind) with IP-addresses because we’re on a multihomed server (multiple IP addresses), and use round-robin DNS which means that resolving our hostname will return IP addresses from more than one server.

So, what we need is to be able to specify multiple IP addresses with bind:

bind 203.0.113.15, 2001:DB8:1234:5678:abcd::15

Or to specify bind more than once:

bind 203.0.113.15
bind 2001:DB8:1234:5678:abcd::15

For now, I will use firejail (or socat, thanks @keks) as a workaround. But @mholt could you please give this issue the attention it deserves?

will this issue be getting any attention? lack of parity with nginx for just this issue has me a bit confused about all the recent “use caddy!” hype from high profile aggregators and bloggers.

Hi, I’m the owner of the ip6.nl domain and I maintain the server to which the associated IP addresses are assigned.

I was surprised to see that these were used in an example, by someone I don’t know. For the record, I would like to add to this discussion that ip6.nl does not use Caddy, and that the user wmark is not associated with ip6.nl in any way.

It is generally a bad idea to re-use actual internet domain names and public routable IP addresses in examples or for local tests. This is why the domain name example.org (RFC2606) and the IPv6 prefix 2001:db8::/32 (RFC3849) exist.

wmark, please only use official example domains and IP addresses, or your own.

@mholt I totally understand, so no arguing from me. It’s just that the project I’d like to run caddy for is one I support pro-bono - a small grass-roots, donation supported podcast, so there’s no money for things like this. I’ll just have to wait until someone else picks up the issue, especially as I am a sysadmin, not a coder.

FYI, I wouldn’t label this as easy/for-beginners: The good implementation will require some knowledge of OS (as well as networking!) fundamentals a beginner won’t have. Plus, testing will be tricky.

But I support labelling this as bug and not RFE.

I would also love to see this fixed. For now I use socat as a workaround:

sudo socat  tcp6-listen:80,bind=example.com,fork  tcp4-connect:example.com:80  &
sudo socat  tcp6-listen:443,bind=example.com,fork tcp4-connect:example.com:443 &

But seriously, this bug has been open for way too long.

I expected this to work, but it only listens on the last ‘bind’ statement. (I understand it working this way might be required to be able to override a default):

bind 2607:f238:3::222:0 bind 207.171.7.222

so alternatively having this work would be nice:

bind 2607:f238:3::222:0 207.171.7.222

There’s no way to use a set of specific IPs and have IPv4 and IPv6 work currently without duplicating the whole configuration.