
/*---------------------------------------------------------------------------*/
/* dns.cpp                                                                   */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>

#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <errno.h>

#include "common/ilib/str.h"
#include "sdk/common/interface/time.h"
#include "sdk/common/interface/local_storage.h"
#include "sdk/common/os/iomux.h"
#include "sdk/common/interface/dns.h"
#include "sdk/common/lib/dns_idna.h"
#include "sdk/common/lib/dns_cache.h"

#include "dns.h"

/* Android and iOS are caching themself and need to be queried every time to */
/* track changes on DNS server switch */
#if (!defined(__ANDROID__) && !defined(_IOS_))
#define DNS_RESPOND_FROM_CACHE
#endif
#define DNS_MAX_ANSWER_LENGTH   4096
#define DNS_GETHOSTBYNAME_PERSIST  0x40000000

/*---------------------------------------------------------------------------*/
/* Little helpers                                                            */
/*---------------------------------------------------------------------------*/

struct DNSResult
{
    char ipAddress[INET6_ADDRSTRLEN];
    bool IPv6;
    byte answer[DNS_MAX_ANSWER_LENGTH];
    int length;
    DNSResult * next;

    DNSResult() {
        this->ipAddress[0] = '\0';
        this->IPv6 = false;
        this->length = -1;
        this->next = NULL;
    }
};

struct DNSRequest
{
    UDns * const user;
    IInstanceLog * const log;

    char * hostName;
    word servicePort;
    char * serviceName;
    addrinfo * addrList;
    addrinfo * addrListV6;
    int res;
    int resV6;
    int errorNumber;
    int errorNumberV6;
    int gaiErrorNumber;
    int gaiErrorNumberV6;
    word rcode;
    DNSRequest * next;
    DNSResult result;
    dword flags;
    dword expiry;
    dns_query_type_sdk queryType;

    DNSRequest(UDns * const user, const char * hostName, IInstanceLog * const log, dword expiry, dword flags, word servicePort, const char * serviceName, dns_query_type_sdk queryType)
        : user(user),
          log(log)
    {
        unsigned l;
        if (hostName) {
            l = strlen(hostName) + 1024;
            this->hostName = (char *)(malloc(l));
            IDnsIDNA::QueryNameToASCII(hostName, this->hostName, l);
        }
        else {
            this->hostName = NULL;
        }
        this->servicePort = servicePort;
        if (serviceName) {
            l = strlen(serviceName) + 1024;
            this->serviceName = (char *)(malloc(l));
            IDnsIDNA::QueryNameToASCII(serviceName, this->serviceName, l);
        }
        else {
            this->serviceName = NULL;
        }
        this->res = 0;
        this->resV6 = 0;
        this->addrList = NULL;
        this->addrListV6 = NULL;
        this->errorNumber = 0;
        this->errorNumberV6 = 0;
        this->gaiErrorNumber = 0;
        this->gaiErrorNumberV6 = 0;
        this->rcode = DNS_RES_CACHE_MISS;
        this->next = NULL;
        this->flags = flags;
        this->expiry = expiry;
        this->queryType = queryType;
    }

    ~DNSRequest()
    {
        if (hostName) free(hostName);
        if (serviceName) free(serviceName);
        if (addrList) freeaddrinfo(addrList);
        if (addrListV6) freeaddrinfo(addrListV6);
        while (result.next) {
            DNSResult * nextRes = result.next;
            result.next = nextRes->next;
            delete nextRes;
        }
    }
};

class DnsThread : public istd::listElement<DnsThread>
{
friend class Dns;
    pthread_t threadID;
    pthread_mutex_t requestListLock;
    pthread_cond_t pendingRequests;
    IIoMux * const iomux;
    struct DNSRequest * topRequest;
    struct DNSRequest * lastRequest;
    struct DNSRequest * topResolved;
    struct DNSRequest * lastResolved;
    bool isRunning;
    bool topRequestExpired;

public:
    DnsThread(IIoMux * iomux);
    virtual ~DnsThread();
    virtual void ThreadFunction();

    Dns * dns;
};

class DnsThreadPool : public IShutdownHandler
{
    class IIoMux * iomux;

    void Shutdown() override;
    void ShutdownTimeout() override;

public:
    DnsThreadPool(IIoMux * iomux);
    virtual ~DnsThreadPool() {};

    istd::list<DnsThread> threadsIdle;
    istd::list<DnsThread> threadsBusy;
};

static class DnsThreadPool * dnsThreadPool = NULL;
static class IDnsCache * dnsCache = NULL;

void *__dns_thread_main(void * dnsThreadObj)
{
    DnsThread * tmp = static_cast<DnsThread *>(dnsThreadObj);
    tmp->ThreadFunction();
    return NULL;
}


/*---------------------------------------------------------------------------*/
/* class Dns                                                                 */
/*---------------------------------------------------------------------------*/

IDns * IDns::Create(IIoMux * const iomux)
{
    return new Dns(iomux);
}


Dns::Dns(IIoMux * const iomux)
    : iomux(iomux),
      responseCounter(0),
      expiryTimer(iomux, this),
      pipeCreated(false)
{
    long flags;
    unsigned int i;

    if (!dnsThreadPool) {
        dnsThreadPool = new DnsThreadPool(iomux);
        dnsCache = IDnsCache::Create();
    }
    thread = dnsThreadPool->threadsIdle.empty() ? new DnsThread(iomux) : dnsThreadPool->threadsIdle.pop_front();
    thread->dns = this;
    dnsThreadPool->threadsBusy.push_back(thread);
    if (!pipeCreated) {
        if(pipe(pipefd) < 0) {
            debug->printf("Dns(%p)::Dns(): cannot create pipe", this);
        }
        else {
            pipeCreated = true;
            for(i = 0; i < sizeof(pipefd) / sizeof(pipefd[0]); i++) {
                flags = fcntl(pipefd[i], F_GETFL);
                if(flags == -1) {
                    debug->printf("Dns(%p)::Dns(): cannot fcntl(F_GETFL)", this);
                    flags = 0;
                }
                flags |= O_NONBLOCK;
                if(fcntl(pipefd[i], F_SETFL, flags) == -1) {
                    debug->printf("Dns(%p)::Dns(): cannot fcntl(F_SETFL)", this);
                }
            }
            iomux->FDUnblock(pipefd[0]);
            iomux->FDAdd(pipefd[0], this, false);
        }
    }
}


DnsThread::DnsThread(IIoMux * const iomux) :
    threadID(0),
    requestListLock(),
    pendingRequests(),
    iomux(iomux),
    topRequest(NULL),
    lastRequest(NULL),
    topResolved(NULL),
    lastResolved(NULL),
    isRunning(true),
    topRequestExpired(false)
{
    if (pthread_mutex_init(&requestListLock, NULL) != 0) {
        debug->printf("Dns::Dns(): Initialization of mutex failed (%u : %s)", errno, strerror(errno));
        ASSERT(false, "Dns::Dns(): Initialization of mutex failed");
    }

    if (pthread_cond_init(&pendingRequests, NULL) != 0) {
        debug->printf("Dns::Dns(): Initialization of cond failed (%u : %s)", errno, strerror(errno));
        ASSERT(false, "Dns::Dns(): Initialization of cond failed");
    }

    if (pthread_create(&threadID, NULL, __dns_thread_main, this) != 0) {
        debug->printf("Dns::Dns(): Initialization of DNS thread failed (%u : %s)", errno, strerror(errno));
        ASSERT(false, "Dns::Dns(): Initialization of DNS thread failed");
    }
}


Dns::~Dns()
{
    unsigned int i;

    ASSERT(responseCounter == 0, "Dns::~Dns responseCounter not 0, wait for DnsGetHostByNameResult missing?");
    if (thread) {
        thread->dns = NULL;
        thread->remove();
        dnsThreadPool->threadsIdle.push_back(thread);
    }
    if(pipeCreated) {
        iomux->FDClose(this);
        for(i = 1; i < sizeof(pipefd) / sizeof(pipefd[0]); i++) {
            close(pipefd[i]);
        }
        pipeCreated = false;
    }
}


DnsThread::~DnsThread()
{
    pthread_mutex_lock(&requestListLock);
    isRunning = false;
    pthread_cond_signal(&pendingRequests);
    pthread_mutex_unlock(&requestListLock);
    pthread_join(threadID, NULL);
    if (dns) {
        while (topResolved) {
            DNSRequest * nextReq = topResolved;
            topResolved = topResolved->next;
            iomux->SetExec(dns, nextReq);
        }
        while (topRequest) {
            DNSRequest * nextReq = topRequest;
            topRequest = topRequest->next;
            if (topRequestExpired) {
                topRequestExpired = false;
                delete nextReq;
            }
            else {
                iomux->SetExec(dns, nextReq);
            }
        }
        dns->thread = NULL;
        dns->expiryTimer.Cancel();
    }
    pthread_mutex_destroy(&requestListLock);
    pthread_cond_destroy(&pendingRequests);
}

DnsThreadPool::DnsThreadPool(IIoMux * iomux) :
    iomux(iomux)
{
    iomux->RegisterShutdownHandler(this);
}

void DnsThreadPool::Shutdown()
{
    while (!threadsIdle.empty()) {
        delete threadsIdle.pop_front();
    }
    while (!threadsBusy.empty()) {
        delete threadsBusy.pop_front();
    }
    iomux->ShutdownComplete(this);
    delete this;
    dnsThreadPool = NULL;
    delete dnsCache;
    dnsCache = NULL;
    //debug->printf("DnsThreadPool::ShutdownComplete");
}

void DnsThreadPool::ShutdownTimeout()
{
    debug->printf("DnsThreadPool::ShutdownTimeout");
}


void Dns::UpdateNextTimeout(dword t)
{
    dword e;
    DNSRequest * r;

    e = t + 0x3fffffff;
    r = thread->topResolved;
    while (r) {
        if (((int32)(r->expiry - e)) < 0) {
            e = r->expiry;
        }
        r = r->next;
    }
    r = thread->topRequest;
    if (r) {
        if (thread->topRequestExpired) {
            r = r->next;
        }
        while (r) {
            if (((int32)(r->expiry - e)) < 0) {
                e = r->expiry;
            }
            r = r->next;
        }
    }
    nextTimeout = (((int32)(e - t)) <= 0) ? 1 : e - t;
}


void Dns::GetHostByName(const char * hostName, UDns * const user, IInstanceLog * const log, dword timeout, dword flags, word servicePort, const char * serviceName)
{
#ifdef DNS_RESPOND_FROM_CACHE
    DnsCacheResult *r;
#endif

    log->Log(LOG_DNS, "Dns(%08x)::GetHostByName hostName: %s timeout %d flags %x service:%u,%s", user, hostName, timeout, flags, servicePort, serviceName);
    ASSERT(hostName != NULL && hostName[0] != '\0', "Dns::GetHostByName() called without an given hostName");
    ASSERT(user != NULL, "Dns::GetHostByName() called with user == NULL");

    dword e = (dword)(ITime::TimeStampMilliseconds());
    dword t = ((timeout == 0) || (timeout > 0x3fffffff)) ? 0x3fffffff : timeout;
    DNSRequest * req = new DNSRequest(user, hostName, log, e + t, flags | ((t <= 3000) ? DNS_GETHOSTBYNAME_PERSIST : 0), servicePort, serviceName, DNS_QUERY_TYPE_NONE);

    responseCounter++;
    char * p = 0;
    str::to_ip(hostName, &p, /*prefix*/0);
#ifdef DNS_RESPOND_FROM_CACHE
    dword lookupFlags =
        ((req->flags & DNS_GETHOSTBYNAME_FLAG_ALL) ? DNS_CACHE_LOOKUP_FLAG_ALL : 0) |
        ((req->flags & DNS_GETHOSTBYNAME_NO_IPV6) ? DNS_CACHE_LOOKUP_NO_IPV6 : 0) |
        ((req->flags & DNS_GETHOSTBYNAME_ONLY_IPV6) ? DNS_CACHE_LOOKUP_ONLY_IPV6 : 0) |
        ((req->flags & DNS_GETHOSTBYNAME_ITERATE) ? DNS_CACHE_LOOKUP_ITERATE : 0);
#endif
    if (!dnsCache || !thread || (p != hostName)) {
        iomux->SetExec(this, req);
    }
#ifdef DNS_RESPOND_FROM_CACHE
    else if (((req->rcode = dnsCache->Lookup(req->hostName, lookupFlags, req->servicePort, req->serviceName, &r)) != DNS_RES_CACHE_MISS)) {
        if (req->rcode == DNS_RES_SUCCESS) {
            DNSResult * nextRes = &(req->result);
            if (r && ((req->flags & DNS_GETHOSTBYNAME_FLAG_ALL) || !(r->resultFlags & DNS_CACHE_FLAG_UNSUITABLE) || !(req->flags & DNS_GETHOSTBYNAME_ITERATE))) {
                do {
                    const char * resStr = NULL;
                    if (r->qtype == QTYPE_AAAA) {
                        struct sockaddr_in6 adr;
                        memset(&adr, 0, sizeof(struct sockaddr_in6));
                        memcpy(&(adr.sin6_addr), &(r->u.aaaa.addr), sizeof(struct in6_addr));
                        resStr = inet_ntop(AF_INET6, &adr.sin6_addr, nextRes->ipAddress, INET6_ADDRSTRLEN);
                        nextRes->IPv6 = true;
                    }
                    else {
                        struct sockaddr_in adr;
                        memset(&adr, 0, sizeof(struct sockaddr_in));
                        memcpy(&(adr.sin_addr), &(r->u.a.addr.b[12]), sizeof(struct in_addr));
                        resStr = inet_ntop(AF_INET, &adr.sin_addr, nextRes->ipAddress, INET6_ADDRSTRLEN);
                        nextRes->IPv6 = false;
                    }
                    if (!resStr) {
                        nextRes->ipAddress[0] = '\0';
                        req->errorNumber = errno;
                    }
                    else {
                        req->errorNumber = 0;
                        req->gaiErrorNumber = 0;
                        if (!(req->flags & DNS_GETHOSTBYNAME_FLAG_ALL)) {
                            break;
                        }
                        nextRes->next = new DNSResult();
                        nextRes = nextRes->next;
                    }
                    r = r->resultNext;
                }
                while (r);
            }
        }
        iomux->SetExec(this, req);
    }
#endif
    else {
        pthread_mutex_lock(&thread->requestListLock);
        if (thread->topRequest == NULL)
            thread->topRequest = thread->lastRequest = req;
        else {
            thread->lastRequest->next = req;
            thread->lastRequest = req;
        }
        UpdateNextTimeout(e);
        pthread_cond_signal(&thread->pendingRequests);
        pthread_mutex_unlock(&thread->requestListLock);
        expiryTimer.Start(nextTimeout);
    }
}


void Dns::UnsuitableAddress(const char * hostName, class IInstanceLog * const log, const char * addr, word servicePort, const char * serviceName)
{
    char *host, *service;
    IPaddr a;
    unsigned l;

    debug->printf("Dns::UnsuitableAddress hostName:%s addr:%s service:%u,%s", hostName, addr, servicePort, serviceName);
    ASSERT(hostName != NULL && hostName[0] != '\0', "Dns::UnsuitableAddress() called without an given hostName");

    char * p = 0;
    str::to_ip(hostName, &p, /*prefix*/0);
    if ((p == hostName) && dnsCache) {
        if (hostName) {
            l = strlen(hostName) + 1024;
            host = (char *)(malloc(l));
            IDnsIDNA::QueryNameToASCII(hostName, host, l);
        }
        else {
            host = NULL;
        }
        a = str::to_ip(addr, NULL, /*prefix*/NULL);
        if (serviceName) {
            l = strlen(serviceName) + 1024;
            service = (char *)(malloc(l));
            IDnsIDNA::QueryNameToASCII(serviceName, service, l);
        }
        else {
            service = NULL;
        }

        dnsCache->UnsuitableAddr(host, a, servicePort);

        if (host) free(host);
        if (service) free(service);
    }
}


void Dns::Query(const char * hostName, class UDns * const user, class IInstanceLog * const log, dns_query_type_sdk queryType)
{
    log->Log(LOG_DNS, "Dns(%08x)::Query hostName: %s queryType %u", user, hostName, queryType);
    ASSERT(hostName != NULL && hostName[0] != '\0', "Dns::Query() called without an given hostName");
    ASSERT(user != NULL, "Dns::Query() called with user == NULL");

    dword e = (dword)(ITime::TimeStampMilliseconds());
    dword t = 0x3fffffff;
    DNSRequest * req = new DNSRequest(user, hostName, log, e + t, DNS_GETHOSTBYNAME_ITERATE, 0, NULL, queryType);

    responseCounter++;
    if (!thread) {
        iomux->SetExec(this, req);
    }
    else {
        pthread_mutex_lock(&thread->requestListLock);
        if (thread->topRequest == NULL)
            thread->topRequest = thread->lastRequest = req;
        else {
            thread->lastRequest->next = req;
            thread->lastRequest = req;
        }
        UpdateNextTimeout(e);
        pthread_cond_signal(&thread->pendingRequests);
        pthread_mutex_unlock(&thread->requestListLock);
        expiryTimer.Start(nextTimeout);
    }
}


void Dns::TimerOnTimeout(DnsExpiryTimer * timer)
{
    dword e;
    DNSRequest * r, * lastReq;

    e = (dword)(ITime::TimeStampMilliseconds());
    do {
        pthread_mutex_lock(&thread->requestListLock);
        lastReq = NULL;
        r = thread->topResolved;
        while (r && (((int32)(r->expiry - e)) > 0)) {
            lastReq = r;
            r = r->next;
        }
        if (r) {
            if (r == thread->lastResolved) {
                thread->lastResolved = lastReq;
            }
            if (r == thread->topResolved) {
                thread->topResolved = r->next;
            }
            else {
                lastReq->next = r->next;
            }
        }
        else {
            r = thread->topRequest;
            if (r) {
                if (!thread->topRequestExpired && (((int32)(r->expiry - e)) <= 0)) {
                    thread->topRequestExpired = true;
                    r = new DNSRequest(r->user, r->hostName, r->log, r->expiry, r->flags, r->servicePort, r->serviceName, r->queryType);
                    r->errorNumber = ETIMEDOUT;
                    r->errorNumberV6 = ETIMEDOUT;
                }
                else {
                    do {
                        lastReq = r;
                        r = r->next;
                    }
                    while (r && (((int32)(r->expiry - e)) > 0));
                    if (r) {
                        if (r == thread->lastRequest) {
                            thread->lastRequest = lastReq;
                        }
                        lastReq->next = r->next;
                        r->errorNumber = ETIMEDOUT;
                        r->errorNumberV6 = ETIMEDOUT;
                    }
                    else {
                        UpdateNextTimeout(e);
                    }
                }
            }
        }
        pthread_mutex_unlock(&thread->requestListLock);
        if (r) {
            iomux->SetExec(this, r);
        }
    }
    while (r);
    expiryTimer.Start(nextTimeout);
}


void DnsThread::ThreadFunction()
{
#ifdef __ANDROID__
    unsigned hostNameAlloc = 2048;
    char * hostName = (char *)(malloc(hostNameAlloc));
#endif
    pthread_mutex_lock(&requestListLock);
    while(isRunning) {
        if (!topRequest) {
            pthread_cond_wait(&pendingRequests, &requestListLock);
        }
        if (topRequest && isRunning) {
            pthread_mutex_unlock(&requestListLock);
            if (topRequest->queryType != DNS_QUERY_TYPE_NONE) {
                topRequest->result.length = res_query(topRequest->hostName, 1/* the arpa internet */, (int)topRequest->queryType, topRequest->result.answer, DNS_MAX_ANSWER_LENGTH);
            }
            else {
#ifdef _IOS_
                addrinfo hints;
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = AF_UNSPEC;
                hints.ai_socktype = SOCK_STREAM;
                //hints.ai_flags = AI_CANONNAME;
                topRequest->res = getaddrinfo(topRequest->hostName, NULL, &hints, &topRequest->addrList);
                if (topRequest->res != 0) {
                    topRequest->addrList = NULL;
                }
#elif defined(__ANDROID__)
                unsigned l;
                bool no_suffix_succeeded = false;
                addrinfo hints;
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = AF_INET;
                hints.ai_socktype = SOCK_STREAM;
                //hints.ai_flags = AI_CANONNAME;
                if (strchr(topRequest->hostName, '.')) {
                    l = strlen(topRequest->hostName);
                    if (hostNameAlloc <= l) {
                        free(hostName);
                        do {
                            hostNameAlloc <<= 1;
                        }
                        while (hostNameAlloc <= l);
                        hostName = (char *)(malloc(hostNameAlloc));
                    }
                    memcpy(hostName, topRequest->hostName, l);
                    hostName[l] = '.';
                    hostName[l+1] = '\0';
                    topRequest->res = getaddrinfo(hostName, NULL, &hints, &topRequest->addrList); // try to resolve an IPv4 address first
                    no_suffix_succeeded = ((topRequest->res == 0) && topRequest->addrList);
                }
                if (!no_suffix_succeeded) {
                    topRequest->res = getaddrinfo(topRequest->hostName, NULL, &hints, &topRequest->addrList); // try to resolve an IPv4 address first
                }
                if (topRequest->res != 0) {
                    topRequest->addrList = NULL;
                }
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = AF_INET6;
                hints.ai_socktype = SOCK_STREAM;
                //hints.ai_flags = AI_CANONNAME;
                topRequest->resV6 = getaddrinfo(no_suffix_succeeded ? hostName : topRequest->hostName, NULL, &hints, &topRequest->addrListV6); // afterwards an IPv6 address (as some servers to not respond to IPv6 requests at all => timeout)
                if (topRequest->resV6 != 0) {
                    topRequest->addrListV6 = NULL;
                }
#else /* _IOS_ */
                // we do not resolve IPv4 and IPv6 addresses together in one getaddrinfo call, as there is some strange
                // bug if glibc getaddrinfo is used together with a Microsoft DNS Server
                // see http://philippecloutier.com/blogpost28-dig-1-and-other-DNS-clients-sometimes-taking-5-seconds-to-return-the-results-of-a-local-query for more information
                addrinfo hints;
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = AF_INET;
                hints.ai_socktype = SOCK_STREAM;
                //hints.ai_flags = AI_CANONNAME;
                topRequest->res = getaddrinfo(topRequest->hostName, NULL, &hints, &topRequest->addrList); // try to resolve an IPv4 address first
                if (topRequest->res != 0) {
                    topRequest->addrList = NULL;
                }
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = AF_INET6;
                hints.ai_socktype = SOCK_STREAM;
                //hints.ai_flags = AI_CANONNAME;
                topRequest->resV6 = getaddrinfo(topRequest->hostName, NULL, &hints, &topRequest->addrListV6); // afterwards an IPv6 address (as some servers to not respond to IPv6 requests at all => timeout)
                if (topRequest->resV6 != 0) {
                    topRequest->addrListV6 = NULL;
                }
#endif /* _IOS_ */
            }
            pthread_mutex_lock(&requestListLock);
            if (isRunning) {
                DNSRequest * nextReq = topRequest;
                topRequest = topRequest->next;
                if (topRequestExpired) {
                    topRequestExpired = false;
                    delete nextReq;
                }
                else {
                    nextReq->next = NULL;
                    if (topResolved == NULL)
                        topResolved = lastResolved = nextReq;
                    else {
                        lastResolved->next = nextReq;
                        lastResolved = nextReq;
                    }
                    pthread_mutex_unlock(&requestListLock);
                    byte buf[1] = {0};
                    write(dns->pipefd[1], buf, sizeof(buf));
                    pthread_mutex_lock(&requestListLock);
                }
            }
        }
    }
    pthread_mutex_unlock(&requestListLock);
#ifdef __ANDROID__
    free(hostName);
#endif
}


void Dns::IoContextNotify(bool indRX, bool indTX)
{
    byte buf[1];

    while(read(pipefd[0], buf, sizeof(buf)) == sizeof(buf)) {
        if (thread) {
            pthread_mutex_lock(&thread->requestListLock);
            while (thread->topResolved) {
                pthread_mutex_unlock(&thread->requestListLock);
                if (thread->topResolved->queryType == DNS_QUERY_TYPE_NONE) {
                    // we do not resolve IPv4 and IPv6 addresses together in one getaddrinfo call, as there is some strange
                    // bug if glibc getaddrinfo is used together with a Microsoft DNS Server
                    // see http://philippecloutier.com/blogpost28-dig-1-and-other-DNS-clients-sometimes-taking-5-seconds-to-return-the-results-of-a-local-query for more information
                    addrinfo * x, * addrList;
#if !defined(DNS_RESPOND_FROM_CACHE)
                    DnsCacheResult *p, *r, **rr;
                    rr = &r;
                    dnsCache->LookupA(thread->topResolved->hostName, thread->topResolved->servicePort, &rr);
                    dnsCache->LookupAAAA(thread->topResolved->hostName, thread->topResolved->servicePort, &rr);
                    *rr = NULL;
                    if (r) {
                        bool ipv6;
                        p = r;
                        if (thread->topResolved->flags & DNS_GETHOSTBYNAME_NO_IPV6) {
                            while (p && (p->qtype != QTYPE_A)) {
                                p = p->resultNext;
                            }
                            if (p) {
                                while (p && ((p->resultFlags & DNS_CACHE_FLAG_UNSUITABLE) || (p->qtype != QTYPE_A))) {
                                    p = p->resultNext;
                                }
                                if (!p) {
                                    dnsCache->ClearUnsuitableAddrs(thread->topResolved->hostName, thread->topResolved->servicePort);
                                }
                            }
                        }
                        else if (thread->topResolved->flags & DNS_GETHOSTBYNAME_ONLY_IPV6) {
                            while (p && (p->qtype != QTYPE_AAAA)) {
                                p = p->resultNext;
                            }
                            if (p) {
                                while (p && ((p->resultFlags & DNS_CACHE_FLAG_UNSUITABLE) || (p->qtype != QTYPE_AAAA))) {
                                    p = p->resultNext;
                                }
                                if (!p) {
                                    dnsCache->ClearUnsuitableAddrs(thread->topResolved->hostName, thread->topResolved->servicePort);
                                }
                            }
                        }
                        else {
                            while (p && (p->resultFlags & DNS_CACHE_FLAG_UNSUITABLE)) {
                                p = p->resultNext;
                            }
                            if (!p) {
                                dnsCache->ClearUnsuitableAddrs(thread->topResolved->hostName, thread->topResolved->servicePort);
                            }
                        }
                        ipv6 = false;
                        addrList = thread->topResolved->addrList;
                        x = addrList;
                        do {
                            p = r;
                            if (x) {
                                IPaddr a;
                                if (x->ai_family == AF_INET6) {
                                    memcpy(a.b, &(((sockaddr_in6 *)x->ai_addr)->sin6_addr), sizeof(struct in6_addr));
                                    while (p && ((p->qtype != QTYPE_AAAA) || !IP_MATCH(a, p->u.aaaa.addr))) {
                                        p = p->resultNext;
                                    }
                                }
                                else if (x->ai_family == AF_INET) {
                                    memcpy(a.b, ip_4_anyaddr.b, 12);
                                    memcpy(&(a.b[12]), &(((sockaddr_in *)x->ai_addr)->sin_addr), sizeof(struct in_addr));
                                    while (p && ((p->qtype != QTYPE_A) || !IP_MATCH(a, p->u.a.addr))) {
                                        p = p->resultNext;
                                    }
                                }
                                x = x->ai_next;
                            }
                            if (!x) {
                                if (!ipv6) {
                                    ipv6 = true;
                                    addrList = thread->topResolved->addrListV6;
                                }
                                else {
                                    addrList = NULL;
                                }
                                x = addrList;
                            }
                        }
                        while (x && p);
                        if (!p) {
                            dnsCache->InvalidateQname(thread->topResolved->hostName); // new result, probably DNS server switch
                        }
                    }
#endif
                    int * errorNumber = &thread->topResolved->errorNumber;
                    int * gaiErrorNumber = &thread->topResolved->gaiErrorNumber;
                    addrList = thread->topResolved->addrList;
                    int res = thread->topResolved->res; // try to resolve an IPv4 address first
                    if (res != 0) {
                        *gaiErrorNumber = res;
                        if (res == EAI_SERVICE) {
                            *errorNumber = errno;
                        }
                    }
                    if (res == EAI_NONAME) {
                        dnsCache->UpdateA(thread->topResolved->hostName, 60, 0, DNS_RES_NXDOMAIN, ip_anyaddr);
#ifdef _IOS_
                        dnsCache->UpdateAAAA(thread->topResolved->hostName, 60, 0, DNS_RES_NXDOMAIN, ip_anyaddr);
#endif
                    }
                    x = addrList;
                    do {
                        if (x) {
                            IPaddr a;
                            if (x->ai_family == AF_INET6) {
                                memcpy(a.b, &(((sockaddr_in6 *)x->ai_addr)->sin6_addr), sizeof(struct in6_addr));
#if (!defined(DNS_RESPOND_FROM_CACHE) || defined(__ANDROID__) || defined(_IOS_))
                                dnsCache->UpdateAAAA(thread->topResolved->hostName, 0x3fffffff, 0, DNS_RES_SUCCESS, a);
#else
                                dnsCache->UpdateAAAA(thread->topResolved->hostName, 60, 0, DNS_RES_SUCCESS, a);
#endif
                            }
                            else if (x->ai_family == AF_INET) {
                                memcpy(a.b, ip_4_anyaddr.b, 12);
                                memcpy(&(a.b[12]), &(((sockaddr_in *)x->ai_addr)->sin_addr), sizeof(struct in_addr));
#if (!defined(DNS_RESPOND_FROM_CACHE) || defined(__ANDROID__) || defined(_IOS_))
                                dnsCache->UpdateA(thread->topResolved->hostName, 0x3fffffff, 0, DNS_RES_SUCCESS, a);
#else
                                dnsCache->UpdateA(thread->topResolved->hostName, 60, 0, DNS_RES_SUCCESS, a);
#endif
                            }
                            x = x->ai_next;
                        }
                        if (!x) {
                            if (errorNumber == &thread->topResolved->errorNumber) {
                                errorNumber = &thread->topResolved->errorNumberV6;
                                gaiErrorNumber = &thread->topResolved->gaiErrorNumberV6;
                                addrList = thread->topResolved->addrListV6;
                                res = thread->topResolved->resV6; // afterwards an IPv6 address (as some servers to not respond to IPv6 requests at all => timeout)
                                if (res != 0) {
                                    *gaiErrorNumber = res;
                                    if (res == EAI_SERVICE) {
                                        *errorNumber = errno;
                                    }
                                }
                                if (res == EAI_NONAME) {
                                    dnsCache->UpdateAAAA(thread->topResolved->hostName, 60, 0, DNS_RES_NXDOMAIN, ip_anyaddr);
                                }
                            }
                            else {
                                addrList = NULL;
                            }
                            x = addrList;
                        }
                    }
                    while (addrList);
                }
                pthread_mutex_lock(&thread->requestListLock);
                DNSRequest * nextReq = thread->topResolved;
                thread->topResolved = thread->topResolved->next;
                UpdateNextTimeout((dword)(ITime::TimeStampMilliseconds()));
                pthread_mutex_unlock(&thread->requestListLock);
                iomux->SetExec(this, nextReq);
                expiryTimer.Start(nextTimeout);
                pthread_mutex_lock(&thread->requestListLock);
            }
            pthread_mutex_unlock(&thread->requestListLock);
        }
    }
}


void Dns::IoExec(void * execContext)
{
    DNSRequest * req = static_cast<DNSRequest *>(execContext);
    DNSResult * nextRes;
    DnsCacheResult *r;
    responseCounter--;

    if (req->queryType != DNS_QUERY_TYPE_NONE) {
        nextRes = &(req->result);
        UserDnsQueryResult(req->user, req->log, (nextRes->length >= 0) ? nextRes->answer : NULL, (nextRes->length >= 0) ? nextRes->length : 0);
    }
    else {
        char * p = 0;
        IPaddr addr = str::to_ip(req->hostName, &p, /*prefix*/0);
        dword lookupFlags =
            ((req->flags & DNS_GETHOSTBYNAME_FLAG_ALL) ? DNS_CACHE_LOOKUP_FLAG_ALL : 0) |
            ((req->flags & DNS_GETHOSTBYNAME_NO_IPV6) ? DNS_CACHE_LOOKUP_NO_IPV6 : 0) |
            ((req->flags & DNS_GETHOSTBYNAME_ONLY_IPV6) ? DNS_CACHE_LOOKUP_ONLY_IPV6 : 0) |
            ((req->flags & DNS_GETHOSTBYNAME_ITERATE) ? DNS_CACHE_LOOKUP_ITERATE : 0);
        if (p != req->hostName) {
            strcpy(req->result.ipAddress, req->hostName);
            req->result.IPv6 = !is_ip4(addr);
        }
        else if (dnsCache) {
#if (defined(__ANDROID__) || defined(_IOS_))
            if (req->flags & DNS_GETHOSTBYNAME_PERSIST) {
                addrinfo * x, * addrList;
                char name[2048];
                IPaddr a;
                bool ipv6;
                if (req->addrList || req->addrListV6) {
                    char bufA[16384], bufAAAA[16384];
                    unsigned posA, posAAAA;
                    posA = 0;
                    posAAAA = 0;
                    ipv6 = false;
                    addrList = req->addrList;
                    x = addrList;
                    do {
                        if (x) {
                            if (x->ai_family == AF_INET6) {
                                if ((posAAAA != 0) && (posAAAA < sizeof(bufAAAA) - 1)) {
                                    bufAAAA[posAAAA++] = ',';
                                }
                                memcpy(a.b, &(((sockaddr_in6 *)x->ai_addr)->sin6_addr), sizeof(struct in6_addr));
                                posAAAA += str::from_ip(&bufAAAA[posAAAA], sizeof(bufAAAA) - posAAAA, &a);
                            }
                            else if (x->ai_family == AF_INET) {
                                if ((posA != 0) && (posA < sizeof(bufA) - 1)) {
                                    bufA[posA++] = ',';
                                }
                                memcpy(a.b, ip_4_anyaddr.b, 12);
                                memcpy(&(a.b[12]), &(((sockaddr_in *)x->ai_addr)->sin_addr), sizeof(struct in_addr));
                                posA += str::from_ip(&bufA[posA], sizeof(bufA) - posA, &a);
                            }
                            x = x->ai_next;
                        }
                        if (!x) {
                            if (!ipv6) {
                                ipv6 = true;
                                addrList = req->addrListV6;
                            }
                            else {
                                addrList = NULL;
                            }
                            x = addrList;
                        }
                    }
                    while (x);
                    if (posA != 0) {
                        bufA[sizeof(bufA) - 1] = '\0';
                        snprintf(name, sizeof(name), "DNS_A_%s", req->hostName);
                        name[sizeof(name) - 1] = '\0';
                        ILocalStorage::SetItem(name, bufA);
                    }
                    if (posAAAA != 0) {
                        bufAAAA[sizeof(bufAAAA) - 1] = '\0';
                        snprintf(name, sizeof(name), "DNS_AAAA_%s", req->hostName);
                        name[sizeof(name) - 1] = '\0';
                        ILocalStorage::SetItem(name, bufAAAA);
                    }
                }
                else {
                    char *buf, *b, *p;
                    ipv6 = false;
                    p = NULL;
                    snprintf(name, sizeof(name), "DNS_A_%s", req->hostName);
                    name[sizeof(name) - 1] = '\0';
                    buf = ILocalStorage::GetItem(name);
                    b = buf;
                    do {
                        if (b) {
                            a = str::to_ip(b, &p, /*prefix*/0);
                            if (p != b) {
#ifdef _IOS_
                                if (req->res != EAI_NONAME)
#else
                                if ((ipv6 ? req->resV6 : req->res) != EAI_NONAME)
#endif
                                {
                                    if (ipv6) {
                                        dnsCache->UpdateAAAA(req->hostName, 0x3fffffff, 0, DNS_RES_SUCCESS, a);
                                    }
                                    else {
                                        dnsCache->UpdateA(req->hostName, 0x3fffffff, 0, DNS_RES_SUCCESS, a);
                                    }
                                }
                                b = p;
                                if (*b != '\0') {
                                    b++;
                                }
                                else {
                                    b = NULL;
                                }
                            }
                            else {
                                b = NULL;
                            }
                        }
                        if (!b) {
                            if (buf) {
                                free(buf);
                            }
                            if (!ipv6) {
                                ipv6 = true;
                                snprintf(name, sizeof(name), "DNS_AAAA_%s", req->hostName);
                                name[sizeof(name) - 1] = '\0';
                                buf = ILocalStorage::GetItem(name);
                                b = buf;
                            }
                        }
                    }
                    while (b);
                }
            }
#endif
            if ((req->rcode == DNS_RES_CACHE_MISS) && ((req->rcode = dnsCache->Lookup(req->hostName, lookupFlags, req->servicePort, req->serviceName, &r)) == DNS_RES_SUCCESS)) {
                req->errorNumber = 0;
                req->errorNumberV6 = 0;
                nextRes = &(req->result);
                if (r && ((req->flags & DNS_GETHOSTBYNAME_FLAG_ALL) || !(r->resultFlags & DNS_CACHE_FLAG_UNSUITABLE) || !(req->flags & DNS_GETHOSTBYNAME_ITERATE))) {
                    do {
                        const char * resStr = NULL;
                        if (r->qtype == QTYPE_AAAA) {
                            struct sockaddr_in6 adr;
                            memset(&adr, 0, sizeof(struct sockaddr_in6));
                            memcpy(&(adr.sin6_addr), &(r->u.aaaa.addr), sizeof(struct in6_addr));
                            resStr = inet_ntop(AF_INET6, &adr.sin6_addr, nextRes->ipAddress, INET6_ADDRSTRLEN);
                            nextRes->IPv6 = true;
                        }
                        else {
                            struct sockaddr_in adr;
                            memset(&adr, 0, sizeof(struct sockaddr_in));
                            memcpy(&(adr.sin_addr), &(r->u.a.addr.b[12]), sizeof(struct in_addr));
                            resStr = inet_ntop(AF_INET, &adr.sin_addr, nextRes->ipAddress, INET6_ADDRSTRLEN);
                            nextRes->IPv6 = false;
                        }
                        if (!resStr) {
                            nextRes->ipAddress[0] = '\0';
                            req->errorNumber = errno;
                        }
                        else {
                            req->errorNumber = 0;
                            req->gaiErrorNumber = 0;
                            if (!(req->flags & DNS_GETHOSTBYNAME_FLAG_ALL)) {
                                break;
                            }
                            nextRes->next = new DNSResult();
                            nextRes = nextRes->next;
                        }
                        r = r->resultNext;
                    }
                    while (r);
                }
            }
        }
        if (req->log->LogFlagSet(LOG_DNS) && dnsCache) {
            char buf[1024];

            debug->printf("DNS result %s %u %s %u", req->hostName, req->rcode, req->result.ipAddress, req->result.IPv6);
            dnsCache->List(&r);
            while (r) {
                r->Print(buf, sizeof(buf));
                debug->printf("dnsCache: %s", buf);
                r = r->resultNext;
            }
        }
        nextRes = &(req->result);
        do {
            if ((p == req->hostName)
             && ((req->rcode != DNS_RES_SUCCESS)
              || (((req->errorNumber != 0) || (req->gaiErrorNumber != 0))
               && ((req->errorNumberV6 != 0) || (req->gaiErrorNumberV6 != 0))))) {
                debug->printf("Dns(%08x)::IoExec DNS request for %s flags %x service:%u,%s failed %u IPv4(%i : %s) IPv6(%i : %s)", req->user, req->hostName, req->flags, req->servicePort, req->serviceName,
                    req->rcode,
                    (req->errorNumber != 0) ? req->errorNumber : req->gaiErrorNumber, (req->errorNumber != 0) ? strerror(req->errorNumber) : gai_strerror(req->gaiErrorNumber),
                    (req->errorNumberV6 != 0) ? req->errorNumberV6 : req->gaiErrorNumberV6, (req->errorNumberV6 != 0) ? strerror(req->errorNumberV6) : gai_strerror(req->gaiErrorNumberV6));
                UserDnsGetHostByNameResult(req->user, req->log, NULL, false);
            }
            else if (nextRes->ipAddress[0] == '\0') {
                UserDnsGetHostByNameResult(req->user, req->log, NULL, false);
            }
            else {
                UserDnsGetHostByNameResult(req->user, req->log, nextRes->ipAddress, nextRes->IPv6);
            }
            nextRes = nextRes->next;
        }
        while (nextRes);
    }
    delete req;
}

void Dns::UserDnsGetHostByNameResult(UDns * user, IInstanceLog * const log, const char * addr, bool isIPv6)
{
    log->Log(LOG_DNS, "Dns(%08x)::GetHostByNameResult addr:%s isIPv6:%i", user, addr, isIPv6);
    user->DnsGetHostByNameResult(addr, isIPv6);
}

void Dns::UserDnsQueryResult(UDns * user, IInstanceLog * const log, const byte * answer, unsigned length)
{
    log->Log(LOG_DNS, "Dns(%08x)::QueryResult answer:%08x length:%i", user, answer, length);
    user->DnsQueryResult(answer, length);
}
