service: Save/load service hidden status
[platform/upstream/connman.git] / src / dnsproxy.c
index cf6f30e..5404348 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  Connection Manager
  *
- *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
@@ -33,6 +33,7 @@
 #include <sys/socket.h>
 #include <netdb.h>
 #include <resolv.h>
+#include <gweb/gresolv.h>
 
 #include <glib.h>
 
@@ -127,6 +128,8 @@ struct listener_data {
 
 struct cache_data {
        time_t inserted;
+       time_t valid_until;
+       time_t cache_until;
        int timeout;
        uint16_t type;
        uint16_t answers;
@@ -136,6 +139,8 @@ struct cache_data {
 
 struct cache_entry {
        char *key;
+       int want_refresh;
+       int hits;
        struct cache_data *ipv4;
        struct cache_data *ipv6;
 };
@@ -158,6 +163,10 @@ struct domain_rr {
  * when setting the cache entry life time. The value is in seconds.
  */
 #define MAX_CACHE_TTL (60 * 30)
+/*
+ * Also limit the other end, cache at least for 30 seconds.
+ */
+#define MIN_CACHE_TTL (30)
 
 /*
  * We limit the cache size to some sane value so that cached data does
@@ -176,6 +185,7 @@ 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 int protocol_offset(int protocol)
 {
@@ -192,6 +202,27 @@ static int protocol_offset(int protocol)
 
 }
 
+/*
+ * There is a power and efficiency benefit to have entries
+ * in our cache expire at the same time. To this extend,
+ * we round down the cache valid time to common boundaries.
+ */
+static time_t round_down_ttl(time_t end_time, int ttl)
+{
+       if (ttl < 15)
+               return end_time;
+
+       /* Less than 5 minutes, round to 10 second boundary */
+       if (ttl < 300) {
+               end_time = end_time / 10;
+               end_time = end_time * 10;
+       } else { /* 5 or more minutes, round to 30 seconds */
+               end_time = end_time / 30;
+               end_time = end_time * 30;
+       }
+       return end_time;
+}
+
 static struct request_data *find_request(guint16 id)
 {
        GSList *list;
@@ -235,9 +266,110 @@ static struct server_data *find_server(const char *interface,
        return NULL;
 }
 
+/* we can keep using the same resolve's */
+static GResolv *ipv4_resolve;
+static GResolv *ipv6_resolve;
+
+static void dummy_resolve_func(GResolvResultStatus status,
+                                       char **results, gpointer user_data)
+{
+}
+
+/*
+ * Refresh a DNS entry, but also age the hit count a bit */
+static void refresh_dns_entry(struct cache_entry *entry, char *name)
+{
+       int age = 1;
+
+       if (ipv4_resolve == NULL) {
+               ipv4_resolve = g_resolv_new(0);
+               g_resolv_set_address_family(ipv4_resolve, AF_INET);
+               g_resolv_add_nameserver(ipv4_resolve, "127.0.0.1", 53, 0);
+       }
+
+       if (ipv6_resolve == NULL) {
+               ipv6_resolve = g_resolv_new(0);
+               g_resolv_set_address_family(ipv6_resolve, AF_INET6);
+               g_resolv_add_nameserver(ipv6_resolve, "127.0.0.1", 53, 0);
+       }
+
+       if (entry->ipv4 == NULL) {
+               DBG("Refresing A record for %s", name);
+               g_resolv_lookup_hostname(ipv4_resolve, name,
+                                       dummy_resolve_func, NULL);
+               age = 4;
+       }
+
+       if (entry->ipv6 == NULL) {
+               DBG("Refresing AAAA record for %s", name);
+               g_resolv_lookup_hostname(ipv6_resolve, name,
+                                       dummy_resolve_func, NULL);
+               age = 4;
+       }
+
+       entry->hits -= age;
+       if (entry->hits < 0)
+               entry->hits = 0;
+}
+
+static int dns_name_length(unsigned char *buf)
+{
+       if ((buf[0] & NS_CMPRSFLGS) == NS_CMPRSFLGS) /* compressed name */
+               return 2;
+       return strlen((char *)buf);
+}
+
+static void update_cached_ttl(unsigned char *buf, int len, int new_ttl)
+{
+       unsigned char *c;
+       uint32_t *i;
+       uint16_t *w;
+       int l;
+
+       /* skip the header */
+       c = buf + 12;
+       len -= 12;
+
+       /* skip the query, which is a name and 2 16 bit words */
+       l = dns_name_length(c);
+       c += l;
+       len -= l;
+       c += 4;
+       len -= 4;
+
+       /* now we get the answer records */
+
+       while (len > 0) {
+               /* first a name */
+               l = dns_name_length(c);
+               c += l;
+               len -= l;
+               if (len < 0)
+                       break;
+               /* then type + class, 2 bytes each */
+               c += 4;
+               len -= 4;
+               if (len < 0)
+                       break;
+
+               /* now the 4 byte TTL field */
+               i = (uint32_t *)c;
+               *i = htonl(new_ttl);
+               c += 4;
+               len -= 4;
+               if (len < 0)
+                       break;
+
+               /* now the 2 byte rdlen field */
+               w = (uint16_t *)c;
+               c += ntohs(*w) + 2;
+               len -= ntohs(*w) + 2;
+       }
+}
+
 static void send_cached_response(int sk, unsigned char *buf, int len,
                                const struct sockaddr *to, socklen_t tolen,
-                               int protocol, int id, uint16_t answers)
+                               int protocol, int id, uint16_t answers, int ttl)
 {
        struct domain_hdr *hdr;
        int err, offset = protocol_offset(protocol);
@@ -257,6 +389,12 @@ static void send_cached_response(int sk, unsigned char *buf, int len,
        hdr->nscount = 0;
        hdr->arcount = 0;
 
+       /* if this is a negative reply, we are authorative */
+       if (answers == 0)
+               hdr->aa = 1;
+       else
+               update_cached_ttl(buf, len, ttl);
+
        DBG("id 0x%04x answers %d", hdr->id, answers);
 
        err = sendto(sk, buf, len, 0, to, tolen);
@@ -416,44 +554,86 @@ static gboolean cache_check_is_valid(struct cache_data *data,
        if (data == NULL)
                return FALSE;
 
-       if (data->inserted + data->timeout < current_time)
+       if (data->cache_until < current_time)
                return FALSE;
 
        return TRUE;
 }
 
+/*
+ * remove stale cached entries so that they can be refreshed
+ */
+static void cache_enforce_validity(struct cache_entry *entry)
+{
+       time_t current_time = time(NULL);
+
+       if (cache_check_is_valid(entry->ipv4, current_time) == FALSE
+                                                       && entry->ipv4) {
+               DBG("cache timeout \"%s\" type A", entry->key);
+               g_free(entry->ipv4->data);
+               g_free(entry->ipv4);
+               entry->ipv4 = NULL;
+
+       }
+
+       if (cache_check_is_valid(entry->ipv6, current_time) == FALSE
+                                                       && entry->ipv6) {
+               DBG("cache timeout \"%s\" type AAAA", entry->key);
+               g_free(entry->ipv6->data);
+               g_free(entry->ipv6);
+               entry->ipv6 = NULL;
+       }
+}
+
 static uint16_t cache_check_validity(char *question, uint16_t type,
-                               struct cache_data *ipv4,
-                               struct cache_data *ipv6)
+                               struct cache_entry *entry)
 {
-       time_t current_time = time(0);
+       time_t current_time = time(NULL);
+       int want_refresh = 0;
+
+       /*
+        * if we have a popular entry, we want a refresh instead of
+        * total destruction of the entry.
+        */
+       if (entry->hits > 2)
+               want_refresh = 1;
+
+       cache_enforce_validity(entry);
 
        switch (type) {
        case 1:         /* IPv4 */
-               if (cache_check_is_valid(ipv4, current_time) == FALSE) {
-                       DBG("cache %s \"%s\" type A",
-                               ipv4 ? "timeout" : "entry missing", question);
+               if (cache_check_is_valid(entry->ipv4, current_time) == FALSE) {
+                       DBG("cache %s \"%s\" type A", entry->ipv4 ?
+                                       "timeout" : "entry missing", question);
+
+                       if (want_refresh)
+                               entry->want_refresh = 1;
 
                        /*
                         * We do not remove cache entry if there is still
                         * valid IPv6 entry found in the cache.
                         */
-                       if (cache_check_is_valid(ipv6, current_time) == FALSE)
+                       if (cache_check_is_valid(entry->ipv6, current_time)
+                                       == FALSE && want_refresh == FALSE) {
                                g_hash_table_remove(cache, question);
-
-                       type = 0;
+                               type = 0;
+                       }
                }
                break;
 
        case 28:        /* IPv6 */
-               if (cache_check_is_valid(ipv6, current_time) == FALSE) {
-                       DBG("cache %s \"%s\" type AAAA",
-                               ipv6 ? "timeout" : "entry missing", question);
+               if (cache_check_is_valid(entry->ipv6, current_time) == FALSE) {
+                       DBG("cache %s \"%s\" type AAAA", entry->ipv6 ?
+                                       "timeout" : "entry missing", question);
 
-                       if (cache_check_is_valid(ipv4, current_time) == FALSE)
-                               g_hash_table_remove(cache, question);
+                       if (want_refresh)
+                               entry->want_refresh = 1;
 
-                       type = 0;
+                       if (cache_check_is_valid(entry->ipv4, current_time)
+                                       == FALSE && want_refresh == FALSE) {
+                               g_hash_table_remove(cache, question);
+                               type = 0;
+                       }
                }
                break;
        }
@@ -481,7 +661,7 @@ static struct cache_entry *cache_check(gpointer request, int *qtype)
        if (entry == NULL)
                return NULL;
 
-       type = cache_check_validity(question, type, entry->ipv4, entry->ipv6);
+       type = cache_check_validity(question, type, entry);
        if (type == 0)
                return NULL;
 
@@ -509,7 +689,7 @@ static int get_name(int counter,
 
        p = start;
        while (*p) {
-               if (*p & NS_CMPRSFLGS) {
+               if ((*p & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
                        uint16_t offset = (*p & 0x3F) * 256 + *(p + 1);
 
                        if (offset >= max - pkt)
@@ -579,6 +759,9 @@ static int parse_rr(unsigned char *buf, unsigned char *start,
 
        rr = (void *) (*end);
 
+       if (rr == NULL)
+               return -EINVAL;
+
        *type = ntohs(rr->type);
        *class = ntohs(rr->class);
        *ttl = ntohl(rr->ttl);
@@ -817,6 +1000,7 @@ out:
 struct cache_timeout {
        time_t current_time;
        int max_timeout;
+       int try_harder;
 };
 
 static gboolean cache_check_entry(gpointer key, gpointer value,
@@ -826,31 +1010,40 @@ static gboolean cache_check_entry(gpointer key, gpointer value,
        struct cache_entry *entry = value;
        int max_timeout;
 
+       /* Scale the number of hits by half as part of cache aging */
+
+       entry->hits /= 2;
+
        /*
         * If either IPv4 or IPv6 cached entry has expired, we
         * remove both from the cache.
         */
 
        if (entry->ipv4 != NULL && entry->ipv4->timeout > 0) {
-               max_timeout = entry->ipv4->inserted + entry->ipv4->timeout;
+               max_timeout = entry->ipv4->cache_until;
                if (max_timeout > data->max_timeout)
                        data->max_timeout = max_timeout;
 
-               if (entry->ipv4->inserted + entry->ipv4->timeout
-                                                       < data->current_time)
+               if (entry->ipv4->cache_until < data->current_time)
                        return TRUE;
        }
 
        if (entry->ipv6 != NULL && entry->ipv6->timeout > 0) {
-               max_timeout = entry->ipv6->inserted + entry->ipv6->timeout;
+               max_timeout = entry->ipv6->cache_until;
                if (max_timeout > data->max_timeout)
                        data->max_timeout = max_timeout;
 
-               if (entry->ipv6->inserted + entry->ipv6->timeout
-                                                       < data->current_time)
+               if (entry->ipv6->cache_until < data->current_time)
                        return TRUE;
        }
 
+       /*
+        * if we're asked to try harder, also remove entries that have
+        * few hits
+        */
+       if (data->try_harder && entry->hits < 4)
+               return TRUE;
+
        return FALSE;
 }
 
@@ -858,20 +1051,32 @@ static void cache_cleanup(void)
 {
        static int max_timeout;
        struct cache_timeout data;
-       int count;
+       int count = 0;
 
-       data.current_time = time(0);
+       data.current_time = time(NULL);
        data.max_timeout = 0;
+       data.try_harder = 0;
 
-       if (max_timeout > data.current_time) {
-               DBG("waiting %ld secs before cleaning cache",
-                       max_timeout - data.current_time);
-               return;
+       /*
+        * In the first pass, we only remove entries that have timed out.
+        * We use a cache of the first time to expire to do this only
+        * when it makes sense.
+        */
+       if (max_timeout <= data.current_time) {
+               count = g_hash_table_foreach_remove(cache, cache_check_entry,
+                                               &data);
        }
+       DBG("removed %d in the first pass", count);
 
-       count = g_hash_table_foreach_remove(cache, cache_check_entry,
+       /*
+        * In the second pass, if the first pass turned up blank,
+        * we also expire entries with a low hit count,
+        * while aging the hit count at the same time.
+        */
+       data.try_harder = 1;
+       if (count == 0)
+               count = g_hash_table_foreach_remove(cache, cache_check_entry,
                                                &data);
-       DBG("removed %d", count);
 
        if (count == 0)
                /*
@@ -885,12 +1090,130 @@ static void cache_cleanup(void)
                max_timeout = 0;
 }
 
+static gboolean cache_invalidate_entry(gpointer key, gpointer value,
+                                       gpointer user_data)
+{
+       struct cache_entry *entry = value;
+
+       /* first, delete any expired elements */
+       cache_enforce_validity(entry);
+
+       /* if anything is not expired, mark the entry for refresh */
+       if (entry->hits > 0 && (entry->ipv4 || entry->ipv6))
+               entry->want_refresh = 1;
+
+       /* delete the cached data */
+       if (entry->ipv4) {
+               g_free(entry->ipv4->data);
+               g_free(entry->ipv4);
+               entry->ipv4 = NULL;
+       }
+
+       if (entry->ipv6) {
+               g_free(entry->ipv6->data);
+               g_free(entry->ipv6);
+               entry->ipv6 = NULL;
+       }
+
+       /* keep the entry if we want it refreshed, delete it otherwise */
+       if (entry->want_refresh)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+/*
+ * cache_invalidate is called from places where the DNS landscape
+ * has changed, say because connections are added or we entered a VPN.
+ * The logic is to wipe all cache data, but mark all non-expired
+ * parts of the cache for refresh rather than deleting the whole cache.
+ */
+static void cache_invalidate(void)
+{
+       DBG("Invalidating the DNS cache %p", cache);
+
+       if (cache == NULL)
+               return;
+
+       g_hash_table_foreach_remove(cache, cache_invalidate_entry, NULL);
+}
+
+static void cache_refresh_entry(struct cache_entry *entry)
+{
+
+       cache_enforce_validity(entry);
+
+       if (entry->hits > 2 && entry->ipv4 == NULL)
+               entry->want_refresh = 1;
+       if (entry->hits > 2 && entry->ipv6 == NULL)
+               entry->want_refresh = 1;
+
+       if (entry->want_refresh) {
+               char *c;
+               char dns_name[NS_MAXDNAME + 1];
+               entry->want_refresh = 0;
+
+               /* turn a DNS name into a hostname with dots */
+               strncpy(dns_name, entry->key, NS_MAXDNAME);
+               c = dns_name;
+               while (c && *c) {
+                       int jump;
+                       jump = *c;
+                       *c = '.';
+                       c += jump + 1;
+               }
+               DBG("Refreshing %s\n", dns_name);
+               /* then refresh the hostname */
+               refresh_dns_entry(entry, &dns_name[1]);
+       }
+}
+
+static void cache_refresh_iterator(gpointer key, gpointer value,
+                                       gpointer user_data)
+{
+       struct cache_entry *entry = value;
+
+       cache_refresh_entry(entry);
+}
+
+static void cache_refresh(void)
+{
+       if (cache == NULL)
+               return;
+
+       g_hash_table_foreach(cache, cache_refresh_iterator, NULL);
+}
+
+static int reply_query_type(unsigned char *msg, int len)
+{
+       unsigned char *c;
+       uint16_t *w;
+       int l;
+       int type;
+
+       /* skip the header */
+       c = msg + sizeof(struct domain_hdr);
+       len -= sizeof(struct domain_hdr);
+
+       if (len < 0)
+               return 0;
+
+       /* now the query, which is a name and 2 16 bit words */
+       l = dns_name_length(c) + 1;
+       c += l;
+       len -= l;
+       w = (uint16_t *) c;
+       type = ntohs(*w);
+
+       return type;
+}
+
 static int cache_update(struct server_data *srv, unsigned char *msg,
                        unsigned int msg_len)
 {
        int offset = protocol_offset(srv->protocol);
        int err, qlen, ttl = 0;
-       uint16_t answers, type = 0, class = 0;
+       uint16_t answers = 0, type = 0, class = 0;
        struct domain_question *q;
        struct cache_entry *entry;
        struct cache_data *data;
@@ -907,6 +1230,15 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                        return 0;
        }
 
+       current_time = time(NULL);
+
+       /* don't do a cache refresh more than twice a minute */
+       if (next_refresh < current_time) {
+               cache_refresh();
+               next_refresh = current_time + 30;
+       }
+
+
        /* Continue only if response code is 0 (=ok) */
        if (msg[3] & 0x0f)
                return 0;
@@ -921,11 +1253,44 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                                question, sizeof(question) - 1,
                                &type, &class, &ttl,
                                response, &rsplen, &answers);
+
+       /*
+        * special case: if we do a ipv6 lookup and get no result
+        * for a record that's already in our ipv4 cache.. we want
+        * to cache the negative response.
+        */
+       if ((err == -ENOMSG || err == -ENOBUFS) &&
+                       reply_query_type(msg, msg_len) == 28) {
+               entry = g_hash_table_lookup(cache, question);
+               if (entry && entry->ipv4 && entry->ipv6 == NULL) {
+                       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->timeout = entry->ipv4->timeout;
+                       data->data_len = msg_len;
+                       data->data = ptr = g_malloc(msg_len);
+                       data->valid_until = entry->ipv4->valid_until;
+                       data->cache_until = entry->ipv4->cache_until;
+                       memcpy(data->data, msg, msg_len);
+                       entry->ipv6 = data;
+                       /*
+                        * we will get a "hit" when we serve the response
+                        * out of the cache
+                        */
+                       entry->hits--;
+                       if (entry->hits < 0)
+                               entry->hits = 0;
+                       return 0;
+               }
+       }
+
        if (err < 0 || ttl == 0)
                return 0;
 
        qlen = strlen(question);
-       current_time = time(0);
 
        /*
         * If the cache contains already data, check if the
@@ -948,6 +1313,8 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
 
                entry->key = g_strdup(question);
                entry->ipv4 = entry->ipv6 = NULL;
+               entry->want_refresh = 0;
+               entry->hits = 0;
 
                if (type == 1)
                        entry->ipv4 = data;
@@ -969,15 +1336,19 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                else
                        entry->ipv6 = data;
 
+               /*
+                * compensate for the hit we'll get for serving
+                * the response out of the cache
+                */
+               entry->hits--;
+               if (entry->hits < 0)
+                       entry->hits = 0;
+
                new_entry = FALSE;
        }
 
-       /*
-        * Restrict the cached DNS record TTL to some sane value
-        * in order to prevent data staying in the cache too long.
-        */
-       if (ttl > MAX_CACHE_TTL)
-               ttl = MAX_CACHE_TTL;
+       if (ttl < MIN_CACHE_TTL)
+               ttl = MIN_CACHE_TTL;
 
        data->inserted = current_time;
        data->type = type;
@@ -985,6 +1356,17 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
        data->timeout = ttl;
        data->data_len = 12 + qlen + 1 + 2 + 2 + rsplen;
        data->data = ptr = g_malloc(data->data_len);
+       data->valid_until = current_time + ttl;
+
+       /*
+        * Restrict the cached DNS record TTL to some sane value
+        * in order to prevent data staying in the cache too long.
+        */
+       if (ttl > MAX_CACHE_TTL)
+               ttl = MAX_CACHE_TTL;
+
+       data->cache_until = round_down_ttl(current_time + ttl, ttl);
+
        if (data->data == NULL) {
                g_free(entry->key);
                g_free(data);
@@ -1006,7 +1388,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg,
                cache_size++;
        }
 
-       DBG("cache %d %squestion \"%s\" type %d ttl %d size %ld",
+       DBG("cache %d %squestion \"%s\" type %d ttl %d size %zd",
                cache_size, new_entry ? "new " : "old ",
                question, type, ttl,
                sizeof(*entry) + sizeof(*data) + data->data_len + qlen);
@@ -1024,6 +1406,7 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
 
        entry = cache_check(request, &type);
        if (entry != NULL) {
+               int ttl_left = 0;
                struct cache_data *data;
 
                DBG("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA");
@@ -1032,10 +1415,15 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
                else
                        data = entry->ipv6;
 
+               if (data) {
+                       ttl_left = data->valid_until - time(NULL);
+                       entry->hits++;
+               }
+
                if (data != NULL && req->protocol == IPPROTO_TCP) {
                        send_cached_response(req->client_sk, data->data,
                                        data->data_len, NULL, 0, IPPROTO_TCP,
-                                       req->srcid, data->answers);
+                                       req->srcid, data->answers, ttl_left);
                        return 1;
                }
 
@@ -1046,7 +1434,8 @@ static int ns_resolv(struct server_data *server, struct request_data *req,
 
                        send_cached_response(sk, data->data,
                                data->data_len, &req->sa, req->sa_len,
-                               IPPROTO_UDP, req->srcid, data->answers);
+                               IPPROTO_UDP, req->srcid, data->answers,
+                               ttl_left);
                        return 1;
                }
        }
@@ -1238,6 +1627,18 @@ static void cache_element_destroy(gpointer value)
                cache_size = 0;
 }
 
+static gboolean try_remove_cache(gpointer user_data)
+{
+       if (__sync_fetch_and_sub(&cache_refcount, 1) == 1) {
+               DBG("No cache users, removing it.");
+
+               g_hash_table_destroy(cache);
+               cache = NULL;
+       }
+
+       return FALSE;
+}
+
 static void destroy_server(struct server_data *server)
 {
        GList *list;
@@ -1267,8 +1668,16 @@ static void destroy_server(struct server_data *server)
        }
        g_free(server->interface);
 
-       if (__sync_fetch_and_sub(&cache_refcount, 1) == 1)
-               g_hash_table_destroy(cache);
+       /*
+        * We do not remove cache right away but delay it few seconds.
+        * The idea is that when IPv6 DNS server is added via RDNSS, it has a
+        * lifetime. When the lifetime expires we decrease the refcount so it
+        * is possible that the cache is then removed. Because a new DNS server
+        * is usually created almost immediately we would then loose the cache
+        * without any good reason. The small delay allows the new RDNSS to
+        * create a new DNS server instance and the refcount does not go to 0.
+        */
+       g_timeout_add_seconds(3, try_remove_cache, NULL);
 
        g_free(server);
 }
@@ -1791,9 +2200,12 @@ static void dnsproxy_offline_mode(connman_bool_t enabled)
                if (enabled == FALSE) {
                        connman_info("Enabling DNS server %s", data->server);
                        data->enabled = TRUE;
+                       cache_invalidate();
+                       cache_refresh();
                } else {
                        connman_info("Disabling DNS server %s", data->server);
                        data->enabled = FALSE;
+                       cache_invalidate();
                }
        }
 }
@@ -1805,6 +2217,9 @@ static void dnsproxy_default_changed(struct connman_service *service)
 
        DBG("service %p", service);
 
+       /* DNS has changed, invalidate the cache */
+       cache_invalidate();
+
        if (service == NULL) {
                /* When no services are active, then disable DNS proxying */
                dnsproxy_offline_mode(TRUE);
@@ -1828,6 +2243,7 @@ static void dnsproxy_default_changed(struct connman_service *service)
        }
 
        g_free(interface);
+       cache_refresh();
 }
 
 static struct connman_notifier dnsproxy_notifier = {
@@ -2252,6 +2668,17 @@ static int create_listener(struct listener_data *ifdata)
        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)
 {
        GSList *list;
@@ -2264,11 +2691,7 @@ static void destroy_listener(struct listener_data *ifdata)
 
                DBG("Dropping pending request (id 0x%04x -> 0x%04x)",
                                                req->srcid, req->dstid);
-
-               g_free(req->resp);
-               g_free(req->request);
-               g_free(req->name);
-               g_free(req);
+               destroy_request_data(req);
                list->data = NULL;
        }
 
@@ -2280,11 +2703,7 @@ static void destroy_listener(struct listener_data *ifdata)
 
                DBG("Dropping request (id 0x%04x -> 0x%04x)",
                                                req->srcid, req->dstid);
-
-               g_free(req->resp);
-               g_free(req->request);
-               g_free(req->name);
-               g_free(req);
+               destroy_request_data(req);
                list->data = NULL;
        }
 
@@ -2344,7 +2763,12 @@ void __connman_dnsproxy_remove_listener(const char *interface)
 
 static void remove_listener(gpointer key, gpointer value, gpointer user_data)
 {
-       __connman_dnsproxy_remove_listener(key);
+       const char *interface = key;
+       struct listener_data *ifdata = value;
+
+       DBG("interface %s", interface);
+
+       destroy_listener(ifdata);
 }
 
 int __connman_dnsproxy_init(void)