How to Connect to a Multi-Homed Server Over TCP

With the world wide deployment of IPv6 in parallel with IPv4, it has become apparent that a traditional connection loop is no longer good enough.

In fact, this is a large part of the reason why Google is white listing resolvers and Yahoo only wants to return to AAAA records to DNS queries made over IPv6. The traditional connection loop does not behave well in the presence of some network errors. It introduces excessive delays when there are good alternate addresses to use.

This has not been a big problem in the past, as most sites have been single homed, so there were no alternate addresses to try. But with the deployment of IPv6 along side IPv4, almost all sites will become multi-homed, with a minimum of two addresses, so now is the time to fix this problem.

With a traditional connection loop, each address returned from gethostbyname() or getaddrinfo() is tried in turn and the application then stalls until the connection attempt succeeds or fails. Then the next address is tried, etc. While most successful connections take less than 500 milliseconds, a failed connection attempt can take up to half a minute before we move onto the next address, adding a lot of unnecessary latency.

The connect call can take 30 seconds to fail and if the first address you try is broken you can end up waiting a long time until you try the next address.

    fd = -1;
    for (ai = ai0; ai; ai = ai->ai_next) {
        fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
        if (fd < 0)
            continue;
        if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) {
            close(fd);
            fd = -1;
            continue;
        }
        /* success */ 
        break; 
    }

You see this sort of connection loop in most text books on socket programming and in the man page for getaddrinfo().

The first observation to be made is that we can make these connection attempts in parallel, which works but leads to lots of unnecessary connections being made if we start them all at once. Most of the time, the first connection attempt will succeed, so we should give it an opportunity to do so before making a second attempt.

The sample code attached takes the output of getaddrinfo() and tries each address in turn, waiting a decreasing amount of time between subsequent connection attempts. When one of the connection attempts completes, it will abort the others. The initial timeout is 500 milliseconds which is enough time to connect to a European server from Sydney, Australia using terrestrial paths.

Code samples for poll(), select() and pthread based C are included below.

Thread based sample code
/*
 * Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Initial timeout between connection attempts.  The smaller this is the
 * more embryonic connection attempts that will be made.  On each subsequent
 * connection attempt the timeout will be halved leading to all connection
 * attempts being initiated within 2 * TIMEOUT ms.
 *
 * 100 ms will let most intra continent connections succeed without a
 *    embryonic connection.
 * 500 ms well let most intercontinental connections succeed without a
 *    embryonic connection.
 */
#define TIMEOUT 500 /* 500 ms */

#define TESTING 1

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>

#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct common {
    pthread_mutex_t *mutex;
    pthread_cond_t *cond;
    int *count;
    int *fd;
};

struct state {
    struct addrinfo *addrinfo;
    struct common *common;
};

static void
fatal(char *format, ...) {
    va_list ap;

    va_start(ap, format);
    vfprintf(stderr, format, ap);
    va_end(ap);
    abort();
}

static void
connect_to_address_cleanup(void *arg) {
    int *fd = arg;

    if (*fd != -1)
        close(*fd);
}

static void *
connect_to_address(void *arg) {
    struct state *state = arg;
    struct addrinfo *addrinfo = state->addrinfo;
    struct common *common = state->common;
    int fd = -1, n;

    /* Ensure that fd is closed if we are canceled. */
    pthread_cleanup_push(connect_to_address_cleanup, &fd);
    fd = socket(addrinfo->ai_family, addrinfo->ai_socktype,
            addrinfo->ai_protocol);
    if (fd < 0) {
        /*
         * If AI_ADDRCONFIG is not supported we will get EAFNOSUPPORT
         * returned.  Silently ignore it.
         */
        if (errno != EAFNOSUPPORT)
            perror("socket");
    } else if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) == -1) {
        perror("connect");
        close(fd);
        fd = -1;
    } 
    /* If we get here we want the rest of the thread to complete. */
    n = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    if (n != 0)
        fatal("pthread_setcancelstate: %s", strerror(n));
    n = pthread_mutex_lock(common->mutex);
    if (n != 0)
        fatal("pthread_mutex_lock: %s", strerror(n));
    if (fd != -1 && *common->fd == -1) {
        /* Record success. */
        *common->fd = fd;
        fd = -1;
    }
    *common->count -= 1;
    n = pthread_mutex_unlock(common->mutex);
    if (n != 0)
        fatal("pthread_mutex_unlock: %s", strerror(n));
    pthread_cleanup_pop(1);
    /* Signal that we are done. */
    n = pthread_cond_signal(common->cond);
    if (n != 0)
        fatal("pthread_cond_signal: %s", strerror(n));
    pthread_exit(NULL);
}

int
connect_to_host(struct addrinfo *res0) {
    struct addrinfo *res;
    int fd = -1, n, i, j, count;
    pthread_t *threads;
    struct state *state;
    int timeout = TIMEOUT * 1000;
    struct timespec timespec;
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    struct common common;

    /*
     * Work out how many possible descriptors we could use.
     */
    for (res = res0, count = 0; res; res = res->ai_next)
        count++;
    threads = calloc(count, sizeof(*threads));
    state = calloc(count, sizeof(*state));
    if (threads == NULL || state == NULL) {
        perror("calloc");
        goto cleanup;
    }

    n = pthread_mutex_init(&mutex, NULL);
    if (n != 0) {
        fprintf(stderr, "pthread_mutex_init: %s", strerror(n));
        goto cleanup;
    }

    n = pthread_cond_init(&cond, NULL);
    if (n != 0) {
        fprintf(stderr, "pthread_cond_init: %s", strerror(n));
        goto cleanup_mutex;
    }

    common.fd = &fd;
    common.cond = &cond;
    common.count = &count;
    common.mutex = &mutex;

    /*
     * fd and count are protected by mutex.
     */
    for (res = res0, i = 0, count = 0; res; res = res->ai_next) {
        bool done;

        state[i].common = &common;
        state[i].addrinfo = res;
        
        n = pthread_create(&threads[i], NULL, connect_to_address,
                   &state[i]);
        if (n != 0)
            fprintf(stderr, "pthread_create: %s", strerror(n));
        else {
            i++;
            n = pthread_mutex_lock(&mutex);
            if (n != 0)
                fatal("pthread_mutex_lock: %s", strerror(n));
            count++;
            n = pthread_mutex_unlock(&mutex);
            if (n != 0)
                fatal("pthread_mutex_unlock: %s", strerror(n));
        }
        done = false;
        do {
            n = pthread_mutex_lock(&mutex);
            if (n != 0)
                fatal("pthread_mutex_lock: %s", strerror(n));
            /* No outstanding threads? */
            if (count == 0) {
                /* Are we done? */
                if (fd != -1 || res->ai_next == NULL)
                    done = true;
                n = pthread_mutex_unlock(&mutex);
                if (n != 0)
                    fatal("pthread_mutex_unlock: %s",
                          strerror(n));
                break;
            }
            if (res->ai_next != NULL) {
                struct timeval tv;

                n = gettimeofday(&tv, NULL);
                if (n != 0)
                    fatal("gettimeofday: %s\n",
                          strerror(errno));
                timespec.tv_sec = tv.tv_sec;
                timespec.tv_nsec = (tv.tv_usec + timeout) *
                           1000;
                while (timespec.tv_nsec >= 1000000000) {
                    timespec.tv_nsec -= 1000000000;
                    timespec.tv_sec += 1;
                }
                n = pthread_cond_timedwait(&cond, &mutex,
                                   &timespec);
            } else
                n = pthread_cond_wait(&cond, &mutex);

            if (n == ETIMEDOUT)
                timeout >>= 1;
            else if (n != 0)
                fatal("pthread_cond_%swait: %s\n",
                      res->ai_next != NULL ? "timed" : "",
                      strerror(n));
            if (fd != -1 || (count == 0 && res->ai_next == NULL))
                done = true;
            n = pthread_mutex_unlock(&mutex);
            if (n != 0)
                fatal("pthread_mutex_unlock: %s", strerror(n));
        } while (!done && res->ai_next == NULL);
        if (done)
            break;
    }

    /* Shutdown and tidy up all the threads we started. */
    for (j = 0; j < i; j++) {
        n = pthread_cancel(threads[j]);
        if (n != 0 && n != ESRCH)
            fatal("pthread_cancel: %s\n", strerror(n));
        n = pthread_join(threads[j], NULL);
        if (n != 0)
            fatal("pthread_join: %s\n", strerror(n));
    }

    /* Cleanup the resources we used. */
    n = pthread_cond_destroy(&cond);
    if (n != 0)
        fatal("pthread_cond_destroy: %s", strerror(n));

 cleanup_mutex:
    n = pthread_mutex_destroy(&mutex);
    if (n != 0)
        fatal("pthread_mutex_destroy: %s", strerror(n));

 cleanup:
    /* Free everything. */
    if (threads) free(threads);
    if (state) free(state);

    return (fd);
}

#if TESTING
int
main(int argc, char **argv) {
    int fd, n;
    struct timeval then, now;
    struct addrinfo hints, *res0;
    const char *hostname, *servname;

    hostname  = "localhost";
    if (argv[1])
        hostname = argv[1];
    servname = "http";

    /*
     * Not all getaddrinfo() implementations support AI_ADDRCONFIG
     * even if it is defined.  Retry without it on EAI_BADFLAGS.
     */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
#ifdef AI_ADDRCONFIG
    hints.ai_flags = AI_ADDRCONFIG;
#endif

#ifdef AI_ADDRCONFIG
 again:
#endif
    n = getaddrinfo(hostname, servname, &hints, &res0);
    if (n != 0) {
#ifdef AI_ADDRCONFIG
        if (n == EAI_BADFLAGS && hints.ai_flags & AI_ADDRCONFIG) {
            hints.ai_flags &= ~AI_ADDRCONFIG;
            goto again;
        }
#endif
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(n));
        exit(1);
    }

    gettimeofday(&then, NULL);
    fd = connect_to_host(res0);
    gettimeofday(&now, NULL);
    freeaddrinfo(res0);
    now.tv_sec -= then.tv_sec;
    now.tv_usec -= then.tv_usec;
    while (now.tv_sec > 0) {
        now.tv_usec += 1000000;
        now.tv_sec -= 1;
    }
    fprintf(stderr, "connect_to_host(%s) -> %d in %d ms\n", hostname, fd,
        (int)now.tv_usec/1000);
    close(fd);
    exit(0);
}
#endif
Poll based sample code
/*
 * Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Initial timeout between connection attempts.  The smaller this is the
 * more embryonic connection attempts that will be made.  On each subsequent
 * connection attempt the timeout will be halved leading to all connection
 * attempts being initiated within 2 * TIMEOUT ms.
 *
 * 100 ms will let most intra continent connections succeed without a
 *    embryonic connection.
 * 500 ms well let most intercontinental connections succeed without a
 *    embryonic connection.
 */
#define TIMEOUT 500 /* 500 ms */

#define TESTING 1

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/time.h>

#include <netinet/in.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
connect_to_host(struct addrinfo *res0) {
    struct addrinfo *res;
    int fd = -1, n, i, j, flags, count;
    struct pollfd *fds;
    int timeout = TIMEOUT;

    /*
     * Work out how many possible descriptors we could use.
     */
    for (res = res0, count = 0; res; res = res->ai_next)
        count++;
    fds = calloc(count, sizeof(*fds));
    if (fds == NULL) {
        perror("calloc");
        goto cleanup;
    }

    for (res = res0, i = 0, count = 0; res; res = res->ai_next) {
        fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (fd == -1) {
            /*
             * If AI_ADDRCONFIG is not supported we will get
             * EAFNOSUPPORT returned.  Behave as if the address
             * was not there.
             */
            if (errno != EAFNOSUPPORT)
                perror("socket");
            else if (res->ai_next != NULL)
                continue;
        } else if ((flags = fcntl(fd, F_GETFL)) == -1) {
            perror("fcntl");
            close(fd);
        } else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
            perror("fcntl");
            close(fd);
        } else if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
            if (errno != EINPROGRESS) {
                perror("connect");
                close(fd);
            } else {
                /*
                 * Record the information for this descriptor.
                 */
                fds[i].fd = fd;
                fds[i].events = POLLERR | POLLHUP |
                        POLLIN | POLLOUT;
                count++;
                i++;
            }
        } else  {
            /*
             * We connected without blocking.
             */
            goto done;
        }

        if (count == 0)
            continue;

        do {
            if (res->ai_next == NULL)
                timeout = -1;

            n = poll(fds, i, timeout);
            if (n == 0) {
                timeout >>= 1;
                break;
            }
            if (n < 0) {
                if (errno == EAGAIN || errno == EINTR)
                    continue;
                perror("poll");
                fd = -1;
                goto done;
            }
            for (j = 0; j < i; j++) {
                if (fds[j].fd == -1 || fds[j].events == 0 ||
                    fds[j].revents == 0)
                    continue;
                fd = fds[j].fd;
                if (fds[j].revents & POLLHUP) {
                    close(fd);
                    fds[j].fd = -1;
                    fds[j].events = 0;
                    count--;
                    continue;
                }
                /* Connect succeeded. */
                goto done;
            }
        } while (timeout == -1 && count != 0);
    }

    /* We failed to connect. */
    fd = -1;

 done:
    /* Close all other descriptors we have created. */
    for (j = 0; j < i; j++)
        if (fds[j].fd != fd && fds[j].fd != -1) {
            close(fds[j].fd);
        }

    if (fd != -1) {
        /* Restore default blocking behaviour.  */
        if ((flags = fcntl(fd, F_GETFL)) != -1) {
            flags &= ~O_NONBLOCK;
            if (fcntl(fd, F_SETFL, flags) == -1)
                perror("fcntl");
        } else
            perror("fcntl");
    }

 cleanup:
    /* Free everything. */
    if (fds != NULL) free(fds);

    return (fd);
}

#if TESTING
int
main(int argc, char **argv) {
    int fd, n;
    struct timeval then, now;
    struct addrinfo hints, *res0;
    const char *hostname, *servname;

    hostname  = "localhost";
    if (argv[1])
        hostname = argv[1];
    servname = "http";

    /*
     * Not all getaddrinfo() implementations support AI_ADDRCONFIG
     * even if it is defined.  Retry without it on EAI_BADFLAGS.
     */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
#ifdef AI_ADDRCONFIG
    hints.ai_flags = AI_ADDRCONFIG;
#endif

#ifdef AI_ADDRCONFIG
 again:
#endif
    n = getaddrinfo(hostname, servname, &hints, &res0);
    if (n != 0) {
#ifdef AI_ADDRCONFIG
        if (n == EAI_BADFLAGS && hints.ai_flags & AI_ADDRCONFIG) {
            hints.ai_flags &= ~AI_ADDRCONFIG;
            goto again;
        }
#endif
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(n));
        exit(1);
    }

    gettimeofday(&then, NULL);
    fd = connect_to_host(res0);
    gettimeofday(&now, NULL);
    freeaddrinfo(res0);
    now.tv_sec -= then.tv_sec;
    now.tv_usec -= then.tv_usec;
    while (now.tv_sec > 0) {
        now.tv_usec += 1000000;
        now.tv_sec -= 1;
    }
    fprintf(stderr, "connect_to_host(%s) -> %d in %d ms\n", hostname, fd,
        (int)now.tv_usec/1000);
    close(fd);
    exit(0);
}
#endif
Select based sample code
/*
 * Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Initial timeout between connection attempts.  The smaller this is the
 * more embryonic connection attempts that will be made.  On each subsequent
 * connection attempt the timeout will be halved leading to all connection
 * attempts being initiated within 2 * TIMEOUT ms.
 *
 * 100 ms will let most intra continent connections succeed without a
 *    embryonic connection.
 * 500 ms well let most intercontinental connections succeed without a
 *    embryonic connection.
 */
#define TIMEOUT 500 /* 500 ms */

#define TESTING 1

#if TIMEOUT > 999
#define TIMEOUT 999 /* select() doesn't like tv_usec > 999999. */
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>

#include <netinet/in.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
connect_to_host(struct addrinfo *res0) {
    struct addrinfo *res;
    int fd = -1, n, i, j, flags, count, max = -1, *fds;
    struct timeval *timeout, timeout0 = { 0, TIMEOUT * 1000};
    fd_set fdset, wrset;

    /*
     * Work out how many possible descriptors we could use.
     */
    for (res = res0, count = 0; res; res = res->ai_next)
        count++;
    fds = calloc(count, sizeof(*fds));
    if (fds == NULL) {
        perror("calloc");
        goto cleanup;
    }
    FD_ZERO(&fdset);
    for (res = res0, i = 0, count = 0; res; res = res->ai_next) {
        fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (fd == -1) {
            /*
             * If AI_ADDRCONFIG is not supported we will get
             * EAFNOSUPPORT returned.  Behave as if the address
             * was not there.
             */
            if (errno != EAFNOSUPPORT)
                perror("socket");
            else if (res->ai_next != NULL)
                continue;
        } else if (fd >= FD_SETSIZE) {
            close(fd);
        } else if ((flags = fcntl(fd, F_GETFL)) == -1) {
            perror("fcntl");
            close(fd);
        } else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
            perror("fcntl");
            close(fd);
        } else if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
            if (errno != EINPROGRESS) {
                perror("connect");
                close(fd);
            } else {
                /*
                 * Record the information for this descriptor.
                 */
                fds[i] = fd;
                FD_SET(fd, &fdset);
                if (max == -1 || fd > max)
                    max = fd;
                count++;
                i++;
            }
        } else  {
            /*
             * We connected without blocking.
             */
            goto done;
        }

        if (count == 0)
            continue;

        assert(max != -1);
        do {
            if (res->ai_next != NULL)
                timeout = &timeout0;
            else
                timeout = NULL;

            /* The write bit is set on both success and failure. */
            wrset = fdset;
            n = select(max + 1, NULL, &wrset, NULL, timeout);
            if (n == 0) {
                timeout0.tv_usec >>= 1;
                break;
            }
            if (n < 0) {
                if (errno == EAGAIN || errno == EINTR)
                    continue;
                perror("select");
                fd = -1;
                goto done;
            }
            for (fd = 0; fd <= max; fd++) {
                if (FD_ISSET(fd, &wrset)) {
                    socklen_t len;
                    int err;
                    for (j = 0; j < i; j++)
                        if (fds[j] == fd)
                            break;
                    assert(j < i);
                    /*
                     * Test to see if the connect
                     * succeeded.
                     */
                    len = sizeof(err);
                    n = getsockopt(fd, SOL_SOCKET,
                               SO_ERROR, &err, &len);
                    if (n != 0 || err != 0) {
                        close(fd);
                        FD_CLR(fd, &fdset);
                        fds[j] = -1;
                        count--;
                        continue;
                    }
                    /* Connect succeeded. */
                    goto done;
                }
            }
        } while (timeout == NULL && count != 0);
    }

    /* We failed to connect. */
    fd = -1;

 done:
    /* Close all other descriptors we have created. */
    for (j = 0; j < i; j++)
        if (fds[j] != fd && fds[j] != -1) {
            close(fds[j]);
        }

    if (fd != -1) {
        /* Restore default blocking behaviour.  */
        if ((flags = fcntl(fd, F_GETFL)) != -1) {
            flags &= ~O_NONBLOCK;
            if (fcntl(fd, F_SETFL, flags) == -1)
                perror("fcntl");
        } else
            perror("fcntl");
    }

 cleanup:
    /* Free everything. */
    if (fds) free(fds);

    return (fd);
}

#if TESTING
int
main(int argc, char **argv) {
    int fd, n;
    struct timeval then, now;
    struct addrinfo hints, *res0;
    const char *hostname, *servname;

    hostname  = "localhost";
    if (argv[1])
        hostname = argv[1];
    servname = "http";

    /*
     * Not all getaddrinfo() implementations support AI_ADDRCONFIG
     * even if it is defined.  Retry without it on EAI_BADFLAGS.
     */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
#ifdef AI_ADDRCONFIG
    hints.ai_flags = AI_ADDRCONFIG;
#endif

#ifdef AI_ADDRCONFIG
 again:
#endif
    n = getaddrinfo(hostname, servname, &hints, &res0);
    if (n != 0) {
#ifdef AI_ADDRCONFIG
        if (n == EAI_BADFLAGS && hints.ai_flags & AI_ADDRCONFIG) {
            hints.ai_flags &= ~AI_ADDRCONFIG;
            goto again;
        }
#endif
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(n));
        exit(1);
    }

    gettimeofday(&then, NULL);
    fd = connect_to_host(res0);
    gettimeofday(&now, NULL);
    freeaddrinfo(res0);
    now.tv_sec -= then.tv_sec;
    now.tv_usec -= then.tv_usec;
    while (now.tv_sec > 0) {
        now.tv_usec += 1000000;
        now.tv_sec -= 1;
    }
    fprintf(stderr, "connect_to_host(%s) -> %d in %d ms\n", hostname, fd,
        (int)now.tv_usec/1000);
    close(fd);
    exit(0);
}
#endif

Recent Posts

What's New from ISC

Encrypted DNS: Why all the drama about DOH?

Two years ago, interest in DNS Encryption was lukewarm… In May of 2018, ISC did a survey asking our users about their interest in deploying various DNS privacy measures, including both QNAME minimization and encryption (DNS over HTTP or DoH and DNS over TLS or DoT).

Read post