Search code examples
phpdnsround-robin

PHP not respecting RoundRobin in DNS


I have problem with making PHP to respect RoundRobin-enabled DNS entry. The entry (say for example it's domain.example.com) has three possible IP addresses assigned. RoundRobin works (tested with ping, telnet, wget, etc.). Unfortunately, when using PHP SOAP extension, and even plain file_get_contents it always connects to the first IP address specified in DNS. Surprisingly, gethostbyname function sees RoundRobin perfectly fine. I've put a file outputing 1 or 2 or 3 in each server and executed the script on another server few times:

var_dump(file_get_contents('http://domain.example.com/test.html'));
var_dump(gethostbyname('domain.example.com'));

The first line always prints "1" (from the first IP address). The second line randomly outputs one of three possible IP addresses.

The question: Does anyone had similar problem? How can I force PHP to respect RoundRobin in DNS, at least when making a SOAP requests?

EDIT There is no DNS cache present, and no proxy. As mentioned, ping, telnet, wget etc. works OK on the same server where the test script is placed.


Solution

  • We have recently encountered a similar problem as yours. We found out that file_get_contents() eventually calls getaddrinfo(), which implements some sorting algorithms from RFC3484.

    The piece of snippet from php source code

    PHPAPI int php_network_getaddresses(const char *host, int socktype, struct sockaddr ***sal, zend_string **error_string)
    {
        // skip...
    
        if ((n = getaddrinfo(host, NULL, &hints, &res))) {
            if (error_string) {
                *error_string = strpprintf(0, "php_network_getaddresses: getaddrinfo failed: %s", PHP_GAI_STRERROR(n));
                php_error_docref(NULL, E_WARNING, "%s",     ZSTR_VAL(*error_string));
            } else {
                php_error_docref(NULL, E_WARNING, "php_network_getaddresses: getaddrinfo failed: %s", PHP_GAI_STRERROR(n));
            }
        return 0;
        }
    
        //skip...
    }
    

    We are still figuring out what is a better approach to deal with the problem.

    Edit:

    Section 6 of RFC3484 specifies the destination address selection algorithm, and rule 9 is related to IPv4.

    Rule 9: Use longest matching prefix.

    When DA and DB belong to the same address family (both are IPv6 or both are IPv4): If CommonPrefixLen(DA, Source(DA)) > CommonPrefixLen(DB, Source(DB)), then prefer DA. Similarly, if CommonPrefixLen(DA, Source(DA)) < CommonPrefixLen(DB, Source(DB)), then prefer DB.

    Lets say we have the source address 192.168.1.100/24 and four candidate destination addresses 192.168.1.33/24, 192.168.1.44/24, 192.168.2.55/24, 192.168.2.66/24.

    With the above addresses representing in binary format

    S  192.168.1.100    11000000.10101000.00000001.01100100
    -------------------------------------------------------
    DA 192.168.1.33     11000000.10101000.00000001.00100001
    DB 192.168.1.44     11000000.10101000.00000001.00101100
    DC 192.168.2.55     11000000.10101000.00000010.00110111
    DD 192.168.2.66     11000000.10101000.00000010.01000010
    

    You can see that

    CommonPrefixLen(DA, S) == CommonPrefixLen(DB, S) == 25 >
    CommonPrefixLen(DC, S) == CommonPrefixLen(DD, S) == 22
    

    So DA or DB, which ever comes first in the original DNS query, would be chosen as the final destination in glibc implementation.