X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fdnsproxy.c;h=58d1bfe2c3741f94a8f2c28accededb2a88d8e06;hb=335e7562aafe49d4f91184ebe4d84d1aa99b9ca7;hp=a30f4c46998d43361af17d5430e353fefe79c164;hpb=441e41382b97c7ff8802a2ed78f4ca6a0c672489;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/dnsproxy.c b/src/dnsproxy.c index a30f4c4..58d1bfe 100644 --- a/src/dnsproxy.c +++ b/src/dnsproxy.c @@ -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 @@ -24,6 +24,7 @@ #endif #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include @@ -138,6 +140,8 @@ struct cache_data { struct cache_entry { char *key; + int want_refresh; + int hits; struct cache_data *ipv4; struct cache_data *ipv6; }; @@ -180,8 +184,13 @@ 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) { @@ -262,6 +271,52 @@ 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 */ @@ -317,8 +372,6 @@ static void update_cached_ttl(unsigned char *buf, int len, int new_ttl) } } - - 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 ttl) @@ -446,53 +499,50 @@ static int append_query(unsigned char *buf, unsigned int size, const char *query, const char *domain) { unsigned char *ptr = buf; - char *offset; int len; DBG("query %s domain %s", query, domain); - offset = (char *) query; - while (offset != NULL) { - char *tmp; + while (query != NULL) { + const char *tmp; - tmp = strchr(offset, '.'); + tmp = strchr(query, '.'); if (tmp == NULL) { - len = strlen(offset); + len = strlen(query); if (len == 0) break; *ptr = len; - memcpy(ptr + 1, offset, len); + memcpy(ptr + 1, query, len); ptr += len + 1; break; } - *ptr = tmp - offset; - memcpy(ptr + 1, offset, tmp - offset); - ptr += tmp - offset + 1; + *ptr = tmp - query; + memcpy(ptr + 1, query, tmp - query); + ptr += tmp - query + 1; - offset = tmp + 1; + query = tmp + 1; } - offset = (char *) domain; - while (offset != NULL) { - char *tmp; + while (domain != NULL) { + const char *tmp; - tmp = strchr(offset, '.'); + tmp = strchr(domain, '.'); if (tmp == NULL) { - len = strlen(offset); + len = strlen(domain); if (len == 0) break; *ptr = len; - memcpy(ptr + 1, offset, len); + memcpy(ptr + 1, domain, len); ptr += len + 1; break; } - *ptr = tmp - offset; - memcpy(ptr + 1, offset, tmp - offset); - ptr += tmp - offset + 1; + *ptr = tmp - domain; + memcpy(ptr + 1, domain, tmp - domain); + ptr += tmp - domain + 1; - offset = tmp + 1; + domain = tmp + 1; } *ptr++ = 0x00; @@ -517,7 +567,7 @@ static gboolean cache_check_is_valid(struct cache_data *data, */ static void cache_enforce_validity(struct cache_entry *entry) { - time_t current_time = time(0); + time_t current_time = time(NULL); if (cache_check_is_valid(entry->ipv4, current_time) == FALSE && entry->ipv4) { @@ -537,11 +587,18 @@ static void cache_enforce_validity(struct cache_entry *entry) } } - static uint16_t cache_check_validity(char *question, uint16_t type, 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); @@ -551,14 +608,18 @@ static uint16_t cache_check_validity(char *question, uint16_t type, 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(entry->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; @@ -567,10 +628,14 @@ static uint16_t cache_check_validity(char *question, uint16_t type, DBG("cache %s \"%s\" type AAAA", entry->ipv6 ? "timeout" : "entry missing", question); - if (cache_check_is_valid(entry->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; } @@ -937,6 +1002,7 @@ out: struct cache_timeout { time_t current_time; int max_timeout; + int try_harder; }; static gboolean cache_check_entry(gpointer key, gpointer value, @@ -946,6 +1012,10 @@ 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. @@ -969,6 +1039,13 @@ static gboolean cache_check_entry(gpointer key, gpointer value, 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; } @@ -976,20 +1053,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) /* @@ -1003,6 +1092,100 @@ 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; @@ -1020,9 +1203,9 @@ static int reply_query_type(unsigned char *msg, int len) /* 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; } @@ -1048,6 +1231,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; @@ -1085,6 +1277,13 @@ static int cache_update(struct server_data *srv, unsigned char *msg, 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; } } @@ -1093,7 +1292,6 @@ static int cache_update(struct server_data *srv, unsigned char *msg, return 0; qlen = strlen(question); - current_time = time(0); /* * If the cache contains already data, check if the @@ -1116,6 +1314,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; @@ -1137,6 +1337,14 @@ 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; } @@ -1208,8 +1416,10 @@ static int ns_resolv(struct server_data *server, struct request_data *req, else data = entry->ipv6; - if (data) - ttl_left = data->valid_until - time(0); + 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, @@ -1234,6 +1444,8 @@ 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) + return -EIO; req->numserv++; @@ -1298,6 +1510,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) { @@ -1344,17 +1567,30 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, */ ptr = reply + offset + sizeof(struct domain_hdr); host_len = *ptr; - domain_len = strlen((const char *)ptr) - host_len - 1; + domain_len = strlen((const char *)ptr + host_len + 1); /* - * remove the domain name and replaced it by the end - * of reply. + * Remove the domain name and replace it by the end + * of reply. Check if the domain is really there + * before trying to copy the data. The domain_len can + * be 0 because if the original query did not contain + * a domain name, then we are sending two packets, + * first without the domain name and the second packet + * with domain name. The append_domain is set to true + * even if we sent the first packet without domain + * name. In this case we end up in this branch. */ - memmove(ptr + host_len + 1, - ptr + host_len + domain_len + 1, - reply_len - (ptr - reply + domain_len)); + if (domain_len > 0) { + /* + * Note that we must use memmove() here, + * because the memory areas can overlap. + */ + memmove(ptr + host_len + 1, + ptr + host_len + domain_len + 1, + reply_len - (ptr - reply + domain_len)); - reply_len = reply_len - domain_len; + reply_len = reply_len - domain_len; + } } g_free(req->resp); @@ -1418,6 +1654,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; @@ -1435,7 +1683,7 @@ static void destroy_server(struct server_data *server) g_io_channel_unref(server->channel); if (server->protocol == IPPROTO_UDP) - connman_info("Removing DNS server %s", server->server); + DBG("Removing DNS server %s", server->server); g_free(server->incoming_reply); g_free(server->server); @@ -1447,8 +1695,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); } @@ -1535,6 +1791,7 @@ 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, @@ -1560,29 +1817,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) { @@ -1798,21 +2074,18 @@ static struct server_data *create_server(const char *interface, 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, gpointer request, gpointer name) { GSList *list; - int status; for (list = server_list; list; list = list->next) { struct server_data *data = list->data; @@ -1827,19 +2100,11 @@ static gboolean resolv(struct request_data *req, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, udp_server_event, data); - status = ns_resolv(data, req, request, name); - if (status < 0) - continue; - - if (status > 0) { - if (req->timeout > 0) { - g_source_remove(req->timeout); - req->timeout = 0; - } - } + if (ns_resolv(data, req, request, name) > 0) + return TRUE; } - return TRUE; + return FALSE; } static void append_domain(const char *interface, const char *domain) @@ -1969,11 +2234,14 @@ 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(); } } } @@ -1985,6 +2253,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); @@ -1999,15 +2270,16 @@ static void dnsproxy_default_changed(struct connman_service *service) struct server_data *data = list->data; if (g_strcmp0(data->interface, interface) == 0) { - connman_info("Enabling DNS server %s", data->server); + 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(); } static struct connman_notifier dnsproxy_notifier = { @@ -2038,7 +2310,7 @@ static int parse_request(unsigned char *buf, int len, if (hdr->qr != 0 || qdcount != 1) return -EINVAL; - memset(name, 0, size); + name[0] = '\0'; ptr = buf + sizeof(struct domain_hdr); remain = len - sizeof(struct domain_hdr); @@ -2102,6 +2374,7 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition, socklen_t client_addr_len = sizeof(client_addr); GSList *list; struct listener_data *ifdata = user_data; + int waiting_for_connect = FALSE; DBG("condition 0x%x", condition); @@ -2145,13 +2418,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; @@ -2160,7 +2429,6 @@ 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; @@ -2171,33 +2439,8 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition, server = create_server(data->interface, NULL, data->server, IPPROTO_TCP); - - /* - * 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; - - memcpy(req->request, buf, req->request_len); - - req->name = g_try_malloc0(sizeof(query)); - if (req->name == NULL) { - g_free(req->request); - return TRUE; - } - memcpy(req->name, query, sizeof(query)); - + if (server == NULL) continue; - } - - if (req->timeout > 0) - g_source_remove(req->timeout); for (domains = data->domains; domains; domains = domains->next) { @@ -2209,14 +2452,42 @@ static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition, g_strdup(dom)); } - 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; - } - } + waiting_for_connect = TRUE; + } + + 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; + } + + /* + * 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->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; } @@ -2264,13 +2535,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; @@ -2278,11 +2545,18 @@ static gboolean udp_listener_event(GIOChannel *channel, GIOCondition condition, req->numserv = 0; req->ifdata = (struct listener_data *) ifdata; - req->timeout = g_timeout_add_seconds(5, request_timeout, req); req->append_domain = FALSE; + + if (resolv(req, buf, query) == TRUE) { + /* a cached result was sent, so the request can be released */ + g_free(req); + return TRUE; + } + + req->timeout = g_timeout_add_seconds(5, request_timeout, req); request_list = g_slist_append(request_list, req); - return resolv(req, buf, query); + return TRUE; } static int create_dns_listener(int protocol, struct listener_data *ifdata) @@ -2444,11 +2718,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; } @@ -2460,11 +2730,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; } @@ -2524,7 +2790,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) @@ -2533,6 +2804,8 @@ int __connman_dnsproxy_init(void) DBG(""); + srandom(time(NULL)); + listener_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); err = __connman_dnsproxy_add_listener("lo");