service: Disconnect the connecting service when needed
[platform/upstream/connman.git] / src / dnsproxy.c
index 49651a2..7a9ca91 100644 (file)
@@ -24,6 +24,7 @@
 #endif
 
 #include <errno.h>
+#include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdint.h>
@@ -82,9 +83,11 @@ struct partial_reply {
 };
 
 struct server_data {
-       char *interface;
+       int index;
        GList *domains;
        char *server;
+       struct sockaddr *server_addr;
+       socklen_t server_addr_len;
        int protocol;
        GIOChannel *channel;
        guint watch;
@@ -119,7 +122,7 @@ struct request_data {
 };
 
 struct listener_data {
-       char *ifname;
+       int index;
        GIOChannel *udp_listener_channel;
        guint udp_listener_watch;
        GIOChannel *tcp_listener_channel;
@@ -182,11 +185,14 @@ static GHashTable *cache;
 static int cache_refcount;
 static GSList *server_list = NULL;
 static GSList *request_list = NULL;
-static GSList *request_pending_list = NULL;
-static guint16 request_id = 0x0000;
 static GHashTable *listener_table = NULL;
 static time_t next_refresh;
 
+static guint16 get_id()
+{
+       return random();
+}
+
 static int protocol_offset(int protocol)
 {
        switch (protocol) {
@@ -237,27 +243,27 @@ static struct request_data *find_request(guint16 id)
        return NULL;
 }
 
-static struct server_data *find_server(const char *interface,
+static struct server_data *find_server(int index,
                                        const char *server,
                                                int protocol)
 {
        GSList *list;
 
-       DBG("interface %s server %s", interface, server);
+       DBG("index %d server %s proto %d", index, server, protocol);
 
        for (list = server_list; list; list = list->next) {
                struct server_data *data = list->data;
 
-               if (interface == NULL && data->interface == NULL &&
+               if (index < 0 && data->index < 0 &&
                                g_str_equal(data->server, server) == TRUE &&
                                data->protocol == protocol)
                        return data;
 
-               if (interface == NULL ||
-                               data->interface == NULL || data->server == NULL)
+               if (index < 0 ||
+                               data->index < 0 || data->server == NULL)
                        continue;
 
-               if (g_str_equal(data->interface, interface) == TRUE &&
+               if (data->index == index &&
                                g_str_equal(data->server, server) == TRUE &&
                                data->protocol == protocol)
                        return data;
@@ -372,15 +378,32 @@ static void send_cached_response(int sk, unsigned char *buf, int len,
                                int protocol, int id, uint16_t answers, int ttl)
 {
        struct domain_hdr *hdr;
-       int err, offset = protocol_offset(protocol);
+       unsigned char *ptr = buf;
+       int err, offset, dns_len, adj_len = len - 2;
 
-       if (offset < 0)
+       /*
+        * The cached packet contains always the TCP offset (two bytes)
+        * so skip them for UDP.
+        */
+       switch (protocol) {
+       case IPPROTO_UDP:
+               ptr += 2;
+               len -= 2;
+               dns_len = len;
+               offset = 0;
+               break;
+       case IPPROTO_TCP:
+               offset = 2;
+               dns_len = ptr[0] * 256 + ptr[1];
+               break;
+       default:
                return;
+       }
 
        if (len < 12)
                return;
 
-       hdr = (void *) (buf + offset);
+       hdr = (void *) (ptr + offset);
 
        hdr->id = id;
        hdr->qr = 1;
@@ -393,16 +416,22 @@ static void send_cached_response(int sk, unsigned char *buf, int len,
        if (answers == 0)
                hdr->aa = 1;
        else
-               update_cached_ttl(buf, len, ttl);
+               update_cached_ttl((unsigned char *)hdr, adj_len, ttl);
 
-       DBG("id 0x%04x answers %d", hdr->id, answers);
+       DBG("sk %d id 0x%04x answers %d ptr %p length %d dns %d",
+               sk, hdr->id, answers, ptr, len, dns_len);
 
-       err = sendto(sk, buf, len, 0, to, tolen);
+       err = sendto(sk, ptr, len, MSG_NOSIGNAL, to, tolen);
        if (err < 0) {
                connman_error("Cannot send cached DNS response: %s",
                                strerror(errno));
                return;
        }
+
+       if (err != len || (dns_len != (len - 2) && protocol == IPPROTO_TCP) ||
+                               (dns_len != len && protocol == IPPROTO_UDP))
+               DBG("Packet length mismatch, sent %d wanted %d dns %d",
+                       err, len, dns_len);
 }
 
 static void send_response(int sk, unsigned char *buf, int len,
@@ -412,7 +441,7 @@ static void send_response(int sk, unsigned char *buf, int len,
        struct domain_hdr *hdr;
        int err, offset = protocol_offset(protocol);
 
-       DBG("");
+       DBG("sk %d", sk);
 
        if (offset < 0)
                return;
@@ -431,10 +460,10 @@ static void send_response(int sk, unsigned char *buf, int len,
        hdr->nscount = 0;
        hdr->arcount = 0;
 
-       err = sendto(sk, buf, len, 0, to, tolen);
+       err = sendto(sk, buf, len, MSG_NOSIGNAL, to, tolen);
        if (err < 0) {
-               connman_error("Failed to send DNS response: %s",
-                               strerror(errno));
+               connman_error("Failed to send DNS response to %d: %s",
+                               sk, strerror(errno));
                return;
        }
 }
@@ -459,7 +488,7 @@ static gboolean request_timeout(gpointer user_data)
 
                sk = g_io_channel_unix_get_fd(ifdata->udp_listener_channel);
 
-               err = sendto(sk, req->resp, req->resplen, 0,
+               err = sendto(sk, req->resp, req->resplen, MSG_NOSIGNAL,
                                                &req->sa, req->sa_len);
                if (err < 0)
                        return FALSE;
@@ -638,13 +667,22 @@ static uint16_t cache_check_validity(char *question, uint16_t type,
        return type;
 }
 
-static struct cache_entry *cache_check(gpointer request, int *qtype)
+static struct cache_entry *cache_check(gpointer request, int *qtype, int proto)
 {
-       char *question = request + 12;
+       char *question;
        struct cache_entry *entry;
        struct domain_question *q;
        uint16_t type;
-       int offset;
+       int offset, proto_offset;
+
+       if (request == NULL)
+               return NULL;
+
+       proto_offset = protocol_offset(proto);
+       if (proto_offset < 0)
+               return NULL;
+
+       question = request + proto_offset + 12;
 
        offset = strlen(question) + 1;
        q = (void *) (question + offset);
@@ -1210,6 +1248,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
        int offset = protocol_offset(srv->protocol);
        int err, qlen, ttl = 0;
        uint16_t answers = 0, type = 0, class = 0;
+       struct domain_hdr *hdr = (void *)(msg + offset);
        struct domain_question *q;
        struct cache_entry *entry;
        struct cache_data *data;
@@ -1234,12 +1273,13 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                next_refresh = current_time + 30;
        }
 
-
-       /* Continue only if response code is 0 (=ok) */
-       if (msg[3] & 0x0f)
+       if (offset < 0)
                return 0;
 
-       if (offset < 0)
+       DBG("offset %d hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode);
+
+       /* Continue only if response code is 0 (=ok) */
+       if (hdr->rcode != 0)
                return 0;
 
        rsplen = sizeof(response) - 1;
@@ -1256,21 +1296,30 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
         * to cache the negative response.
         */
        if ((err == -ENOMSG || err == -ENOBUFS) &&
-                       reply_query_type(msg, msg_len) == 28) {
+                       reply_query_type(msg + offset,
+                                       msg_len - offset) == 28) {
                entry = g_hash_table_lookup(cache, question);
                if (entry && entry->ipv4 && entry->ipv6 == NULL) {
+                       int cache_offset = 0;
+
                        data = g_try_new(struct cache_data, 1);
                        if (data == NULL)
                                return -ENOMEM;
                        data->inserted = entry->ipv4->inserted;
                        data->type = type;
-                       data->answers = msg[5];
+                       data->answers = ntohs(hdr->ancount);
                        data->timeout = entry->ipv4->timeout;
-                       data->data_len = msg_len;
-                       data->data = ptr = g_malloc(msg_len);
+                       if (srv->protocol == IPPROTO_UDP)
+                               cache_offset = 2;
+                       data->data_len = msg_len + cache_offset;
+                       data->data = ptr = g_malloc(data->data_len);
+                       ptr[0] = (data->data_len - 2) / 256;
+                       ptr[1] = (data->data_len - 2) - ptr[0] * 256;
+                       if (srv->protocol == IPPROTO_UDP)
+                               ptr += 2;
                        data->valid_until = entry->ipv4->valid_until;
                        data->cache_until = entry->ipv4->cache_until;
-                       memcpy(data->data, msg, msg_len);
+                       memcpy(ptr, msg, msg_len);
                        entry->ipv6 = data;
                        /*
                         * we will get a "hit" when we serve the response
@@ -1350,7 +1399,12 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
        data->type = type;
        data->answers = answers;
        data->timeout = ttl;
-       data->data_len = 12 + qlen + 1 + 2 + 2 + rsplen;
+       /*
+        * The "2" in start of the length is the TCP offset. We allocate it
+        * here even for UDP packet because it simplifies the sending
+        * of cached packet.
+        */
+       data->data_len = 2 + 12 + qlen + 1 + 2 + 2 + rsplen;
        data->data = ptr = g_malloc(data->data_len);
        data->valid_until = current_time + ttl;
 
@@ -1370,13 +1424,24 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                return -ENOMEM;
        }
 
-       memcpy(ptr, msg, 12);
-       memcpy(ptr + 12, question, qlen + 1); /* copy also the \0 */
+       /*
+        * We cache the two extra bytes at the start of the message
+        * in a TCP packet. When sending UDP packet, we skip the first
+        * two bytes. This way we do not need to know the format
+        * (UDP/TCP) of the cached message.
+        */
+       ptr[0] = (data->data_len - 2) / 256;
+       ptr[1] = (data->data_len - 2) - ptr[0] * 256;
+       if (srv->protocol == IPPROTO_UDP)
+               ptr += 2;
+
+       memcpy(ptr, msg, offset + 12);
+       memcpy(ptr + offset + 12, question, qlen + 1); /* copy also the \0 */
 
-       q = (void *) (ptr + 12 + qlen + 1);
+       q = (void *) (ptr + offset + 12 + qlen + 1);
        q->type = htons(type);
        q->class = htons(class);
-       memcpy(ptr + 12 + qlen + 1 + sizeof(struct domain_question),
+       memcpy(ptr + offset + 12 + qlen + 1 + sizeof(struct domain_question),
                response, rsplen);
 
        if (new_entry == TRUE) {
@@ -1384,10 +1449,15 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                cache_size++;
        }
 
-       DBG("cache %d %squestion \"%s\" type %d ttl %d size %zd",
+       DBG("cache %d %squestion \"%s\" type %d ttl %d size %zd packet %u "
+                                                               "dns len %u",
                cache_size, new_entry ? "new " : "old ",
                question, type, ttl,
-               sizeof(*entry) + sizeof(*data) + data->data_len + qlen);
+               sizeof(*entry) + sizeof(*data) + data->data_len + qlen,
+               data->data_len,
+               srv->protocol == IPPROTO_TCP ?
+                       (unsigned int)(data->data[0] * 256 + data->data[1]) :
+                       data->data_len);
 
        return 0;
 }
@@ -1400,7 +1470,7 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
        char *dot, *lookup = (char *) name;
        struct cache_entry *entry;
 
-       entry = cache_check(request, &type);
+       entry = cache_check(request, &type, req->protocol);
        if (entry != NULL) {
                int ttl_left = 0;
                struct cache_data *data;
@@ -1424,11 +1494,10 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
                }
 
                if (data != NULL && req->protocol == IPPROTO_UDP) {
-                       int sk;
-                       sk = g_io_channel_unix_get_fd(
+                       int udp_sk = g_io_channel_unix_get_fd(
                                        req->ifdata->udp_listener_channel);
 
-                       send_cached_response(sk, data->data,
+                       send_cached_response(udp_sk, data->data,
                                data->data_len, &req->sa, req->sa_len,
                                IPPROTO_UDP, req->srcid, data->answers,
                                ttl_left);
@@ -1438,9 +1507,15 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
 
        sk = g_io_channel_unix_get_fd(server->channel);
 
-       err = send(sk, request, req->request_len, 0);
-       if (err < 0)
+       err = sendto(sk, request, req->request_len, MSG_NOSIGNAL,
+                       server->server_addr, server->server_addr_len);
+       if (err < 0) {
+               DBG("Cannot send message to server %s sock %d "
+                       "protocol %d (%s/%d)",
+                       server->server, sk, server->protocol,
+                       strerror(errno), errno);
                return -EIO;
+       }
 
        req->numserv++;
 
@@ -1495,7 +1570,10 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
                        alt[1] = req_len & 0xff;
                }
 
-               err = send(sk, alt, req->request_len + domlen, 0);
+               DBG("req %p dstid 0x%04x altid 0x%04x", req, req->dstid,
+                               req->altid);
+
+               err = send(sk, alt, req->request_len + domlen, MSG_NOSIGNAL);
                if (err < 0)
                        return -EIO;
 
@@ -1505,6 +1583,17 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
        return 0;
 }
 
+static void destroy_request_data(struct request_data *req)
+{
+       if (req->timeout > 0)
+               g_source_remove(req->timeout);
+
+       g_free(req->resp);
+       g_free(req->request);
+       g_free(req->name);
+       g_free(req);
+}
+
 static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                                struct server_data *data)
 {
@@ -1525,7 +1614,8 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
        if (req == NULL)
                return -EINVAL;
 
-       DBG("id 0x%04x rcode %d", hdr->id, hdr->rcode);
+       DBG("req %p dstid 0x%04x altid 0x%04x rcode %d",
+                       req, req->dstid, req->altid, hdr->rcode);
 
        ifdata = req->ifdata;
 
@@ -1541,17 +1631,25 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                 * remove it before forwarding the reply.
                 */
                if (req->append_domain == TRUE) {
+                       unsigned int domain_len = 0;
                        unsigned char *ptr;
                        uint8_t host_len;
-                       unsigned int domain_len;
+                       unsigned int header_len;
 
                        /*
                         * ptr points to the first char of the hostname.
                         * ->hostname.domain.net
                         */
-                       ptr = reply + offset + sizeof(struct domain_hdr);
+                       header_len = offset + sizeof(struct domain_hdr);
+                       ptr = reply + header_len;
                        host_len = *ptr;
-                       domain_len = strlen((const char *)ptr + host_len + 1);
+                       if (host_len > 0)
+                               domain_len = strnlen((const char *)ptr + 1 +
+                                               host_len,
+                                               reply_len - header_len);
+
+
+                       DBG("host len %d domain len %d", host_len, domain_len);
 
                        /*
                         * Remove the domain name and replace it by the end
@@ -1571,7 +1669,7 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                                 */
                                memmove(ptr + host_len + 1,
                                        ptr + host_len + domain_len + 1,
-                                       reply_len - (ptr - reply + domain_len));
+                                       reply_len - header_len - domain_len);
 
                                reply_len = reply_len - domain_len;
                        }
@@ -1593,9 +1691,6 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
        if (hdr->rcode > 0 && req->numresp < req->numserv)
                return -EINVAL;
 
-       if (req->timeout > 0)
-               g_source_remove(req->timeout);
-
        request_list = g_slist_remove(request_list, req);
 
        if (protocol == IPPROTO_UDP) {
@@ -1604,12 +1699,17 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                             &req->sa, req->sa_len);
        } else {
                sk = req->client_sk;
-               err = send(sk, req->resp, req->resplen, 0);
+               err = send(sk, req->resp, req->resplen, MSG_NOSIGNAL);
                close(sk);
        }
 
-       g_free(req->resp);
-       g_free(req);
+       if (err < 0)
+               DBG("Cannot send msg, sk %d proto %d errno %d/%s", sk,
+                       protocol, errno, strerror(errno));
+       else
+               DBG("proto %d sent %d bytes to %d", protocol, err, sk);
+
+       destroy_request_data(req);
 
        return err;
 }
@@ -1650,26 +1750,45 @@ static gboolean try_remove_cache(gpointer user_data)
        return FALSE;
 }
 
-static void destroy_server(struct server_data *server)
+static void server_destroy_socket(struct server_data *data)
 {
-       GList *list;
+       DBG("index %d server %s proto %d", data->index,
+                                       data->server, data->protocol);
 
-       DBG("interface %s server %s", server->interface, server->server);
+       if (data->watch > 0) {
+               g_source_remove(data->watch);
+               data->watch = 0;
+       }
 
-       server_list = g_slist_remove(server_list, server);
+       if (data->timeout > 0) {
+               g_source_remove(data->timeout);
+               data->timeout = 0;
+       }
+
+       if (data->channel != NULL) {
+               g_io_channel_shutdown(data->channel, TRUE, NULL);
+               g_io_channel_unref(data->channel);
+               data->channel = NULL;
+       }
+
+       g_free(data->incoming_reply);
+       data->incoming_reply = NULL;
+}
 
-       if (server->watch > 0)
-               g_source_remove(server->watch);
+static void destroy_server(struct server_data *server)
+{
+       GList *list;
 
-       if (server->timeout > 0)
-               g_source_remove(server->timeout);
+       DBG("index %d server %s sock %d", server->index, server->server,
+                       server->channel != NULL ?
+                       g_io_channel_unix_get_fd(server->channel): -1);
 
-       g_io_channel_unref(server->channel);
+       server_list = g_slist_remove(server_list, server);
+       server_destroy_socket(server);
 
-       if (server->protocol == IPPROTO_UDP)
-               connman_info("Removing DNS server %s", server->server);
+       if (server->protocol == IPPROTO_UDP && server->enabled)
+               DBG("Removing DNS server %s", server->server);
 
-       g_free(server->incoming_reply);
        g_free(server->server);
        for (list = server->domains; list; list = list->next) {
                char *domain = list->data;
@@ -1677,7 +1796,7 @@ static void destroy_server(struct server_data *server)
                server->domains = g_list_remove(server->domains, domain);
                g_free(domain);
        }
-       g_free(server->interface);
+       g_free(server->server_addr);
 
        /*
         * We do not remove cache right away but delay it few seconds.
@@ -1702,7 +1821,7 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition,
 
        if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
                connman_error("Error with UDP server %s", data->server);
-               data->watch = 0;
+               server_destroy_socket(data);
                return FALSE;
        }
 
@@ -1732,7 +1851,7 @@ static gboolean tcp_server_event(GIOChannel *channel, GIOCondition condition,
        if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
                GSList *list;
 hangup:
-               DBG("TCP server channel closed");
+               DBG("TCP server channel closed, sk %d", sk);
 
                /*
                 * Discard any partial response which is buffered; better
@@ -1775,9 +1894,10 @@ hangup:
        if ((condition & G_IO_OUT) && !server->connected) {
                GSList *list;
                GList *domains;
+               int no_request_sent = TRUE;
                struct server_data *udp_server;
 
-               udp_server = find_server(server->interface, server->server,
+               udp_server = find_server(server->index, server->server,
                                                                IPPROTO_UDP);
                if (udp_server != NULL) {
                        for (domains = udp_server->domains; domains;
@@ -1800,29 +1920,48 @@ hangup:
                        server->timeout = 0;
                }
 
-               for (list = request_list; list; list = list->next) {
+               for (list = request_list; list; ) {
                        struct request_data *req = list->data;
+                       int status;
 
-                       if (req->protocol == IPPROTO_UDP)
+                       if (req->protocol == IPPROTO_UDP) {
+                               list = list->next;
                                continue;
+                       }
 
                        DBG("Sending req %s over TCP", (char *)req->name);
 
+                       status = ns_resolv(server, req,
+                                               req->request, req->name);
+                       if (status > 0) {
+                               /*
+                                * A cached result was sent,
+                                * so the request can be released
+                                */
+                               list = list->next;
+                               request_list = g_slist_remove(request_list, req);
+                               destroy_request_data(req);
+                               continue;
+                       }
+
+                       if (status < 0) {
+                               list = list->next;
+                               continue;
+                       }
+
+                       no_request_sent = FALSE;
+
                        if (req->timeout > 0)
                                g_source_remove(req->timeout);
 
                        req->timeout = g_timeout_add_seconds(30,
                                                request_timeout, req);
-                       if (ns_resolv(server, req, req->request,
-                                       req->name) > 0) {
-                               /* We sent cached result so no need for timeout
-                                * handler.
-                                */
-                               if (req->timeout > 0) {
-                                       g_source_remove(req->timeout);
-                                       req->timeout = 0;
-                               }
-                       }
+                       list = list->next;
+               }
+
+               if (no_request_sent == TRUE) {
+                       destroy_server(server);
+                       return FALSE;
                }
 
        } else if (condition & G_IO_IN) {
@@ -1849,7 +1988,7 @@ hangup:
                        reply_len = reply_len_buf[1] | reply_len_buf[0] << 8;
                        reply_len += 2;
 
-                       DBG("TCP reply %d bytes", reply_len);
+                       DBG("TCP reply %d bytes from %d", reply_len, sk);
 
                        reply = g_try_malloc(sizeof(*reply) + reply_len + 2);
                        if (!reply)
@@ -1906,82 +2045,56 @@ static gboolean tcp_idle_timeout(gpointer user_data)
        return FALSE;
 }
 
-static struct server_data *create_server(const char *interface,
-                                       const char *domain, const char *server,
-                                       int protocol)
+static int server_create_socket(struct server_data *data)
 {
-       struct addrinfo hints, *rp;
-       struct server_data *data;
-       int sk, ret;
-
-       DBG("interface %s server %s", interface, server);
-
-       memset(&hints, 0, sizeof(hints));
-
-       switch (protocol) {
-       case IPPROTO_UDP:
-               hints.ai_socktype = SOCK_DGRAM;
-               break;
-
-       case IPPROTO_TCP:
-               hints.ai_socktype = SOCK_STREAM;
-               break;
+       int sk, err;
+       char *interface;
 
-       default:
-               return NULL;
-       }
-       hints.ai_family = AF_UNSPEC;
-       hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_NUMERICHOST;
+       DBG("index %d server %s proto %d", data->index,
+                                       data->server, data->protocol);
 
-       ret = getaddrinfo(server, "53", &hints, &rp);
-       if (ret) {
-               connman_error("Failed to parse server %s address: %s\n",
-                             server, gai_strerror(ret));
-               return NULL;
-       }
-       /* Do not blindly copy this code elsewhere; it doesn't loop over the
-          results using ->ai_next as it should. That's OK in *this* case
-          because it was a numeric lookup; we *know* there's only one. */
-
-       sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+       sk = socket(data->server_addr->sa_family,
+               data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM,
+               data->protocol);
        if (sk < 0) {
-               connman_error("Failed to create server %s socket", server);
-               freeaddrinfo(rp);
-               return NULL;
+               err = errno;
+               connman_error("Failed to create server %s socket",
+                                                       data->server);
+               server_destroy_socket(data);
+               return -err;
        }
 
+       DBG("sk %d", sk);
+
+       interface = connman_inet_ifname(data->index);
        if (interface != NULL) {
                if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE,
-                               interface, strlen(interface) + 1) < 0) {
+                                       interface,
+                                       strlen(interface) + 1) < 0) {
+                       err = errno;
                        connman_error("Failed to bind server %s "
                                                "to interface %s",
-                                                       server, interface);
-                       freeaddrinfo(rp);
+                                               data->server, interface);
                        close(sk);
-                       return NULL;
+                       server_destroy_socket(data);
+                       g_free(interface);
+                       return -err;
                }
-       }
-
-       data = g_try_new0(struct server_data, 1);
-       if (data == NULL) {
-               connman_error("Failed to allocate server %s data", server);
-               freeaddrinfo(rp);
-               close(sk);
-               return NULL;
+               g_free(interface);
        }
 
        data->channel = g_io_channel_unix_new(sk);
        if (data->channel == NULL) {
-               connman_error("Failed to create server %s channel", server);
-               freeaddrinfo(rp);
+               connman_error("Failed to create server %s channel",
+                                                       data->server);
                close(sk);
-               g_free(data);
-               return NULL;
+               server_destroy_socket(data);
+               return -ENOMEM;
        }
 
        g_io_channel_set_close_on_unref(data->channel, TRUE);
 
-       if (protocol == IPPROTO_TCP) {
+       if (data->protocol == IPPROTO_TCP) {
                g_io_channel_set_flags(data->channel, G_IO_FLAG_NONBLOCK, NULL);
                data->watch = g_io_add_watch(data->channel,
                        G_IO_OUT | G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
@@ -1993,39 +2106,16 @@ static struct server_data *create_server(const char *interface,
                        G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
                                                udp_server_event, data);
 
-       data->interface = g_strdup(interface);
-       if (domain)
-               data->domains = g_list_append(data->domains, g_strdup(domain));
-       data->server = g_strdup(server);
-       data->protocol = protocol;
-
-       ret = connect(sk, rp->ai_addr, rp->ai_addrlen);
-       freeaddrinfo(rp);
-       if (ret < 0) {
-               if ((protocol == IPPROTO_TCP && errno != EINPROGRESS) ||
-                               protocol == IPPROTO_UDP) {
-                       GList *list;
-
-                       connman_error("Failed to connect to server %s", server);
-                       if (data->watch > 0)
-                               g_source_remove(data->watch);
-                       if (data->timeout > 0)
-                               g_source_remove(data->timeout);
-
-                       g_io_channel_unref(data->channel);
-                       close(sk);
+       if (connect(sk, data->server_addr, data->server_addr_len) < 0) {
+               err = errno;
 
-                       g_free(data->server);
-                       g_free(data->interface);
-                       for (list = data->domains; list; list = list->next) {
-                               char *domain = list->data;
+               if ((data->protocol == IPPROTO_TCP && errno != EINPROGRESS) ||
+                               data->protocol == IPPROTO_UDP) {
 
-                               data->domains = g_list_remove(data->domains,
-                                                                       domain);
-                               g_free(domain);
-                       }
-                       g_free(data);
-                       return NULL;
+                       connman_error("Failed to connect to server %s",
+                                                               data->server);
+                       server_destroy_socket(data);
+                       return -err;
                }
        }
 
@@ -2035,17 +2125,98 @@ static struct server_data *create_server(const char *interface,
                                        NULL,
                                        cache_element_destroy);
 
+       return 0;
+}
+
+static struct server_data *create_server(int index,
+                                       const char *domain, const char *server,
+                                       int protocol)
+{
+       struct server_data *data;
+       struct addrinfo hints, *rp;
+       int ret;
+
+       DBG("index %d server %s", index, server);
+
+       data = g_try_new0(struct server_data, 1);
+       if (data == NULL) {
+               connman_error("Failed to allocate server %s data", server);
+               return NULL;
+       }
+
+       data->index = index;
+       if (domain)
+               data->domains = g_list_append(data->domains, g_strdup(domain));
+       data->server = g_strdup(server);
+       data->protocol = protocol;
+
+       memset(&hints, 0, sizeof(hints));
+
+       switch (protocol) {
+       case IPPROTO_UDP:
+               hints.ai_socktype = SOCK_DGRAM;
+               break;
+
+       case IPPROTO_TCP:
+               hints.ai_socktype = SOCK_STREAM;
+               break;
+
+       default:
+               destroy_server(data);
+               return NULL;
+       }
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST;
+
+       ret = getaddrinfo(data->server, "53", &hints, &rp);
+       if (ret) {
+               connman_error("Failed to parse server %s address: %s\n",
+                             data->server, gai_strerror(ret));
+               destroy_server(data);
+               return NULL;
+       }
+
+       /* Do not blindly copy this code elsewhere; it doesn't loop over the
+          results using ->ai_next as it should. That's OK in *this* case
+          because it was a numeric lookup; we *know* there's only one. */
+
+       data->server_addr_len = rp->ai_addrlen;
+
+       switch (rp->ai_family) {
+       case AF_INET:
+               data->server_addr = (struct sockaddr *)
+                                       g_try_new0(struct sockaddr_in, 1);
+               break;
+       case AF_INET6:
+               data->server_addr = (struct sockaddr *)
+                                       g_try_new0(struct sockaddr_in6, 1);
+               break;
+       default:
+               connman_error("Wrong address family %d", rp->ai_family);
+               break;
+       }
+       if (data->server_addr == NULL) {
+               freeaddrinfo(rp);
+               destroy_server(data);
+               return NULL;
+       }
+       memcpy(data->server_addr, rp->ai_addr, rp->ai_addrlen);
+       freeaddrinfo(rp);
+
+       if (server_create_socket(data) != 0) {
+               destroy_server(data);
+               return NULL;
+       }
+
        if (protocol == IPPROTO_UDP) {
                /* Enable new servers by default */
                data->enabled = TRUE;
-               connman_info("Adding DNS server %s", data->server);
+               DBG("Adding DNS server %s", data->server);
 
                server_list = g_slist_append(server_list, data);
-
-               return data;
        }
 
-       return NULL;
+       return data;
 }
 
 static gboolean resolv(struct request_data *req,
@@ -2056,15 +2227,22 @@ static gboolean resolv(struct request_data *req,
        for (list = server_list; list; list = list->next) {
                struct server_data *data = list->data;
 
+               if (data->protocol == IPPROTO_TCP) {
+                       DBG("server %s ignored proto TCP", data->server);
+                       continue;
+               }
+
                DBG("server %s enabled %d", data->server, data->enabled);
 
                if (data->enabled == FALSE)
                        continue;
 
-               if (data->watch == 0 && data->protocol == IPPROTO_UDP)
-                       data->watch = g_io_add_watch(data->channel,
-                               G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
-                                               udp_server_event, data);
+               if (data->channel == NULL && data->protocol == IPPROTO_UDP) {
+                       if (server_create_socket(data) < 0) {
+                               DBG("socket creation failed while resolving");
+                               continue;
+                       }
+               }
 
                if (ns_resolv(data, req, request, name) > 0)
                        return TRUE;
@@ -2073,11 +2251,11 @@ static gboolean resolv(struct request_data *req,
        return FALSE;
 }
 
-static void append_domain(const char *interface, const char *domain)
+static void append_domain(int index, const char *domain)
 {
        GSList *list;
 
-       DBG("interface %s domain %s", interface, domain);
+       DBG("index %d domain %s", index, domain);
 
        if (domain == NULL)
                return;
@@ -2088,10 +2266,10 @@ static void append_domain(const char *interface, const char *domain)
                char *dom;
                gboolean dom_found = FALSE;
 
-               if (data->interface == NULL)
+               if (data->index < 0)
                        continue;
 
-               if (g_str_equal(data->interface, interface) == FALSE)
+               if (data->index != index)
                        continue;
 
                for (dom_list = data->domains; dom_list;
@@ -2111,18 +2289,18 @@ static void append_domain(const char *interface, const char *domain)
        }
 }
 
-int __connman_dnsproxy_append(const char *interface, const char *domain,
+int __connman_dnsproxy_append(int index, const char *domain,
                                                        const char *server)
 {
        struct server_data *data;
 
-       DBG("interface %s server %s", interface, server);
+       DBG("index %d server %s", index, server);
 
        if (server == NULL && domain == NULL)
                return -EINVAL;
 
        if (server == NULL) {
-               append_domain(interface, domain);
+               append_domain(index, domain);
 
                return 0;
        }
@@ -2130,35 +2308,35 @@ int __connman_dnsproxy_append(const char *interface, const char *domain,
        if (g_str_equal(server, "127.0.0.1") == TRUE)
                return -ENODEV;
 
-       data = find_server(interface, server, IPPROTO_UDP);
+       data = find_server(index, server, IPPROTO_UDP);
        if (data != NULL) {
-               append_domain(interface, domain);
+               append_domain(index, domain);
                return 0;
        }
 
-       data = create_server(interface, domain, server, IPPROTO_UDP);
+       data = create_server(index, domain, server, IPPROTO_UDP);
        if (data == NULL)
                return -EIO;
 
        return 0;
 }
 
-static void remove_server(const char *interface, const char *domain,
+static void remove_server(int index, const char *domain,
                        const char *server, int protocol)
 {
        struct server_data *data;
 
-       data = find_server(interface, server, protocol);
+       data = find_server(index, server, protocol);
        if (data == NULL)
                return;
 
        destroy_server(data);
 }
 
-int __connman_dnsproxy_remove(const char *interface, const char *domain,
+int __connman_dnsproxy_remove(int index, const char *domain,
                                                        const char *server)
 {
-       DBG("interface %s server %s", interface, server);
+       DBG("index %d server %s", index, server);
 
        if (server == NULL)
                return -EINVAL;
@@ -2166,8 +2344,8 @@ int __connman_dnsproxy_remove(const char *interface, const char *domain,
        if (g_str_equal(server, "127.0.0.1") == TRUE)
                return -ENODEV;
 
-       remove_server(interface, domain, server, IPPROTO_UDP);
-       remove_server(interface, domain, server, IPPROTO_TCP);
+       remove_server(index, domain, server, IPPROTO_UDP);
+       remove_server(index, domain, server, IPPROTO_TCP);
 
        return 0;
 }
@@ -2176,17 +2354,26 @@ void __connman_dnsproxy_flush(void)
 {
        GSList *list;
 
-       list = request_pending_list;
+       list = request_list;
        while (list) {
                struct request_data *req = list->data;
 
                list = list->next;
 
-               request_pending_list =
-                               g_slist_remove(request_pending_list, req);
-               resolv(req, req->request, req->name);
-               g_free(req->request);
-               g_free(req->name);
+               if (resolv(req, req->request, req->name) == TRUE) {
+                       /*
+                        * A cached result was sent,
+                        * so the request can be released
+                        */
+                       request_list =
+                               g_slist_remove(request_list, req);
+                       destroy_request_data(req);
+                       continue;
+               }
+
+               if (req->timeout > 0)
+                       g_source_remove(req->timeout);
+               req->timeout = g_timeout_add_seconds(5, request_timeout, req);
        }
 }
 
@@ -2200,12 +2387,12 @@ static void dnsproxy_offline_mode(connman_bool_t enabled)
                struct server_data *data = list->data;
 
                if (enabled == FALSE) {
-                       connman_info("Enabling DNS server %s", data->server);
+                       DBG("Enabling DNS server %s", data->server);
                        data->enabled = TRUE;
                        cache_invalidate();
                        cache_refresh();
                } else {
-                       connman_info("Disabling DNS server %s", data->server);
+                       DBG("Disabling DNS server %s", data->server);
                        data->enabled = FALSE;
                        cache_invalidate();
                }
@@ -2215,7 +2402,7 @@ static void dnsproxy_offline_mode(connman_bool_t enabled)
 static void dnsproxy_default_changed(struct connman_service *service)
 {
        GSList *list;
-       char *interface;
+       int index;
 
        DBG("service %p", service);
 
@@ -2228,23 +2415,22 @@ static void dnsproxy_default_changed(struct connman_service *service)
                return;
        }
 
-       interface = connman_service_get_interface(service);
-       if (interface == NULL)
+       index = __connman_service_get_index(service);
+       if (index < 0)
                return;
 
        for (list = server_list; list; list = list->next) {
                struct server_data *data = list->data;
 
-               if (g_strcmp0(data->interface, interface) == 0) {
-                       connman_info("Enabling DNS server %s", data->server);
+               if (data->index == index) {
+                       DBG("Enabling DNS server %s", data->server);
                        data->enabled = TRUE;
                } else {
-                       connman_info("Disabling DNS server %s", data->server);
+                       DBG("Disabling DNS server %s", data->server);
                        data->enabled = FALSE;
                }
        }
 
-       g_free(interface);
        cache_refresh();
 }
 
@@ -2282,23 +2468,23 @@ static int parse_request(unsigned char *buf, int len,
        remain = len - sizeof(struct domain_hdr);
 
        while (remain > 0) {
-               uint8_t len = *ptr;
+               uint8_t label_len = *ptr;
 
-               if (len == 0x00) {
+               if (label_len == 0x00) {
                        last_label = (char *) (ptr + 1);
                        break;
                }
 
-               if (used + len + 1 > size)
+               if (used + label_len + 1 > size)
                        return -ENOBUFS;
 
-               strncat(name, (char *) (ptr + 1), len);
+               strncat(name, (char *) (ptr + 1), label_len);
                strcat(name, ".");
 
-               used += len + 1;
+               used += label_len + 1;
 
-               ptr += len + 1;
-               remain -= len + 1;
+               ptr += label_len + 1;
+               remain -= label_len + 1;
        }
 
        if (last_label && arcount && remain >= 9 && last_label[4] == 0 &&
@@ -2334,12 +2520,13 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition,
        unsigned char buf[768];
        char query[512];
        struct request_data *req;
-       struct server_data *server;
        int sk, client_sk, len, err;
        struct sockaddr_in6 client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        GSList *list;
        struct listener_data *ifdata = user_data;
+       int waiting_for_connect = FALSE, qtype = 0;
+       struct cache_entry *entry;
 
        DBG("condition 0x%x", condition);
 
@@ -2366,7 +2553,8 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition,
        if (len < 2)
                return TRUE;
 
-       DBG("Received %d bytes (id 0x%04x)", len, buf[2] | buf[3] << 8);
+       DBG("Received %d bytes (id 0x%04x) from %d", len,
+               buf[2] | buf[3] << 8, client_sk);
 
        err = parse_request(buf + 2, len - 2, query, sizeof(query));
        if (err < 0 || (g_slist_length(server_list) == 0)) {
@@ -2383,13 +2571,9 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition,
        req->client_sk = client_sk;
        req->protocol = IPPROTO_TCP;
 
-       request_id += 2;
-       if (request_id == 0x0000 || request_id == 0xffff)
-               request_id += 2;
-
        req->srcid = buf[2] | (buf[3] << 8);
-       req->dstid = request_id;
-       req->altid = request_id + 1;
+       req->dstid = get_id();
+       req->altid = get_id();
        req->request_len = len;
 
        buf[2] = req->dstid & 0xff;
@@ -2398,63 +2582,82 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition,
        req->numserv = 0;
        req->ifdata = (struct listener_data *) ifdata;
        req->append_domain = FALSE;
-       request_list = g_slist_append(request_list, req);
 
-       for (list = server_list; list; list = list->next) {
-               struct server_data *data = list->data;
-               GList *domains;
+       /*
+        * Check if the answer is found in the cache before
+        * creating sockets to the server.
+        */
+       entry = cache_check(buf, &qtype, IPPROTO_TCP);
+       if (entry != NULL) {
+               int ttl_left = 0;
+               struct cache_data *data;
 
-               if (data->protocol != IPPROTO_UDP || data->enabled == FALSE)
-                       continue;
+               DBG("cache hit %s type %s", query, qtype == 1 ? "A" : "AAAA");
+               if (qtype == 1)
+                       data = entry->ipv4;
+               else
+                       data = entry->ipv6;
 
-               server = create_server(data->interface, NULL,
-                                       data->server, IPPROTO_TCP);
+               if (data != NULL) {
+                       ttl_left = data->valid_until - time(NULL);
+                       entry->hits++;
 
-               /*
-                * If server is NULL, we're not connected yet.
-                * Copy the relevant buffers and continue with
-                * the next nameserver.
-                * The request will actually be sent once we're
-                * properly connected over TCP to this nameserver.
-                */
-               if (server == NULL) {
-                       req->request = g_try_malloc0(req->request_len);
-                       if (req->request == NULL)
-                               return TRUE;
+                       send_cached_response(client_sk, data->data,
+                                       data->data_len, NULL, 0, IPPROTO_TCP,
+                                       req->srcid, data->answers, ttl_left);
 
-                       memcpy(req->request, buf, req->request_len);
+                       g_free(req);
+                       return TRUE;
+               } else
+                       DBG("data missing, ignoring cache for this query");
+       }
 
-                       req->name = g_try_malloc0(sizeof(query));
-                       if (req->name == NULL) {
-                               g_free(req->request);
-                               return TRUE;
-                       }
-                       memcpy(req->name, query, sizeof(query));
+       for (list = server_list; list; list = list->next) {
+               struct server_data *data = list->data;
 
+               if (data->protocol != IPPROTO_UDP || data->enabled == FALSE)
                        continue;
-               }
 
-               if (req->timeout > 0)
-                       g_source_remove(req->timeout);
+               if(create_server(data->index, NULL,
+                                       data->server, IPPROTO_TCP) == NULL)
+                       continue;
 
-               for (domains = data->domains; domains;
-                               domains = domains->next) {
-                       char *dom = domains->data;
+               waiting_for_connect = TRUE;
+       }
 
-                       DBG("Adding domain %s to %s", dom, server->server);
+       if (waiting_for_connect == FALSE) {
+               /* No server is waiting for connect */
+               send_response(client_sk, buf, len, NULL, 0, IPPROTO_TCP);
+               g_free(req);
+               return TRUE;
+       }
 
-                       server->domains = g_list_append(server->domains,
-                                               g_strdup(dom));
-               }
+       /*
+        * The server is not connected yet.
+        * Copy the relevant buffers.
+        * The request will actually be sent once we're
+        * properly connected over TCP to the nameserver.
+        */
+       req->request = g_try_malloc0(req->request_len);
+       if (req->request == NULL) {
+               send_response(client_sk, buf, len, NULL, 0, IPPROTO_TCP);
+               g_free(req);
+               return TRUE;
+       }
+       memcpy(req->request, buf, req->request_len);
 
-               req->timeout = g_timeout_add_seconds(30, request_timeout, req);
-               if (ns_resolv(server, req, buf, query) > 0) {
-                       if (req->timeout > 0) {
-                               g_source_remove(req->timeout);
-                               req->timeout = 0;
-                       }
-               }
+       req->name = g_try_malloc0(sizeof(query));
+       if (req->name == NULL) {
+               send_response(client_sk, buf, len, NULL, 0, IPPROTO_TCP);
+               g_free(req->request);
+               g_free(req);
+               return TRUE;
        }
+       memcpy(req->name, query, sizeof(query));
+
+       req->timeout = g_timeout_add_seconds(30, request_timeout, req);
+
+       request_list = g_slist_append(request_list, req);
 
        return TRUE;
 }
@@ -2502,13 +2705,9 @@ static gboolean udp_listener_event(GIOChannel *channel, GIOCondition condition,
        req->client_sk = 0;
        req->protocol = IPPROTO_UDP;
 
-       request_id += 2;
-       if (request_id == 0x0000 || request_id == 0xffff)
-               request_id += 2;
-
        req->srcid = buf[0] | (buf[1] << 8);
-       req->dstid = request_id;
-       req->altid = request_id + 1;
+       req->dstid = get_id();
+       req->altid = get_id();
        req->request_len = len;
 
        buf[0] = req->dstid & 0xff;
@@ -2542,9 +2741,9 @@ static int create_dns_listener(int protocol, struct listener_data *ifdata)
        socklen_t slen;
        int sk, type, v6only = 0;
        int family = AF_INET6;
+       char *interface;
 
-
-       DBG("interface %s", ifdata->ifname);
+       DBG("index %d", ifdata->index);
 
        switch (protocol) {
        case IPPROTO_UDP:
@@ -2572,13 +2771,17 @@ static int create_dns_listener(int protocol, struct listener_data *ifdata)
                return -EIO;
        }
 
-       if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE,
-                                       ifdata->ifname,
-                                       strlen(ifdata->ifname) + 1) < 0) {
+       interface = connman_inet_ifname(ifdata->index);
+       if (interface == NULL || setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE,
+                                       interface,
+                                       strlen(interface) + 1) < 0) {
                connman_error("Failed to bind %s listener interface", proto);
                close(sk);
+               g_free(interface);
                return -EIO;
        }
+       g_free(interface);
+
        /* Ensure it accepts Legacy IP connections too */
        if (family == AF_INET6 &&
                        setsockopt(sk, SOL_IPV6, IPV6_V6ONLY,
@@ -2639,7 +2842,7 @@ static int create_dns_listener(int protocol, struct listener_data *ifdata)
 
 static void destroy_udp_listener(struct listener_data *ifdata)
 {
-       DBG("interface %s", ifdata->ifname);
+       DBG("index %d", ifdata->index);
 
        if (ifdata->udp_listener_watch > 0)
                g_source_remove(ifdata->udp_listener_watch);
@@ -2649,7 +2852,7 @@ static void destroy_udp_listener(struct listener_data *ifdata)
 
 static void destroy_tcp_listener(struct listener_data *ifdata)
 {
-       DBG("interface %s", ifdata->ifname);
+       DBG("index %d", ifdata->index);
 
        if (ifdata->tcp_listener_watch > 0)
                g_source_remove(ifdata->tcp_listener_watch);
@@ -2659,7 +2862,7 @@ static void destroy_tcp_listener(struct listener_data *ifdata)
 
 static int create_listener(struct listener_data *ifdata)
 {
-       int err;
+       int err, index;
 
        err = create_dns_listener(IPPROTO_UDP, ifdata);
        if (err < 0)
@@ -2671,41 +2874,21 @@ static int create_listener(struct listener_data *ifdata)
                return err;
        }
 
-       if (g_strcmp0(ifdata->ifname, "lo") == 0)
-               __connman_resolvfile_append("lo", NULL, "127.0.0.1");
+       index = connman_inet_ifindex("lo");
+       if (ifdata->index == index)
+               __connman_resolvfile_append(index, NULL, "127.0.0.1");
 
        return 0;
 }
 
-static void destroy_request_data(struct request_data *req)
-{
-       if (req->timeout > 0)
-               g_source_remove(req->timeout);
-
-       g_free(req->resp);
-       g_free(req->request);
-       g_free(req->name);
-       g_free(req);
-}
-
 static void destroy_listener(struct listener_data *ifdata)
 {
+       int index;
        GSList *list;
 
-       if (g_strcmp0(ifdata->ifname, "lo") == 0)
-               __connman_resolvfile_remove("lo", NULL, "127.0.0.1");
-
-       for (list = request_pending_list; list; list = list->next) {
-               struct request_data *req = list->data;
-
-               DBG("Dropping pending request (id 0x%04x -> 0x%04x)",
-                                               req->srcid, req->dstid);
-               destroy_request_data(req);
-               list->data = NULL;
-       }
-
-       g_slist_free(request_pending_list);
-       request_pending_list = NULL;
+       index = connman_inet_ifindex("lo");
+       if (ifdata->index == index)
+               __connman_resolvfile_remove(index, NULL, "127.0.0.1");
 
        for (list = request_list; list; list = list->next) {
                struct request_data *req = list->data;
@@ -2723,21 +2906,27 @@ static void destroy_listener(struct listener_data *ifdata)
        destroy_udp_listener(ifdata);
 }
 
-int __connman_dnsproxy_add_listener(const char *interface)
+int __connman_dnsproxy_add_listener(int index)
 {
        struct listener_data *ifdata;
        int err;
 
-       DBG("interface %s", interface);
+       DBG("index %d", index);
 
-       if (g_hash_table_lookup(listener_table, interface) != NULL)
+       if (index < 0)
+               return -EINVAL;
+
+       if (listener_table == NULL)
+               return 0;
+
+       if (g_hash_table_lookup(listener_table, GINT_TO_POINTER(index)) != NULL)
                return 0;
 
        ifdata = g_try_new0(struct listener_data, 1);
        if (ifdata == NULL)
                return -ENOMEM;
 
-       ifdata->ifname = g_strdup(interface);
+       ifdata->index = index;
        ifdata->udp_listener_channel = NULL;
        ifdata->udp_listener_watch = 0;
        ifdata->tcp_listener_channel = NULL;
@@ -2745,50 +2934,57 @@ int __connman_dnsproxy_add_listener(const char *interface)
 
        err = create_listener(ifdata);
        if (err < 0) {
-               connman_error("Couldn't create listener for %s err %d",
-                               interface, err);
-               g_free(ifdata->ifname);
+               connman_error("Couldn't create listener for index %d err %d",
+                               index, err);
                g_free(ifdata);
                return err;
        }
-       g_hash_table_insert(listener_table, ifdata->ifname, ifdata);
+       g_hash_table_insert(listener_table, GINT_TO_POINTER(ifdata->index),
+                       ifdata);
        return 0;
 }
 
-void __connman_dnsproxy_remove_listener(const char *interface)
+void __connman_dnsproxy_remove_listener(int index)
 {
        struct listener_data *ifdata;
 
-       DBG("interface %s", interface);
+       DBG("index %d", index);
 
-       ifdata = g_hash_table_lookup(listener_table, interface);
+       if (listener_table == NULL)
+               return;
+
+       ifdata = g_hash_table_lookup(listener_table, GINT_TO_POINTER(index));
        if (ifdata == NULL)
                return;
 
        destroy_listener(ifdata);
 
-       g_hash_table_remove(listener_table, interface);
+       g_hash_table_remove(listener_table, GINT_TO_POINTER(index));
 }
 
 static void remove_listener(gpointer key, gpointer value, gpointer user_data)
 {
-       const char *interface = key;
+       int index = GPOINTER_TO_INT(key);
        struct listener_data *ifdata = value;
 
-       DBG("interface %s", interface);
+       DBG("index %d", index);
 
        destroy_listener(ifdata);
 }
 
 int __connman_dnsproxy_init(void)
 {
-       int err;
+       int err, index;
 
        DBG("");
 
-       listener_table = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                                       g_free, g_free);
-       err = __connman_dnsproxy_add_listener("lo");
+       srandom(time(NULL));
+
+       listener_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                                                       NULL, g_free);
+
+       index = connman_inet_ifindex("lo");
+       err = __connman_dnsproxy_add_listener(index);
        if (err < 0)
                return err;
 
@@ -2799,7 +2995,7 @@ int __connman_dnsproxy_init(void)
        return 0;
 
 destroy:
-       __connman_dnsproxy_remove_listener("lo");
+       __connman_dnsproxy_remove_listener(index);
        g_hash_table_destroy(listener_table);
 
        return err;