[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#609527: /lib/libc.so.6: Parallel gethostbyname2_r() fails if DNS server answers the first query when receiving the second.



Here is the test code.

There are two programs, gethostbyname_threads.cpp and dnsserver.cpp.
Suggested compile command is in a comment at the top of each file.

Set up "dnsserver" as your DNS server (e.g. starting it as root and
modifying /etc/resolv.conf to use 127.0.0.1), and then run:

   gethostbyname_threads washingtonpost.com

(The dns server is extremely simpleminded and only knows to provide a
canned response to a very specific question.)

gethostbyname_threads will start two threads and look up the same
hostname in both threads.  It will then print out the result and exit.

The way the ifdefs in dnsserver.cpp is set up, it will wait for the
second request, immediately send a response to the first request and
then it will never send another packet.

Thus, I would expect the first gethostbyname2_r() call (in
gethostbyname_threads) to succeed and the second call to time out,
giving this output:

$ ./gethostbyname_threads washingtonpost.com
got: washingtonpost.com, IPv4: 12.129.147.65
gethostbyname2_r errored


Instead, both calls times out, and I get this output instead:

$ ./gethostbyname_threads washingtonpost.com
gethostbyname2_r errored
gethostbyname2_r errored

eirik


/*
g++ -o gethostbyname_threads gethostbyname_threads.cpp -lpthread

Permission to share code granted in Opera bug DSK-325405
 */

#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/socket.h>

void lookup(const char * hostname, int type);
void* lookup_both(void * hostname);

int main(int argc, const char ** argv)
{
    if (argc != 2)
    {
        printf("usage: gethostbyname_threads <hostname>\n");
        return 1;
    }

    const char * hostname = argv[1];

    pthread_t t1;
    pthread_t t2;
    pthread_create(&t1, 0, lookup_both, (void*)hostname);
    pthread_create(&t2, 0, lookup_both, (void*)hostname);
    void * r;
    pthread_join(t1, &r);
    pthread_join(t2, &r);
    return 0;
};

void * lookup_both(void * hostname)
{
    lookup((char*)hostname, AF_INET);
    return 0;
};


void print_hostent(struct hostent * res);

void lookup(const char * hostname, int type)
{
    int error = 0;
    struct hostent hostent;
    struct hostent *res = 0;
    const int BUFFER_SIZE=512;
    char buffer[BUFFER_SIZE];
    int err = gethostbyname2_r(hostname, type, &hostent,
                               buffer, BUFFER_SIZE,
                               &res, &error);
    if (err == ENOMEM || err == ENOBUFS)
        printf("gethostbyname2_r OOMed\n");
    else if (res)
        print_hostent(res);
    else
        printf("gethostbyname2_r errored\n");
};


void print_hostent(struct hostent * res)
{
    const char * type = "UNKNOWN";
    char addr[100];
    addr[0] = 0;
    if (res->h_addrtype == AF_INET)
    {
        type = "IPv4";
        unsigned char * a = (unsigned char*)res->h_addr_list[0];
        if (a != 0)
            snprintf(addr, 100, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]);
    }
    else if (res->h_addrtype == AF_INET6)
    {
        type = "IPv6";
        unsigned char * a = (unsigned char*)res->h_addr_list[0];
        if (a != 0)
        {
            int next = 0;
            for (int i = 0; i < res->h_length && next < 90; i++)
            {
                if (i != 0 && (i&1) == 0)
                {
                    addr[next] = ':';
                    next ++;
                };
                int len = snprintf(addr + next, 100 - next, "%02x", a[i]);
                next += len;
            };
        };
    };
    printf("got: %s, %s: %s\n", res->h_name, type, addr);
};
/*
g++ -o dnsserver dnsserver.cpp

Permission to share code granted in Opera bug DSK-325405
 */

#include <sys/types.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int run_dns_server();
int main(int argc, const char ** argv)
{
    return run_dns_server();
};


void handle_request(int sock, unsigned char * buf, size_t buflen, const struct sockaddr_in * addr);
void read_and_handle_single_request(int sock);
int run_dns_server()
{
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        fprintf(stderr, "Failed to create socket (errno: %s)\n", strerror(errno));
        return 1;
    };

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(53);
    saddr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sock, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)
    {
        close(sock);
        fprintf(stderr, "Failed to bind socket (errno: %s)\n", strerror(errno));
        return 1;
    };

    while (true)
    {
        read_and_handle_single_request(sock);
    };
};

void read_and_handle_single_request(int sock)
{
    const size_t bufsize = 1024;
    unsigned char actual_buf[bufsize];
    unsigned char * buf = actual_buf;

    struct sockaddr_in remoteaddr;
    socklen_t addrbuflen = sizeof(remoteaddr);
    ssize_t got = recvfrom(sock, buf, bufsize, MSG_TRUNC, (struct sockaddr*)&remoteaddr, &addrbuflen);
    if (got < 0)
    {
        fprintf(stderr, "Failed to recv (errno: %s), sleeping for a second\n", strerror(errno));
        sleep(1);
    }
    else if (got > bufsize)
    {
        fprintf(stderr, "Too large incoming packet, dropping (packet size: %d)\n", got);
    }
    else
    {
        handle_request(sock, buf, got, &remoteaddr);
    };
};


bool seen_request = false;
unsigned char query_id[2];
int request_count = 0;

void handle_request(int sock, unsigned char * buf, size_t buflen, const struct sockaddr_in * addr)
{
    {
        unsigned char * addr_buf = (unsigned char*)&addr->sin_addr;
        printf("got request from %d.%d.%d.%d:%d\n", addr_buf[0], addr_buf[1], addr_buf[2], addr_buf[3], ntohs(addr->sin_port));
    }

#if 1
    for (size_t i = 0; i < buflen; i++)
    {
        if (i % 16 == 0 && i > 0)
            printf("<--\n");
        if (buf[i] == '\\')
            printf("\\\\");
        else if (buf[i] >= 0x20 && buf[i] <= 0x7e)
            printf("%c", buf[i]);
        else
            printf("\\%02x", buf[i]);
    };
    printf("<--\n");
#endif


    const char * query_data = "\x01\x00\x00\x01\0\0\0\0\0\0\x0ewashingtonpost\x03" "com\0\0\x01\0\x01";
    int query_data_length = 34;
    if (buflen != query_data_length + 2)
    {
        printf("Unrecognized query (%d bytes instead of %d)\n", (int)buflen, query_data_length + 2);
        return;
    };
    if (memcmp(buf + 2, query_data, query_data_length) != 0)
    {
        // Unrecognized query
        printf("Query mismatch\n");
        for (int i = 0; i < query_data_length; i++)
            if (buf[i+2] != query_data[i])
                printf("byte %d: %02x != %02x\n", i+2, buf[i+2], query_data[i]);
        return;
    };

#if 0
    if (!seen_request)
    {
        printf("No request seen, dropping\n");
        query_id[0] = buf[0];
        query_id[1] = buf[1];
        seen_request = true;
        return;
    };
#endif

    request_count += 1;
#if 0
    if (request_count < 2)
    {
        printf("handle next request first\n");
        read_and_handle_single_request(sock);
    };
#endif
#if 0
    if (request_count != 2)
    {
        printf("dropping request\n");
        return;
    };
#endif
#if 0
    if (request_count != 1)
    {
        printf("dropping request\n");
        return;
    };
    printf("wait a bit\n");
    usleep(200000);
#endif
#if 1
    if (request_count == 1)
    {
        query_id[0] = buf[0];
        query_id[1] = buf[1];
    };
    if (request_count != 2)
    {
        printf("dropping request\n");
        return;
    };
#endif

    unsigned char response[512];
    // transaction id
#if 0
    response[0] = buf[0];
    response[1] = buf[1];
#else
    response[0] = query_id[0];
    response[1] = query_id[1];
#endif
    memcpy(response + 2, "\x81\x80\x00\x01\x00\x01\x00\x06\x00\x07\x0e\x77\x61\x73\x68\x69\x6e\x67\x74\x6f\x6e\x70\x6f\x73\x74\x03\x63\x6f\x6d\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\xa8\x6e\x00\x04\x0c\x81\x93\x41\xc0\x0c\x00\x02\x00\x01\x00\x01\x51\x2e\x00\x14\x05\x70\x64\x6e\x73\x31\x08\x75\x6c\x74\x72\x61\x64\x6e\x73\x03\x6e\x65\x74\x00\xc0\x0c\x00\x02\x00\x01\x00\x01\x51\x2e\x00\x16\x05\x70\x64\x6e\x73\x36\x08\x75\x6c\x74\x72\x61\x64\x6e\x73\x02\x63\x6f\x02\x75\x6b\x00\xc0\x0c\x00\x02\x00\x01\x00\x01\x51\x2e\x00\x08\x05\x70\x64\x6e\x73\x32\xc0\x46\xc0\x0c\x00\x02\x00\x01\x00\x01\x51\x2e\x00\x15\x05\x70\x64\x6e\x73\x35\x08\x75\x6c\x74\x72\x61\x64\x6e\x73\x04\x69\x6e\x66\x6f\x00\xc0\x0c\x00\x02\x00\x01\x00\x01\x51\x2e\x00\x14\x05\x70\x64\x6e\x73\x33\x08\x75\x6c\x74\x72\x61\x64\x6e\x73\x03\x6f\x72\x67\x00\xc0\x0c\x00\x02\x00\x01\x00\x01\x51\x2e\x00\x08\x05\x70\x64\x6e\x73\x34\xc0\xbd\xc0\x40\x00\x01\x00\x01\x00\x01\x0f\x6b\x00\x04\xcc\x4a\x6c\x01\xc0\x40\x00\x1c\x00\x01\x00\x01\x0f\x6b\x00\x10\x20\x01\x05\x02\xf3\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x82\x00\x01\x00\x01\x00\x01\x0f\x6b\x00\x04\xcc\x4a\x6d\x01\xc0\xd7\x00\x01\x00\x01\x00\x01\x0f\x6b\x00\x04\xc7\x07\x45\x01\xc0\xd7\x00\x1c\x00\x01\x00\x01\x0f\x6b\x00\x10\x20\x01\x05\x02\x46\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x96\x00\x01\x00\x01\x00\x01\x0f\x6e\x00\x04\xcc\x4a\x72\x01\xc0\x60\x00\x01\x00\x01\x00\x01\x0f\x6b\x00\x04\xcc\x4a\x73\x01", 357);
    printf("Sending response\n");
    if (sendto(sock, response, 359, 0, (struct sockaddr*)addr, sizeof(*addr)) != 359)
        fprintf(stderr, "Failed to send response (errno: %s)\n", strerror(errno));
    seen_request = false;
    return;
};

Reply to: