CERN Accelerating science

SLC6 (actually: since glibc 2.9) change in AI_PASSIVE (bind) PF_UNSPEC getaddrinfo preference

Type:

The short story

In the testing we performed on SLC5 (patched glibc 2.5) we were apparently relying on the classical pattern of two mistakes (or, better, asymmetries between IPv4 and IPv6) compensating to produce the correct (or, at least, expected) result of preferring dual-stack binding for services that bind and listen on TCP or UDP. When we upgraded to SLC6 (patched glibc 2.12) we inherited the fix of one of the asymmetries (appearing with this commit in glibc 2.9, to comply with RFC 4291 2.5.3), and we were left with the other, that comes straight out of RFC 3484 2.1 and 5/rule 6. The result is that services that used to bind on both stacks on SLC5 (e.g. the GridFTP server) started binding on IPv4 only, unless the symmetry between IPv4 and IPv6 is restored by installing this file as /etc/gai.conf.

The ugly details

  • The results of the libc getaddrinfocall get ordered by the rules of RFC 3484 section 5. Services that need to listen on a TCP/UDP port typically bind to the addresses that are returned by getaddrinfo, upon a request of
    ai_req.ai_flags = AI_PASSIVE; ai_req.ai_family = PF_UNSPEC;
    . The order of the results of this call in a given libc installation can be tested by running this test program.
  • Binding on the IPv6 “unspecified” (::) address usually results, with the hybrid IP stacks deployed in the testbed Linux installations, in binding to both IPv6 and IPv4.
  • There are services (all of the Globus services, including the GridFTP service) that bind to the first result of getaddrinfoonly. The only currently available way to have them bind and listen on IPv6 (and IPv4) is to have getaddrinfoprefer IPv6 in this AI_PASSIVE/PF_UNSPECcase.
  • getaddrinfoorders the results of the AI_PASSIVE/PF_UNSPECquery by applying the rules of RFC 3484 section 5 to these two pairs of addresses:
  Source address: Destination address:
IPv6 ::(all-zeros, unspecified) ::1(loopback/localhost)
IPv4 0.0.0.0(all-zeros, unspecified) 127.0.0.1(loopback/localhost)
  • Up to and including glibc 2.8 (actually until this May 14th, 2008 commit became effective) the IPv6 unspecified and localhost address were assigned the same scope (’14’), while the IPv4 unspecified address was given scope ‘14’ and the IPv4 localhost address was given scope ‘2’. This led the IPv6 address to be preferred by section 5, rule 2 of RFC 3484:
 1651       if (a1_dst_scope == a1_src_scope && a2_dst_scope != a2_src_scope)
 1652         return -1;

@@ -1409,7 +1323,10 @@

     {
       if (! IN6_IS_ADDR_MULTICAST (&in6->sin6_addr))
        {
-         if (IN6_IS_ADDR_LINKLOCAL (&in6->sin6_addr))
+         if (IN6_IS_ADDR_LINKLOCAL (&in6->sin6_addr)
+             /* RFC 4291 2.5.3 says that the loopback address is to be
+                treated like a link-local address.  */
+             || IN6_IS_ADDR_LOOPBACK (&in6->sin6_addr))
            scope = 2;
          else if (IN6_IS_ADDR_SITELOCAL (&in6->sin6_addr))
            scope = 5;
  • Consequently, the ‘getaddrinfo’ address ordering can now proceed down to section 5, rule 6 of RFC 3484. Now, the default labels specified in section 2 of the same RFC 3484 are as follows:
Prefix Precedence Label
::1/128 50 0
::/0 40 1
2002::/16 30 2
::/96 20 3
::ffff:0:0/96 10 4
  • Here, the localhost and unspecified IPv6 addresses are assigned different labels (0 and 3), while all of the IPv4 space, including localhost is assigned the same label (4). The asymmetry of dealing with the IPv4 and IPv6 unspecified and localhost address is back, and reversed, leading to prefer the IPv4 binding!
  • The IPv4 localhost address can be assigned to a different label (and therefore be handled in similar fashion to the IPv6 localhost address) by adding a rule to the getaddrinfoconfiguration file (/etc/gai.conf) as in this example: label ::ffff:7f00:0001/128 8
  • This way, the symmetry of dealing with IPv4 and IPv6 is restored, and IPv6 is preferred, as per the general default getaddrinfopreference. Unless there was a specific reason for the default label scheme in RFC 3484, it should probably be amended. This would preferable to either changing all server code to scan all the getaddrinforesults to prefer IPv6 when needed or configuring /etc/gai.confin all installations (though this could be done as part of distributing glibc, or for specific distributions such as Scientific Linux). There are several (1,2,3) tickets and threads open on this issue, but no action was taken so far on distributions, libc or the RFC. If this stall continues, server code patching may remain the only way forward.