There is a slight issue when it comes to IPv6 and FreeBSD jails: DAD can place a curfew on the services binding to an IPv6 address within a jail. shame

What happens is that IPv6 has something called DAD: Duplicate Address Detection. This is a procedure for determining if an IPv6 address is available on the local link. The procedure has a delay of sorts. In the case of FreeBSD, it is the sysctl net.inet6.ip6.dad_count which defaults to 1.

From the sysctl description: Number of ICMPv6 NS messages sent during duplicate address detection

This causes a slight delay before the address is available for binding and can be seen using ifconfig. If the system is still waiting, the word tentative will be listed after the address.

Unfortunately, services will attempt binding to the IPv6 address before the delay is complete. This causes them to either fail to start or to not bind to that address. sshd is a good example of this. It will successfully bind to the IPv4 address in the jail but not the IPv6 address: error: Bind to port 22 on :: failed: Can't assign requested address.

Fortunately (yeah!), there are a few options:

  • Disable DAD for the whole system: net.inet6.ip6.dad_count=0

    This is an easy solution and should not cause problems in setups where the administrator controls everything. It is the “Sledgehammer” approach.

  • Disable DAD for the NIC: ifconfig <interface> no_dad

    This is lighter weight than the whole system approach as it only limits the scope to the NIC. However, it affects the whole NIC. I will call it the “Hammer” approach.

  • Take a nap

    Within the /etc/jail.conf (or /etc/jail.conf.d/<jail>)

    exec.start = "/bin/sleep 5";
    exec.start += "/bin/sh /etc/rc";
    

    A sleep can be made prior to the start of a jail. Adding a sleep prior to the execution of /etc/rc gives time for DAD to finish a nap and let the address pass its test. This can work, but has two problems:

    • It is a race condition because DAD may not have finished his nap.
    • There is a delay for the jail to start when it does not need to wait that long. Having multiple jails starting in serial can take much longer this way.

    I name this “Nap Time”.

  • Watch for the nap to be over

    Watch DAD finish his nap or be impatient and continue. This is the method I am now using. It is similar to the sleep method yet can finish much sooner. It monitors the specified interface for up to 10 seconds waiting for all the tentative labels to disappear.

    exec.start += "for i in $(jot 10); do $(ifconfig ${interface} inet6 | \
        grep -q tentative) || break; sleep 1; done";
    exec.start += "/bin/sh /etc/rc";
    interface = "em0";
    

    It too has issues but tends to work well and faster:

    • This is a bit loose as there could be multiple addresses on the same interface.
    • This does not check against which address is tentative.

    What should I name this? Hmm… How about “Out After Curfew”? ;)