X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fdnsproxy.c;h=58d1bfe2c3741f94a8f2c28accededb2a88d8e06;hb=335e7562aafe49d4f91184ebe4d84d1aa99b9ca7;hp=adf7535c5f95bd992139e3e4ccc5cf3c636aaf21;hpb=1ffb83fdc10568aa288e1288811b233c889cce86;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/dnsproxy.c b/src/dnsproxy.c index adf7535..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 @@ -183,10 +184,14 @@ 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) { @@ -494,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; @@ -565,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) { @@ -588,7 +590,7 @@ 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; /* @@ -1053,7 +1055,7 @@ static void cache_cleanup(void) struct cache_timeout data; int count = 0; - data.current_time = time(0); + data.current_time = time(NULL); data.max_timeout = 0; data.try_harder = 0; @@ -1130,9 +1132,12 @@ static gboolean cache_invalidate_entry(gpointer key, gpointer value, */ static void cache_invalidate(void) { - DBG("Invalidating the DNS cache"); - g_hash_table_foreach_remove(cache, cache_invalidate_entry, - NULL); + 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) @@ -1175,6 +1180,9 @@ static void cache_refresh_iterator(gpointer key, gpointer value, static void cache_refresh(void) { + if (cache == NULL) + return; + g_hash_table_foreach(cache, cache_refresh_iterator, NULL); } @@ -1195,7 +1203,6 @@ 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); @@ -1224,7 +1231,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, return 0; } - current_time = time(0); + current_time = time(NULL); /* don't do a cache refresh more than twice a minute */ if (next_refresh < current_time) { @@ -1410,7 +1417,7 @@ static int ns_resolv(struct server_data *server, struct request_data *req, data = entry->ipv6; if (data) { - ttl_left = data->valid_until - time(0); + ttl_left = data->valid_until - time(NULL); entry->hits++; } @@ -1437,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++; @@ -1501,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) { @@ -1547,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); @@ -1621,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; @@ -1638,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); @@ -1650,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); } @@ -1738,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, @@ -1763,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) { @@ -2001,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; @@ -2030,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) @@ -2172,12 +2234,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(); } @@ -2208,10 +2270,10 @@ 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; } } @@ -2248,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); @@ -2312,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); @@ -2355,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; @@ -2370,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; @@ -2381,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) { @@ -2419,15 +2452,43 @@ 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; } @@ -2474,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; @@ -2488,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) @@ -2654,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; } @@ -2670,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; } @@ -2734,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) @@ -2743,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");