*
* 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
#endif
#include <errno.h>
+#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <sys/socket.h>
#include <netdb.h>
#include <resolv.h>
+#include <gweb/gresolv.h>
#include <glib.h>
struct cache_entry {
char *key;
+ int want_refresh;
int hits;
struct cache_data *ipv4;
struct cache_data *ipv6;
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)
{
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 */
}
}
-
-
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)
{
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;
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,
struct domain_hdr *hdr;
int err, offset = protocol_offset(protocol);
- DBG("");
+ DBG("sk %d", sk);
if (offset < 0)
return;
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;
}
}
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;
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;
*/
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) {
}
}
-
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);
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;
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;
}
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);
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;
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;
/* 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;
}
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;
return 0;
}
- /* Continue only if response code is 0 (=ok) */
- if (msg[3] & 0x0f)
- 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;
+ }
if (offset < 0)
return 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;
question[sizeof(question) - 1] = '\0';
* 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 = 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
return 0;
qlen = strlen(question);
- current_time = time(0);
/*
* If the cache contains already data, check if the
entry->key = g_strdup(question);
entry->ipv4 = entry->ipv6 = NULL;
+ entry->want_refresh = 0;
entry->hits = 0;
if (type == 1)
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;
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) {
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;
}
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;
data = entry->ipv6;
if (data) {
- ttl_left = data->valid_until - time(0);
+ ttl_left = data->valid_until - time(NULL);
entry->hits++;
}
sk = g_io_channel_unix_get_fd(server->channel);
- err = send(sk, request, req->request_len, 0);
+ err = send(sk, request, req->request_len, MSG_NOSIGNAL);
+ if (err < 0)
+ return -EIO;
req->numserv++;
alt[1] = req_len & 0xff;
}
- err = send(sk, alt, req->request_len + domlen, 0);
+ err = send(sk, alt, req->request_len + domlen, MSG_NOSIGNAL);
if (err < 0)
return -EIO;
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)
{
*/
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);
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) {
&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;
}
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;
- DBG("interface %s server %s", server->interface, server->server);
+ DBG("interface %s server %s sock %d", server->interface, server->server,
+ g_io_channel_unix_get_fd(server->channel));
server_list = g_slist_remove(server_list, 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);
}
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);
}
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
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,
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) {
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)
return NULL;
}
+ DBG("sk %d", sk);
+
if (interface != NULL) {
if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE,
interface, strlen(interface) + 1) < 0) {
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;
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)
{
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);
}
}
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();
}
}
}
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);
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 = {
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);
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);
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)) {
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;
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->interface, 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;
}
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;
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)
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);
-
- g_free(req->resp);
- g_free(req->request);
- g_free(req->name);
- g_free(req);
- list->data = NULL;
- }
-
- g_slist_free(request_pending_list);
- request_pending_list = NULL;
-
for (list = request_list; list; list = list->next) {
struct request_data *req = list->data;
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;
}
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)
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");