X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fdnsproxy.c;h=cb5832514c6eb8352f219a6ef70cf0c45cc88bfe;hb=0b07242ac61ec568ca4e722855c0c51f1f13fe72;hp=24aad9106a2fca0c63c7c1402279ebe38ad6a0b4;hpb=a781b520ba389a0723161bababfbddc0ee2c8e41;p=platform%2Fupstream%2Fconnman.git diff --git a/src/dnsproxy.c b/src/dnsproxy.c old mode 100644 new mode 100755 index 24aad91..cb58325 --- 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-2014 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 @@ -31,12 +32,22 @@ #include #include #include +#include #include +#include +#include #include #include "connman.h" +#if defined TIZEN_EXT +#include +#include +#endif + +#define debug(fmt...) do { } while (0) + #if __BYTE_ORDER == __LITTLE_ENDIAN struct domain_hdr { uint16_t id; @@ -73,6 +84,11 @@ struct domain_hdr { #error "Unknown byte order" #endif +struct qtype_qclass { + uint16_t qtype; + uint16_t qclass; +} __attribute__ ((packed)); + struct partial_reply { uint16_t len; uint16_t received; @@ -80,15 +96,17 @@ struct partial_reply { }; struct server_data { - char *interface; + int index; GList *domains; char *server; + struct sockaddr *server_addr; + socklen_t server_addr_len; int protocol; GIOChannel *channel; guint watch; guint timeout; - gboolean enabled; - gboolean connected; + bool enabled; + bool connected; struct partial_reply *incoming_reply; }; @@ -100,6 +118,7 @@ struct request_data { socklen_t sa_len; int client_sk; int protocol; + int family; guint16 srcid; guint16 dstid; guint16 altid; @@ -113,22 +132,125 @@ struct request_data { gpointer resp; gsize resplen; struct listener_data *ifdata; - gboolean append_domain; + bool append_domain; }; struct listener_data { - char *ifname; - GIOChannel *udp_listener_channel; - guint udp_listener_watch; - GIOChannel *tcp_listener_channel; - guint tcp_listener_watch; + int index; + + GIOChannel *udp4_listener_channel; + GIOChannel *tcp4_listener_channel; + guint udp4_listener_watch; + guint tcp4_listener_watch; + + GIOChannel *udp6_listener_channel; + GIOChannel *tcp6_listener_channel; + guint udp6_listener_watch; + guint tcp6_listener_watch; +}; + +/* + * The TCP client requires some extra handling as we need to + * be prepared to receive also partial DNS requests. + */ +struct tcp_partial_client_data { + int family; + struct listener_data *ifdata; + GIOChannel *channel; + guint watch; + unsigned char *buf; + unsigned int buf_end; + guint timeout; }; +struct cache_data { + time_t inserted; + time_t valid_until; + time_t cache_until; + int timeout; + uint16_t type; + uint16_t answers; + unsigned int data_len; + unsigned char *data; /* contains DNS header + body */ +}; + +struct cache_entry { + char *key; + bool want_refresh; + int hits; + struct cache_data *ipv4; + struct cache_data *ipv6; +}; + +struct domain_question { + uint16_t type; + uint16_t class; +} __attribute__ ((packed)); + +struct domain_rr { + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t rdlen; +} __attribute__ ((packed)); + +/* + * Max length of the DNS TCP packet. + */ +#define TCP_MAX_BUF_LEN 4096 + +/* + * We limit how long the cached DNS entry stays in the cache. + * By default the TTL (time-to-live) of the DNS response is used + * when setting the cache entry life time. The value is in seconds. + */ +#if defined TIZEN_EXT +#define MAX_CACHE_TTL (60 * 60) +#else +#define MAX_CACHE_TTL (60 * 30) +#endif +/* + * 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 + * not occupy too much memory. Each cached entry occupies on average + * about 100 bytes memory (depending on DNS name length). + * Example: caching www.connman.net uses 97 bytes memory. + * The value is the max amount of cached DNS responses (count). + */ +#define MAX_CACHE_SIZE 256 + +static int cache_size; +static GHashTable *cache; +static int cache_refcount; static GSList *server_list = NULL; +#if defined TIZEN_EXT +static GSList *server_list_sec = NULL; +#endif 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 GHashTable *partial_tcp_req_table; +static guint cache_timer = 0; + +#if defined TIZEN_EXT +static void destroy_server_sec(struct server_data *server); +static struct server_data *create_server_sec(int index, + const char *domain, const char *server, + int protocol); +#endif + +static guint16 get_id(void) +{ + uint64_t rand; + + __connman_util_get_random(&rand); + + return rand; +} static int protocol_offset(int protocol) { @@ -145,6 +267,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; @@ -159,28 +302,28 @@ static struct request_data *find_request(guint16 id) return NULL; } -static struct server_data *find_server(const char *interface, +static struct server_data *find_server(int index, const char *server, int protocol) { GSList *list; - DBG("interface %s server %s", interface, server); + debug("index %d server %s proto %d", index, server, protocol); for (list = server_list; list; list = list->next) { struct server_data *data = list->data; - if (interface == NULL && data->interface == NULL && - g_str_equal(data->server, server) == TRUE && + if (index < 0 && data->index < 0 && + g_str_equal(data->server, server) && data->protocol == protocol) return data; - if (interface == NULL || - data->interface == NULL || data->server == NULL) + if (index < 0 || + data->index < 0 || !data->server) continue; - if (g_str_equal(data->interface, interface) == TRUE && - g_str_equal(data->server, server) == TRUE && + if (data->index == index && + g_str_equal(data->server, server) && data->protocol == protocol) return data; } @@ -188,88 +331,293 @@ 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) { + 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) { + ipv6_resolve = g_resolv_new(0); + g_resolv_set_address_family(ipv6_resolve, AF_INET6); + g_resolv_add_nameserver(ipv6_resolve, "::1", 53, 0); + } + + if (!entry->ipv4) { + debug("Refreshing A record for %s", name); + g_resolv_lookup_hostname(ipv4_resolve, name, + dummy_resolve_func, NULL); + age = 4; + } + + if (!entry->ipv6) { + debug("Refreshing 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) + 1; +} + +static void update_cached_ttl(unsigned char *buf, int len, int new_ttl) +{ + unsigned char *c; + 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 */ + c[0] = new_ttl >> 24 & 0xff; + c[1] = new_ttl >> 16 & 0xff; + c[2] = new_ttl >> 8 & 0xff; + c[3] = new_ttl & 0xff; + c += 4; + len -= 4; + if (len < 0) + break; + + /* now the 2 byte rdlen field */ + w = c[0] << 8 | c[1]; + c += w + 2; + len -= 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 ttl) +{ + struct domain_hdr *hdr; + unsigned char *ptr = buf; + int err, offset, dns_len, adj_len = len - 2; + + /* + * 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 *) (ptr + offset); + + hdr->id = id; + hdr->qr = 1; + hdr->rcode = ns_r_noerror; + hdr->ancount = htons(answers); + 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((unsigned char *)hdr, adj_len, ttl); + + debug("sk %d id 0x%04x answers %d ptr %p length %d dns %d", + sk, hdr->id, answers, ptr, len, dns_len); + + err = sendto(sk, ptr, len, MSG_NOSIGNAL, to, tolen); + if (err < 0) { + connman_error("Cannot send cached DNS response: %s", + strerror(errno)); + return; + } -static void send_response(int sk, unsigned char *buf, int len, + if (err != len || (dns_len != (len - 2) && protocol == IPPROTO_TCP) || + (dns_len != len && protocol == IPPROTO_UDP)) + debug("Packet length mismatch, sent %d wanted %d dns %d", + err, len, dns_len); +} + +static void send_response(int sk, unsigned char *buf, size_t len, const struct sockaddr *to, socklen_t tolen, int protocol) { struct domain_hdr *hdr; int err, offset = protocol_offset(protocol); - DBG(""); + debug("sk %d", sk); if (offset < 0) return; - if (len < 12) + if (len < sizeof(*hdr) + offset) return; hdr = (void *) (buf + offset); + if (offset) { + buf[0] = 0; + buf[1] = sizeof(*hdr); + } - DBG("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode); + debug("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode); hdr->qr = 1; - hdr->rcode = 2; + hdr->rcode = ns_r_servfail; + hdr->qdcount = 0; hdr->ancount = 0; hdr->nscount = 0; hdr->arcount = 0; - err = sendto(sk, buf, len, 0, to, tolen); + err = sendto(sk, buf, sizeof(*hdr) + offset, 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; } } +static int get_req_udp_socket(struct request_data *req) +{ + GIOChannel *channel; + + if (req->family == AF_INET) + channel = req->ifdata->udp4_listener_channel; + else + channel = req->ifdata->udp6_listener_channel; + + if (!channel) + return -1; + + return g_io_channel_unix_get_fd(channel); +} + +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 gboolean request_timeout(gpointer user_data) { struct request_data *req = user_data; - struct listener_data *ifdata; - - DBG("id 0x%04x", req->srcid); + struct sockaddr *sa; + int sk; - if (req == NULL) + if (!req) return FALSE; - ifdata = req->ifdata; + debug("id 0x%04x", req->srcid); request_list = g_slist_remove(request_list, req); - req->numserv--; - if (req->resplen > 0 && req->resp != NULL) { - int sk, err; + if (req->protocol == IPPROTO_UDP) { + sk = get_req_udp_socket(req); + sa = &req->sa; + } else if (req->protocol == IPPROTO_TCP) { + sk = req->client_sk; + sa = NULL; + } else + goto out; - sk = g_io_channel_unix_get_fd(ifdata->udp_listener_channel); + if (req->resplen > 0 && req->resp) { + /* + * Here we have received at least one reply (probably telling + * "not found" result), so send that back to client instead + * of more fatal server failed error. + */ + if (sk >= 0) + sendto(sk, req->resp, req->resplen, MSG_NOSIGNAL, + sa, req->sa_len); - err = sendto(sk, req->resp, req->resplen, 0, - &req->sa, req->sa_len); - if (err < 0) - return FALSE; - } else if (req->request && req->numserv == 0) { + } else if (req->request) { + /* + * There was not reply from server at all. + */ struct domain_hdr *hdr; - if (req->protocol == IPPROTO_TCP) { - hdr = (void *) (req->request + 2); - hdr->id = req->srcid; - send_response(req->client_sk, req->request, - req->request_len, NULL, 0, IPPROTO_TCP); - - } else if (req->protocol == IPPROTO_UDP) { - int sk; + hdr = (void *)(req->request + protocol_offset(req->protocol)); + hdr->id = req->srcid; - hdr = (void *) (req->request); - hdr->id = req->srcid; - sk = g_io_channel_unix_get_fd( - ifdata->udp_listener_channel); + if (sk >= 0) send_response(sk, req->request, req->request_len, - &req->sa, req->sa_len, IPPROTO_UDP); - } + sa, req->sa_len, req->protocol); } - g_free(req->resp); - g_free(req); + /* + * We cannot leave TCP client hanging so just kick it out + * if we get a request timeout from server. + */ + if (req->protocol == IPPROTO_TCP) { + debug("client %d removed", req->client_sk); + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(req->client_sk)); + } + +out: + req->timeout = 0; + destroy_request_data(req); return FALSE; } @@ -278,53 +626,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); + debug("query %s domain %s", query, domain); - offset = (char *) query; - while (offset != NULL) { - char *tmp; + while (query) { + const char *tmp; - tmp = strchr(offset, '.'); - if (tmp == NULL) { - len = strlen(offset); + tmp = strchr(query, '.'); + if (!tmp) { + 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) { + const char *tmp; - tmp = strchr(offset, '.'); - if (tmp == NULL) { - len = strlen(offset); + tmp = strchr(domain, '.'); + if (!tmp) { + 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; @@ -332,201 +677,1597 @@ static int append_query(unsigned char *buf, unsigned int size, return ptr - buf; } -static int ns_resolv(struct server_data *server, struct request_data *req, - gpointer request, gpointer name) +static bool cache_check_is_valid(struct cache_data *data, + time_t current_time) { - GList *list; - int sk, err; - char *dot, *lookup = (char *) name; + if (!data) + return false; - sk = g_io_channel_unix_get_fd(server->channel); + if (data->cache_until < current_time) + return false; - err = send(sk, request, req->request_len, 0); + return true; +} - req->numserv++; +/* + * 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 we have more than one dot, we don't add domains */ - dot = strchr(lookup, '.'); - if (dot != NULL && dot != lookup + strlen(lookup) - 1) - return 0; + if (!cache_check_is_valid(entry->ipv4, current_time) + && entry->ipv4) { + debug("cache timeout \"%s\" type A", entry->key); + g_free(entry->ipv4->data); + g_free(entry->ipv4); + entry->ipv4 = NULL; - if (server->domains != NULL && server->domains->data != NULL) - req->append_domain = TRUE; + } - for (list = server->domains; list; list = list->next) { - char *domain; - unsigned char alt[1024]; - struct domain_hdr *hdr = (void *) &alt; - int altlen, domlen, offset; + if (!cache_check_is_valid(entry->ipv6, current_time) + && entry->ipv6) { + debug("cache timeout \"%s\" type AAAA", entry->key); + g_free(entry->ipv6->data); + g_free(entry->ipv6); + entry->ipv6 = NULL; + } +} - domain = list->data; +static uint16_t cache_check_validity(char *question, uint16_t type, + struct cache_entry *entry) +{ + time_t current_time = time(NULL); + bool want_refresh = false; - if (domain == NULL) - continue; + /* + * if we have a popular entry, we want a refresh instead of + * total destruction of the entry. + */ + if (entry->hits > 2) + want_refresh = true; - offset = protocol_offset(server->protocol); - if (offset < 0) - return offset; + cache_enforce_validity(entry); - domlen = strlen(domain) + 1; - if (domlen < 5) - return -EINVAL; + switch (type) { + case 1: /* IPv4 */ + if (!cache_check_is_valid(entry->ipv4, current_time)) { + debug("cache %s \"%s\" type A", entry->ipv4 ? + "timeout" : "entry missing", question); - alt[offset] = req->altid & 0xff; - alt[offset + 1] = req->altid >> 8; + if (want_refresh) + entry->want_refresh = true; - memcpy(alt + offset + 2, request + offset + 2, 10); - hdr->qdcount = htons(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) && !want_refresh) { + g_hash_table_remove(cache, question); + type = 0; + } + } + break; - altlen = append_query(alt + offset + 12, sizeof(alt) - 12, - name, domain); - if (altlen < 0) - return -EINVAL; + case 28: /* IPv6 */ + if (!cache_check_is_valid(entry->ipv6, current_time)) { + debug("cache %s \"%s\" type AAAA", entry->ipv6 ? + "timeout" : "entry missing", question); - altlen += 12; + if (want_refresh) + entry->want_refresh = true; - memcpy(alt + offset + altlen, - request + offset + altlen - domlen, - req->request_len - altlen + domlen); + if (!cache_check_is_valid(entry->ipv4, current_time) && !want_refresh) { + g_hash_table_remove(cache, question); + type = 0; + } + } + break; + } - if (server->protocol == IPPROTO_TCP) { - int req_len = req->request_len + domlen - 2; + return type; +} - alt[0] = (req_len >> 8) & 0xff; - alt[1] = req_len & 0xff; - } +static void cache_element_destroy(gpointer value) +{ + struct cache_entry *entry = value; - err = send(sk, alt, req->request_len + domlen, 0); - if (err < 0) - return -EIO; + if (!entry) + return; - req->numserv++; + if (entry->ipv4) { + g_free(entry->ipv4->data); + g_free(entry->ipv4); } - return 0; + if (entry->ipv6) { + g_free(entry->ipv6->data); + g_free(entry->ipv6); + } + + g_free(entry->key); + g_free(entry); + + if (--cache_size < 0) + cache_size = 0; } -static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol) +static gboolean try_remove_cache(gpointer user_data) { - struct domain_hdr *hdr; - struct request_data *req; - int dns_id, sk, err, offset = protocol_offset(protocol); - struct listener_data *ifdata; + cache_timer = 0; - if (offset < 0) - return offset; + if (__sync_fetch_and_sub(&cache_refcount, 1) == 1) { + debug("No cache users, removing it."); - hdr = (void *)(reply + offset); - dns_id = reply[offset] | reply[offset + 1] << 8; + g_hash_table_destroy(cache); + cache = NULL; + } - DBG("Received %d bytes (id 0x%04x)", reply_len, dns_id); + return FALSE; +} - req = find_request(dns_id); - if (req == NULL) - return -EINVAL; +static void create_cache(void) +{ + if (__sync_fetch_and_add(&cache_refcount, 1) == 0) + cache = g_hash_table_new_full(g_str_hash, + g_str_equal, + NULL, + cache_element_destroy); +} - DBG("id 0x%04x rcode %d", hdr->id, hdr->rcode); +static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) +{ + char *question; + struct cache_entry *entry; + struct domain_question *q; + uint16_t type; + int offset, proto_offset; - ifdata = req->ifdata; + if (!request) + return NULL; - reply[offset] = req->srcid & 0xff; - reply[offset + 1] = req->srcid >> 8; + proto_offset = protocol_offset(proto); + if (proto_offset < 0) + return NULL; - req->numresp++; + question = request + proto_offset + 12; - if (hdr->rcode == 0 || req->resp == NULL) { + offset = strlen(question) + 1; + q = (void *) (question + offset); + type = ntohs(q->type); - /* - * If the domain name was append - * remove it before forwarding the reply. - */ - if (req->append_domain == TRUE) { - unsigned char *ptr; - uint8_t host_len; - unsigned int domain_len; + /* We only cache either A (1) or AAAA (28) requests */ + if (type != 1 && type != 28) + return NULL; - /* - * ptr points to the first char of the hostname. - * ->hostname.domain.net - */ - ptr = reply + offset + sizeof(struct domain_hdr); - host_len = *ptr; - domain_len = strlen((const char *)ptr) - host_len - 1; + if (!cache) { + create_cache(); + return NULL; + } - /* - * remove the domain name and replaced it by the end - * of reply. - */ - memmove(ptr + host_len + 1, - ptr + host_len + domain_len + 1, - reply_len - (ptr - reply + domain_len)); + entry = g_hash_table_lookup(cache, question); + if (!entry) + return NULL; - reply_len = reply_len - domain_len; - } + type = cache_check_validity(question, type, entry); + if (type == 0) + return NULL; - g_free(req->resp); + *qtype = type; + return entry; +} + +/* + * Get a label/name from DNS resource record. The function decompresses the + * label if necessary. The function does not convert the name to presentation + * form. This means that the result string will contain label lengths instead + * of dots between labels. We intentionally do not want to convert to dotted + * format so that we can cache the wire format string directly. + */ +static int get_name(int counter, + unsigned char *pkt, unsigned char *start, unsigned char *max, + unsigned char *output, int output_max, int *output_len, + unsigned char **end, char *name, size_t max_name, int *name_len) +{ + unsigned char *p; + + /* Limit recursion to 10 (this means up to 10 labels in domain name) */ + if (counter > 10) + return -EINVAL; + + p = start; + while (*p) { + if ((*p & NS_CMPRSFLGS) == NS_CMPRSFLGS) { + uint16_t offset = (*p & 0x3F) * 256 + *(p + 1); + + if (offset >= max - pkt) + return -ENOBUFS; + + if (!*end) + *end = p + 2; + + return get_name(counter + 1, pkt, pkt + offset, max, + output, output_max, output_len, end, + name, max_name, name_len); + } else { + unsigned label_len = *p; + + if (pkt + label_len > max) + return -ENOBUFS; + + if (*output_len > output_max) + return -ENOBUFS; + + if ((*name_len + 1 + label_len + 1) > max_name) + return -ENOBUFS; + + /* + * We need the original name in order to check + * if this answer is the correct one. + */ + name[(*name_len)++] = label_len; + memcpy(name + *name_len, p + 1, label_len + 1); + *name_len += label_len; + + /* We compress the result */ + output[0] = NS_CMPRSFLGS; + output[1] = 0x0C; + *output_len = 2; + + p += label_len + 1; + + if (!*end) + *end = p; + + if (p >= max) + return -ENOBUFS; + } + } + + return 0; +} + +static int parse_rr(unsigned char *buf, unsigned char *start, + unsigned char *max, + unsigned char *response, unsigned int *response_size, + uint16_t *type, uint16_t *class, int *ttl, int *rdlen, + unsigned char **end, + char *name, size_t max_name) +{ + struct domain_rr *rr; + int err, offset; + int name_len = 0, output_len = 0, max_rsp = *response_size; + + err = get_name(0, buf, start, max, response, max_rsp, + &output_len, end, name, max_name, &name_len); + if (err < 0) + return err; + + offset = output_len; + + if ((unsigned int) offset > *response_size) + return -ENOBUFS; + + rr = (void *) (*end); + + if (!rr) + return -EINVAL; + + *type = ntohs(rr->type); + *class = ntohs(rr->class); + *ttl = ntohl(rr->ttl); + *rdlen = ntohs(rr->rdlen); + + if (*ttl < 0) + return -EINVAL; + + memcpy(response + offset, *end, sizeof(struct domain_rr)); + + offset += sizeof(struct domain_rr); + *end += sizeof(struct domain_rr); + + if ((unsigned int) (offset + *rdlen) > *response_size) + return -ENOBUFS; + + memcpy(response + offset, *end, *rdlen); + + *end += *rdlen; + + *response_size = offset + *rdlen; + + return 0; +} + +static bool check_alias(GSList *aliases, char *name) +{ + GSList *list; + + if (aliases) { + for (list = aliases; list; list = list->next) { + int len = strlen((char *)list->data); + if (strncmp((char *)list->data, name, len) == 0) + return true; + } + } + + return false; +} + +static int parse_response(unsigned char *buf, int buflen, + char *question, int qlen, + uint16_t *type, uint16_t *class, int *ttl, + unsigned char *response, unsigned int *response_len, + uint16_t *answers) +{ + struct domain_hdr *hdr = (void *) buf; + struct domain_question *q; + unsigned char *ptr; + uint16_t qdcount = ntohs(hdr->qdcount); + uint16_t ancount = ntohs(hdr->ancount); + int err, i; + uint16_t qtype, qclass; + unsigned char *next = NULL; + unsigned int maxlen = *response_len; + GSList *aliases = NULL, *list; + char name[NS_MAXDNAME + 1]; + + if (buflen < 12) + return -EINVAL; + + debug("qr %d qdcount %d", hdr->qr, qdcount); + + /* We currently only cache responses where question count is 1 */ + if (hdr->qr != 1 || qdcount != 1) + return -EINVAL; + + ptr = buf + sizeof(struct domain_hdr); + + strncpy(question, (char *) ptr, qlen); + qlen = strlen(question); + ptr += qlen + 1; /* skip \0 */ + + q = (void *) ptr; + qtype = ntohs(q->type); + + /* We cache only A and AAAA records */ + if (qtype != 1 && qtype != 28) + return -ENOMSG; + + qclass = ntohs(q->class); + + ptr += 2 + 2; /* ptr points now to answers */ + + err = -ENOMSG; + *response_len = 0; + *answers = 0; + + memset(name, 0, sizeof(name)); + + /* + * We have a bunch of answers (like A, AAAA, CNAME etc) to + * A or AAAA question. We traverse the answers and parse the + * resource records. Only A and AAAA records are cached, all + * the other records in answers are skipped. + */ + for (i = 0; i < ancount; i++) { + /* + * Get one address at a time to this buffer. + * The max size of the answer is + * 2 (pointer) + 2 (type) + 2 (class) + + * 4 (ttl) + 2 (rdlen) + addr (16 or 4) = 28 + * for A or AAAA record. + * For CNAME the size can be bigger. + */ + unsigned char rsp[NS_MAXCDNAME]; + unsigned int rsp_len = sizeof(rsp) - 1; + int ret, rdlen; + + memset(rsp, 0, sizeof(rsp)); + + ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len, + type, class, ttl, &rdlen, &next, name, + sizeof(name) - 1); + if (ret != 0) { + err = ret; + goto out; + } + + /* + * Now rsp contains compressed or uncompressed resource + * record. Next we check if this record answers the question. + * The name var contains the uncompressed label. + * One tricky bit is the CNAME records as they alias + * the name we might be interested in. + */ + + /* + * Go to next answer if the class is not the one we are + * looking for. + */ + if (*class != qclass) { + ptr = next; + next = NULL; + continue; + } + + /* + * Try to resolve aliases also, type is CNAME(5). + * This is important as otherwise the aliased names would not + * be cached at all as the cache would not contain the aliased + * question. + * + * If any CNAME is found in DNS packet, then we cache the alias + * IP address instead of the question (as the server + * said that question has only an alias). + * This means in practice that if e.g., ipv6.google.com is + * queried, DNS server returns CNAME of that name which is + * ipv6.l.google.com. We then cache the address of the CNAME + * but return the question name to client. So the alias + * status of the name is not saved in cache and thus not + * returned to the client. We do not return DNS packets from + * cache to client saying that ipv6.google.com is an alias to + * ipv6.l.google.com but we return instead a DNS packet that + * says ipv6.google.com has address xxx which is in fact the + * address of ipv6.l.google.com. For caching purposes this + * should not cause any issues. + */ + if (*type == 5 && strncmp(question, name, qlen) == 0) { + /* + * So now the alias answered the question. This is + * not very useful from caching point of view as + * the following A or AAAA records will not match the + * question. We need to find the real A/AAAA record + * of the alias and cache that. + */ + unsigned char *end = NULL; + int name_len = 0, output_len = 0; + + memset(rsp, 0, sizeof(rsp)); + rsp_len = sizeof(rsp) - 1; + + /* + * Alias is in rdata part of the message, + * and next-rdlen points to it. So we need to get + * the real name of the alias. + */ + ret = get_name(0, buf, next - rdlen, buf + buflen, + rsp, rsp_len, &output_len, &end, + name, sizeof(name) - 1, &name_len); + if (ret != 0) { + /* just ignore the error at this point */ + ptr = next; + next = NULL; + continue; + } + + /* + * We should now have the alias of the entry we might + * want to cache. Just remember it for a while. + * We check the alias list when we have parsed the + * A or AAAA record. + */ + aliases = g_slist_prepend(aliases, g_strdup(name)); + + ptr = next; + next = NULL; + continue; + } + + if (*type == qtype) { + /* + * We found correct type (A or AAAA) + */ + if (check_alias(aliases, name) || + (!aliases && strncmp(question, name, + qlen) == 0)) { + /* + * We found an alias or the name of the rr + * matches the question. If so, we append + * the compressed label to the cache. + * The end result is a response buffer that + * will contain one or more cached and + * compressed resource records. + */ + if (*response_len + rsp_len > maxlen) { + err = -ENOBUFS; + goto out; + } + memcpy(response + *response_len, rsp, rsp_len); + *response_len += rsp_len; + (*answers)++; + err = 0; + } + } + + ptr = next; + next = NULL; + } + +out: + for (list = aliases; list; list = list->next) + g_free(list->data); + g_slist_free(aliases); + + return err; +} + +struct cache_timeout { + time_t current_time; + int max_timeout; + int try_harder; +}; + +static gboolean cache_check_entry(gpointer key, gpointer value, + gpointer user_data) +{ + struct cache_timeout *data = user_data; + 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 && entry->ipv4->timeout > 0) { + max_timeout = entry->ipv4->cache_until; + if (max_timeout > data->max_timeout) + data->max_timeout = max_timeout; + + if (entry->ipv4->cache_until < data->current_time) + return TRUE; + } + + if (entry->ipv6 && entry->ipv6->timeout > 0) { + max_timeout = entry->ipv6->cache_until; + if (max_timeout > data->max_timeout) + data->max_timeout = max_timeout; + + 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; +} + +static void cache_cleanup(void) +{ + static int max_timeout; + struct cache_timeout data; + int count = 0; + + data.current_time = time(NULL); + data.max_timeout = 0; + data.try_harder = 0; + + /* + * 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); + } + debug("removed %d in the first pass", count); + + /* + * 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); + + if (count == 0) + /* + * If we could not remove anything, then remember + * what is the max timeout and do nothing if we + * have not yet reached it. This will prevent + * constant traversal of the cache if it is full. + */ + max_timeout = data.max_timeout; + else + 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 = true; + + /* 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) +{ + debug("Invalidating the DNS cache %p", cache); + + if (!cache) + 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) + entry->want_refresh = true; + if (entry->hits > 2 && !entry->ipv6) + entry->want_refresh = true; + + if (entry->want_refresh) { + char *c; + char dns_name[NS_MAXDNAME + 1]; + entry->want_refresh = false; + + /* 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; + } + debug("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) + return; + + g_hash_table_foreach(cache, cache_refresh_iterator, NULL); +} + +static int reply_query_type(unsigned char *msg, int len) +{ + unsigned char *c; + 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); + c += l; + type = c[0] << 8 | c[1]; + + 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 = 0, type = 0, class = 0; + struct domain_hdr *hdr = (void *)(msg + offset); + struct domain_question *q; + struct cache_entry *entry; + struct cache_data *data; + char question[NS_MAXDNAME + 1]; + unsigned char response[NS_MAXDNAME + 1]; + unsigned char *ptr; + unsigned int rsplen; + bool new_entry = true; + time_t current_time; + + if (cache_size >= MAX_CACHE_SIZE) { + cache_cleanup(); + if (cache_size >= MAX_CACHE_SIZE) + 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; + + debug("offset %d hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode); + + /* Continue only if response code is 0 (=ok) */ + if (hdr->rcode != ns_r_noerror) + return 0; + + if (!cache) + create_cache(); + + rsplen = sizeof(response) - 1; + question[sizeof(question) - 1] = '\0'; + + err = parse_response(msg + offset, msg_len - offset, + 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 + offset, + msg_len - offset) == 28) { + entry = g_hash_table_lookup(cache, question); + if (entry && entry->ipv4 && !entry->ipv6) { + int cache_offset = 0; + + data = g_try_new(struct cache_data, 1); + if (!data) + return -ENOMEM; + data->inserted = entry->ipv4->inserted; + data->type = type; + data->answers = ntohs(hdr->ancount); + data->timeout = entry->ipv4->timeout; + 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(ptr, 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); + + /* + * If the cache contains already data, check if the + * type of the cached data is the same and do not add + * to cache if data is already there. + * This is needed so that we can cache both A and AAAA + * records for the same name. + */ + entry = g_hash_table_lookup(cache, question); + if (!entry) { + entry = g_try_new(struct cache_entry, 1); + if (!entry) + return -ENOMEM; + + data = g_try_new(struct cache_data, 1); + if (!data) { + g_free(entry); + return -ENOMEM; + } + + entry->key = g_strdup(question); + entry->ipv4 = entry->ipv6 = NULL; + entry->want_refresh = false; + entry->hits = 0; + + if (type == 1) + entry->ipv4 = data; + else + entry->ipv6 = data; + } else { + if (type == 1 && entry->ipv4) + return 0; + + if (type == 28 && entry->ipv6) + return 0; + + data = g_try_new(struct cache_data, 1); + if (!data) + return -ENOMEM; + + if (type == 1) + entry->ipv4 = data; + 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; + } + + if (ttl < MIN_CACHE_TTL) + ttl = MIN_CACHE_TTL; + + data->inserted = current_time; + data->type = type; + data->answers = answers; + data->timeout = ttl; + /* + * 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; + + /* + * 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) { + g_free(entry->key); + g_free(data); + g_free(entry); + return -ENOMEM; + } + + /* + * 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. + */ + if (srv->protocol == IPPROTO_UDP) + memcpy(ptr + 2, msg, offset + 12); + else + memcpy(ptr, msg, offset + 12); + + 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 + offset + 12, question, qlen + 1); /* copy also the \0 */ + + q = (void *) (ptr + offset + 12 + qlen + 1); + q->type = htons(type); + q->class = htons(class); + memcpy(ptr + offset + 12 + qlen + 1 + sizeof(struct domain_question), + response, rsplen); + + if (new_entry) { + g_hash_table_replace(cache, entry->key, entry); + cache_size++; + } + + debug("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, + data->data_len, + srv->protocol == IPPROTO_TCP ? + (unsigned int)(data->data[0] * 256 + data->data[1]) : + data->data_len); + + return 0; +} + +static int ns_resolv(struct server_data *server, struct request_data *req, + gpointer request, gpointer name) +{ + GList *list; + int sk, err, type = 0; + char *dot, *lookup = (char *) name; + struct cache_entry *entry; + + entry = cache_check(request, &type, req->protocol); + if (entry) { + int ttl_left = 0; + struct cache_data *data; + + debug("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA"); + if (type == 1) + data = entry->ipv4; + else + data = entry->ipv6; + + if (data) { + ttl_left = data->valid_until - time(NULL); + entry->hits++; + } + + if (data && req->protocol == IPPROTO_TCP) { + send_cached_response(req->client_sk, data->data, + data->data_len, NULL, 0, IPPROTO_TCP, + req->srcid, data->answers, ttl_left); + return 1; + } + + if (data && req->protocol == IPPROTO_UDP) { + int udp_sk = get_req_udp_socket(req); + + if (udp_sk < 0) + return -EIO; + + send_cached_response(udp_sk, data->data, + data->data_len, &req->sa, req->sa_len, + IPPROTO_UDP, req->srcid, data->answers, + ttl_left); + return 1; + } + } + +#if defined TIZEN_EXT + if (server->protocol == IPPROTO_UDP) { + GList *domains; + struct server_data *new_server = NULL; + + new_server = create_server_sec(server->index, NULL, + server->server, IPPROTO_UDP); + + if (new_server != NULL) { + for (domains = server->domains; domains; + domains = domains->next) { + char *dom = domains->data; + + DBG("Adding domain %s to %s", + dom, new_server->server); + + new_server->domains = g_list_append( + new_server->domains, + g_strdup(dom)); + } + + server = new_server; + } + } +#endif + sk = g_io_channel_unix_get_fd(server->channel); + + err = sendto(sk, request, req->request_len, MSG_NOSIGNAL, + server->server_addr, server->server_addr_len); + if (err < 0) { + debug("Cannot send message to server %s sock %d " + "protocol %d (%s/%d)", + server->server, sk, server->protocol, + strerror(errno), errno); + return -EIO; + } + + req->numserv++; + + /* If we have more than one dot, we don't add domains */ + dot = strchr(lookup, '.'); + if (dot && dot != lookup + strlen(lookup) - 1) + return 0; + + if (server->domains && server->domains->data) + req->append_domain = true; + + for (list = server->domains; list; list = list->next) { + char *domain; + unsigned char alt[1024]; + struct domain_hdr *hdr = (void *) &alt; + int altlen, domlen, offset; + + domain = list->data; + + if (!domain) + continue; + + offset = protocol_offset(server->protocol); + if (offset < 0) + return offset; + + domlen = strlen(domain) + 1; + if (domlen < 5) + return -EINVAL; + + alt[offset] = req->altid & 0xff; + alt[offset + 1] = req->altid >> 8; + + memcpy(alt + offset + 2, request + offset + 2, 10); + hdr->qdcount = htons(1); + + altlen = append_query(alt + offset + 12, sizeof(alt) - 12, + name, domain); + if (altlen < 0) + return -EINVAL; + + altlen += 12; + + memcpy(alt + offset + altlen, + request + offset + altlen - domlen, + req->request_len - altlen - offset + domlen); + + if (server->protocol == IPPROTO_TCP) { + int req_len = req->request_len + domlen - 2; + + alt[0] = (req_len >> 8) & 0xff; + alt[1] = req_len & 0xff; + } + + debug("req %p dstid 0x%04x altid 0x%04x", req, req->dstid, + req->altid); + + err = send(sk, alt, req->request_len + domlen, MSG_NOSIGNAL); + if (err < 0) + return -EIO; + + req->numserv++; + } + + return 0; +} + +static char *convert_label(char *start, char *end, char *ptr, char *uptr, + int remaining_len, int *used_comp, int *used_uncomp) +{ + int pos, comp_pos; + char name[NS_MAXLABEL]; + + pos = dn_expand((u_char *)start, (u_char *)end, (u_char *)ptr, + name, NS_MAXLABEL); + if (pos < 0) { + debug("uncompress error [%d/%s]", errno, strerror(errno)); + goto out; + } + + /* + * We need to compress back the name so that we get back to internal + * label presentation. + */ + comp_pos = dn_comp(name, (u_char *)uptr, remaining_len, NULL, NULL); + if (comp_pos < 0) { + debug("compress error [%d/%s]", errno, strerror(errno)); + goto out; + } + + *used_comp = pos; + *used_uncomp = comp_pos; + + return ptr; + +out: + return NULL; +} + +static char *uncompress(int16_t field_count, char *start, char *end, + char *ptr, char *uncompressed, int uncomp_len, + char **uncompressed_ptr) +{ + char *uptr = *uncompressed_ptr; /* position in result buffer */ + + debug("count %d ptr %p end %p uptr %p", field_count, ptr, end, uptr); + + while (field_count-- > 0 && ptr < end) { + int dlen; /* data field length */ + int ulen; /* uncompress length */ + int pos; /* position in compressed string */ + char name[NS_MAXLABEL]; /* tmp label */ + uint16_t dns_type, dns_class; + int comp_pos; + + if (!convert_label(start, end, ptr, name, NS_MAXLABEL, + &pos, &comp_pos)) + goto out; + + /* + * Copy the uncompressed resource record, type, class and \0 to + * tmp buffer. + */ + + ulen = strlen(name); + strncpy(uptr, name, uncomp_len - (uptr - uncompressed)); + + debug("pos %d ulen %d left %d name %s", pos, ulen, + (int)(uncomp_len - (uptr - uncompressed)), uptr); + + uptr += ulen; + *uptr++ = '\0'; + + ptr += pos; + + /* + * We copy also the fixed portion of the result (type, class, + * ttl, address length and the address) + */ + memcpy(uptr, ptr, NS_RRFIXEDSZ); + + dns_type = uptr[0] << 8 | uptr[1]; + dns_class = uptr[2] << 8 | uptr[3]; + + if (dns_class != ns_c_in) + goto out; + + ptr += NS_RRFIXEDSZ; + uptr += NS_RRFIXEDSZ; + + /* + * Then the variable portion of the result (data length). + * Typically this portion is also compressed + * so we need to uncompress it also when necessary. + */ + if (dns_type == ns_t_cname) { + if (!convert_label(start, end, ptr, uptr, + uncomp_len - (uptr - uncompressed), + &pos, &comp_pos)) + goto out; + + uptr[-2] = comp_pos << 8; + uptr[-1] = comp_pos & 0xff; + + uptr += comp_pos; + ptr += pos; + + } else if (dns_type == ns_t_a || dns_type == ns_t_aaaa) { + dlen = uptr[-2] << 8 | uptr[-1]; + + if (ptr + dlen > end) { + debug("data len %d too long", dlen); + goto out; + } + + memcpy(uptr, ptr, dlen); + uptr += dlen; + ptr += dlen; + + } else if (dns_type == ns_t_soa) { + int total_len = 0; + char *len_ptr; + + /* Primary name server expansion */ + if (!convert_label(start, end, ptr, uptr, + uncomp_len - (uptr - uncompressed), + &pos, &comp_pos)) + goto out; + + total_len += comp_pos; + len_ptr = &uptr[-2]; + ptr += pos; + uptr += comp_pos; + + /* Responsible authority's mailbox */ + if (!convert_label(start, end, ptr, uptr, + uncomp_len - (uptr - uncompressed), + &pos, &comp_pos)) + goto out; + + total_len += comp_pos; + ptr += pos; + uptr += comp_pos; + + /* + * Copy rest of the soa fields (serial number, + * refresh interval, retry interval, expiration + * limit and minimum ttl). They are 20 bytes long. + */ + memcpy(uptr, ptr, 20); + uptr += 20; + ptr += 20; + total_len += 20; + + /* + * Finally fix the length of the data part + */ + len_ptr[0] = total_len << 8; + len_ptr[1] = total_len & 0xff; + } + + *uncompressed_ptr = uptr; + } + + return ptr; + +out: + return NULL; +} + +static int strip_domains(char *name, char *answers, int maxlen) +{ + uint16_t data_len; + int name_len = strlen(name); + char *ptr, *start = answers, *end = answers + maxlen; + + while (maxlen > 0) { + ptr = strstr(answers, name); + if (ptr) { + char *domain = ptr + name_len; + + if (*domain) { + int domain_len = strlen(domain); + + memmove(answers + name_len, + domain + domain_len, + end - (domain + domain_len)); + + end -= domain_len; + maxlen -= domain_len; + } + } + + answers += strlen(answers) + 1; + answers += 2 + 2 + 4; /* skip type, class and ttl fields */ + + data_len = answers[0] << 8 | answers[1]; + answers += 2; /* skip the length field */ + + if (answers + data_len > end) + return -EINVAL; + + answers += data_len; + maxlen -= answers - ptr; + } + + return end - start; +} + +static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, + struct server_data *data) +{ + struct domain_hdr *hdr; + struct request_data *req; + int dns_id, sk, err, offset = protocol_offset(protocol); + + if (offset < 0) + return offset; + + hdr = (void *)(reply + offset); + dns_id = reply[offset] | reply[offset + 1] << 8; + + debug("Received %d bytes (id 0x%04x)", reply_len, dns_id); + + req = find_request(dns_id); + if (!req) + return -EINVAL; + + debug("req %p dstid 0x%04x altid 0x%04x rcode %d", + req, req->dstid, req->altid, hdr->rcode); + + reply[offset] = req->srcid & 0xff; + reply[offset + 1] = req->srcid >> 8; + + req->numresp++; + + if (hdr->rcode == ns_r_noerror || !req->resp) { + unsigned char *new_reply = NULL; + + /* + * If the domain name was append + * remove it before forwarding the reply. + * If there were more than one question, then this + * domain name ripping can be hairy so avoid that + * and bail out in that that case. + * + * The reason we are doing this magic is that if the + * user's DNS client tries to resolv hostname without + * domain part, it also expects to get the result without + * a domain name part. + */ + if (req->append_domain && ntohs(hdr->qdcount) == 1) { + uint16_t domain_len = 0; + uint16_t header_len; + uint16_t dns_type, dns_class; + uint8_t host_len, dns_type_pos; + char uncompressed[NS_MAXDNAME], *uptr; + char *ptr, *eom = (char *)reply + reply_len; + + /* + * ptr points to the first char of the hostname. + * ->hostname.domain.net + */ + header_len = offset + sizeof(struct domain_hdr); + ptr = (char *)reply + header_len; + + host_len = *ptr; + if (host_len > 0) + domain_len = strnlen(ptr + 1 + host_len, + reply_len - header_len); + + /* + * If the query type is anything other than A or AAAA, + * then bail out and pass the message as is. + * We only want to deal with IPv4 or IPv6 addresses. + */ + dns_type_pos = host_len + 1 + domain_len + 1; + + dns_type = ptr[dns_type_pos] << 8 | + ptr[dns_type_pos + 1]; + dns_class = ptr[dns_type_pos + 2] << 8 | + ptr[dns_type_pos + 3]; + if (dns_type != ns_t_a && dns_type != ns_t_aaaa && + dns_class != ns_c_in) { + debug("Pass msg dns type %d class %d", + dns_type, dns_class); + goto pass; + } + + /* + * 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. We also need to + * uncompress the answers if necessary. + * 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. + */ + if (domain_len > 0) { + int len = host_len + 1; + int new_len, fixed_len; + char *answers; + + /* + * First copy host (without domain name) into + * tmp buffer. + */ + uptr = &uncompressed[0]; + memcpy(uptr, ptr, len); + + uptr[len] = '\0'; /* host termination */ + uptr += len + 1; + + /* + * Copy type and class fields of the question. + */ + ptr += len + domain_len + 1; + memcpy(uptr, ptr, NS_QFIXEDSZ); + + /* + * ptr points to answers after this + */ + ptr += NS_QFIXEDSZ; + uptr += NS_QFIXEDSZ; + answers = uptr; + fixed_len = answers - uncompressed; + + /* + * We then uncompress the result to buffer + * so that we can rip off the domain name + * part from the question. First answers, + * then name server (authority) information, + * and finally additional record info. + */ + + ptr = uncompress(ntohs(hdr->ancount), + (char *)reply + offset, eom, + ptr, uncompressed, NS_MAXDNAME, + &uptr); + if (!ptr) + goto out; + + ptr = uncompress(ntohs(hdr->nscount), + (char *)reply + offset, eom, + ptr, uncompressed, NS_MAXDNAME, + &uptr); + if (!ptr) + goto out; + + ptr = uncompress(ntohs(hdr->arcount), + (char *)reply + offset, eom, + ptr, uncompressed, NS_MAXDNAME, + &uptr); + if (!ptr) + goto out; + + /* + * The uncompressed buffer now contains almost + * valid response. Final step is to get rid of + * the domain name because at least glibc + * gethostbyname() implementation does extra + * checks and expects to find an answer without + * domain name if we asked a query without + * domain part. Note that glibc getaddrinfo() + * works differently and accepts FQDN in answer + */ + new_len = strip_domains(uncompressed, answers, + uptr - answers); + if (new_len < 0) { + debug("Corrupted packet"); + return -EINVAL; + } + + /* + * Because we have now uncompressed the answers + * we might have to create a bigger buffer to + * hold all that data. + */ + + reply_len = header_len + new_len + fixed_len; + + new_reply = g_try_malloc(reply_len); + if (!new_reply) + return -ENOMEM; + + memcpy(new_reply, reply, header_len); + memcpy(new_reply + header_len, uncompressed, + new_len + fixed_len); + + reply = new_reply; + } + } + + pass: + g_free(req->resp); req->resplen = 0; req->resp = g_try_malloc(reply_len); - if (req->resp == NULL) + if (!req->resp) return -ENOMEM; memcpy(req->resp, reply, reply_len); req->resplen = reply_len; - } - if (hdr->rcode > 0 && req->numresp < req->numserv) - return -EINVAL; + cache_update(data, reply, reply_len); - if (req->timeout > 0) - g_source_remove(req->timeout); + g_free(new_reply); + } + +out: + if (req->numresp < req->numserv) { + if (hdr->rcode > ns_r_noerror) { + return -EINVAL; + } else if (hdr->ancount == 0 && req->append_domain) { + return -EINVAL; + } + } request_list = g_slist_remove(request_list, req); if (protocol == IPPROTO_UDP) { - sk = g_io_channel_unix_get_fd(ifdata->udp_listener_channel); - err = sendto(sk, req->resp, req->resplen, 0, - &req->sa, req->sa_len); + sk = get_req_udp_socket(req); + if (sk < 0) { + errno = -EIO; + err = -EIO; + } else + err = sendto(sk, req->resp, req->resplen, 0, + &req->sa, req->sa_len); } else { sk = req->client_sk; - err = send(sk, req->resp, req->resplen, 0); - close(sk); + err = send(sk, req->resp, req->resplen, MSG_NOSIGNAL); } - g_free(req->resp); - g_free(req); + if (err < 0) + debug("Cannot send msg, sk %d proto %d errno %d/%s", sk, + protocol, errno, strerror(errno)); + else + debug("proto %d sent %d bytes to %d", protocol, err, sk); + + destroy_request_data(req); return err; } -static void destroy_server(struct server_data *server) +static void server_destroy_socket(struct server_data *data) { - GList *list; + debug("index %d server %s proto %d", data->index, + data->server, data->protocol); - DBG("interface %s server %s", server->interface, server->server); + if (data->watch > 0) { + g_source_remove(data->watch); + data->watch = 0; + } - server_list = g_slist_remove(server_list, server); + if (data->timeout > 0) { + g_source_remove(data->timeout); + data->timeout = 0; + } + + if (data->channel) { + g_io_channel_shutdown(data->channel, TRUE, NULL); + g_io_channel_unref(data->channel); + data->channel = NULL; + } - if (server->watch > 0) - g_source_remove(server->watch); + g_free(data->incoming_reply); + data->incoming_reply = NULL; +} - if (server->timeout > 0) - g_source_remove(server->timeout); +static void destroy_server(struct server_data *server) +{ + debug("index %d server %s sock %d", server->index, server->server, + server->channel ? + g_io_channel_unix_get_fd(server->channel): -1); - g_io_channel_unref(server->channel); + server_list = g_slist_remove(server_list, server); + server_destroy_socket(server); - if (server->protocol == IPPROTO_UDP) - connman_info("Removing DNS server %s", server->server); + if (server->protocol == IPPROTO_UDP && server->enabled) + debug("Removing DNS server %s", server->server); - g_free(server->incoming_reply); g_free(server->server); - for (list = server->domains; list; list = list->next) { - char *domain = list->data; + g_list_free_full(server->domains, g_free); + g_free(server->server_addr); + + /* + * 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. + */ + if (cache && !cache_timer) + cache_timer = g_timeout_add_seconds(3, try_remove_cache, NULL); - server->domains = g_list_remove(server->domains, domain); - g_free(domain); - } - g_free(server->interface); g_free(server); } @@ -534,25 +2275,34 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { unsigned char buf[4096]; - int sk, err, len; + int sk, len; + struct server_data *data = user_data; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { - struct server_data *data = user_data; - connman_error("Error with UDP server %s", data->server); - data->watch = 0; + server_destroy_socket(data); return FALSE; } sk = g_io_channel_unix_get_fd(channel); len = recv(sk, buf, sizeof(buf), 0); - if (len < 12) - return TRUE; - err = forward_dns_reply(buf, len, IPPROTO_UDP); - if (err < 0) - return TRUE; + if (len >= 12) + forward_dns_reply(buf, len, IPPROTO_UDP, data); + +#if defined TIZEN_EXT + GSList *list; + + for (list = server_list_sec; list; list = list->next) { + struct server_data *new_data = list->data; + + if (new_data == data) { + destroy_server_sec(data); + return TRUE; + } + } +#endif return TRUE; } @@ -570,7 +2320,7 @@ static gboolean tcp_server_event(GIOChannel *channel, GIOCondition condition, if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { GSList *list; hangup: - DBG("TCP server channel closed"); + debug("TCP server channel closed, sk %d", sk); /* * Discard any partial response which is buffered; better @@ -579,14 +2329,16 @@ hangup: g_free(server->incoming_reply); server->incoming_reply = NULL; - for (list = request_list; list; list = list->next) { + list = request_list; + while (list) { struct request_data *req = list->data; struct domain_hdr *hdr; + list = list->next; if (req->protocol == IPPROTO_UDP) continue; - if (req->request == NULL) + if (!req->request) continue; /* @@ -613,16 +2365,17 @@ hangup: if ((condition & G_IO_OUT) && !server->connected) { GSList *list; GList *domains; + bool no_request_sent = true; struct server_data *udp_server; - udp_server = find_server(server->interface, server->server, + udp_server = find_server(server->index, server->server, IPPROTO_UDP); - if (udp_server != NULL) { + if (udp_server) { for (domains = udp_server->domains; domains; domains = domains->next) { char *dom = domains->data; - DBG("Adding domain %s to %s", + debug("Adding domain %s to %s", dom, server->server); server->domains = g_list_append(server->domains, @@ -630,7 +2383,7 @@ hangup: } } - server->connected = TRUE; + server->connected = true; server_list = g_slist_append(server_list, server); if (server->timeout > 0) { @@ -638,20 +2391,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; + } + + debug("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; + } - DBG("Sending req %s over TCP", (char *)req->name); + no_request_sent = false; if (req->timeout > 0) g_source_remove(req->timeout); req->timeout = g_timeout_add_seconds(30, request_timeout, req); - ns_resolv(server, req, req->request, req->name); + list = list->next; + } + + if (no_request_sent) { + destroy_server(server); + return FALSE; } } else if (condition & G_IO_IN) { @@ -678,7 +2459,7 @@ hangup: reply_len = reply_len_buf[1] | reply_len_buf[0] << 8; reply_len += 2; - DBG("TCP reply %d bytes", reply_len); + debug("TCP reply %d bytes from %d", reply_len, sk); reply = g_try_malloc(sizeof(*reply) + reply_len + 2); if (!reply) @@ -707,7 +2488,8 @@ hangup: reply->received += bytes_recv; } - forward_dns_reply(reply->buf, reply->received, IPPROTO_TCP); + forward_dns_reply(reply->buf, reply->received, IPPROTO_TCP, + server); g_free(reply); server->incoming_reply = NULL; @@ -724,25 +2506,306 @@ static gboolean tcp_idle_timeout(gpointer user_data) { struct server_data *server = user_data; + debug(""); + + if (!server) + return FALSE; + + destroy_server(server); + + return FALSE; +} + +static int server_create_socket(struct server_data *data) +{ + int sk, err; + char *interface; + + debug("index %d server %s proto %d", data->index, + data->server, data->protocol); + + sk = socket(data->server_addr->sa_family, + data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, + data->protocol); + if (sk < 0) { + err = errno; + connman_error("Failed to create server %s socket", + data->server); + server_destroy_socket(data); + return -err; + } + + debug("sk %d", sk); + + interface = connman_inet_ifname(data->index); + if (interface) { + if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, + interface, + strlen(interface) + 1) < 0) { + err = errno; + connman_error("Failed to bind server %s " + "to interface %s", + data->server, interface); + close(sk); + server_destroy_socket(data); + g_free(interface); + return -err; + } + g_free(interface); + } + + data->channel = g_io_channel_unix_new(sk); + if (!data->channel) { + connman_error("Failed to create server %s channel", + data->server); + close(sk); + server_destroy_socket(data); + return -ENOMEM; + } + + g_io_channel_set_close_on_unref(data->channel, TRUE); + + if (data->protocol == IPPROTO_TCP) { + g_io_channel_set_flags(data->channel, G_IO_FLAG_NONBLOCK, NULL); + data->watch = g_io_add_watch(data->channel, + G_IO_OUT | G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR, + tcp_server_event, data); + data->timeout = g_timeout_add_seconds(30, tcp_idle_timeout, + data); + } else + data->watch = g_io_add_watch(data->channel, + G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, + udp_server_event, data); + + if (connect(sk, data->server_addr, data->server_addr_len) < 0) { + err = errno; + + if ((data->protocol == IPPROTO_TCP && errno != EINPROGRESS) || + data->protocol == IPPROTO_UDP) { + + connman_error("Failed to connect to server %s", + data->server); + server_destroy_socket(data); + return -err; + } + } + + create_cache(); + + return 0; +} + +static void enable_fallback(bool enable) +{ + GSList *list; + + for (list = server_list; list; list = list->next) { + struct server_data *data = list->data; + + if (data->index != -1) + continue; + + if (enable) + DBG("Enabling fallback DNS server %s", data->server); + else + DBG("Disabling fallback DNS server %s", data->server); + + data->enabled = enable; + } +} + +#if defined TIZEN_EXT + +static void destroy_server_sec(struct server_data *server) +{ + GList *list; + int fd; + + if (server->channel) + fd = g_io_channel_unix_get_fd(server->channel); + else + fd = -1; + + DBG("index %d server %s sock %d", server->index, server->server, fd); + + server_list_sec = g_slist_remove(server_list_sec, server); + + if (fd > 0) + close(fd); + + server_destroy_socket(server); + + if (server->protocol == IPPROTO_UDP && server->enabled) + DBG("Removing DNS server %s", server->server); + + g_free(server->server); + for (list = server->domains; list; list = list->next) { + char *domain = list->data; + + server->domains = g_list_remove(server->domains, domain); + g_free(domain); + } + g_free(server->server_addr); + + /* + * 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. + */ + /* TODO: Need to check this */ + /* g_timeout_add_seconds(3, try_remove_cache, NULL); */ + + g_free(server); +} + +static void destroy_all_server_sec() +{ + GSList *list; + + DBG("remove all dns server"); + + for (list = server_list_sec; list; list = list->next) { + struct server_data *server = list->data; + destroy_server_sec(server); + } + server_list_sec = NULL; +} + +static gboolean sec_udp_idle_timeout(gpointer user_data) +{ + struct server_data *server = user_data; + DBG(""); if (server == NULL) return FALSE; - destroy_server(server); + destroy_server_sec(server); + + return FALSE; +} + +static struct server_data *create_server_sec(int index, + const char *domain, const char *server, + int protocol) +{ + struct server_data *data; + struct addrinfo hints, *rp; + int ret; + + DBG("index %d server %s", index, server); + + data = g_try_new0(struct server_data, 1); + if (data == NULL) { + connman_error("Failed to allocate server %s data", server); + return NULL; + } + + data->index = index; + if (domain) + data->domains = g_list_append(data->domains, g_strdup(domain)); + data->server = g_strdup(server); + data->protocol = protocol; + + memset(&hints, 0, sizeof(hints)); + + switch (protocol) { + case IPPROTO_UDP: + hints.ai_socktype = SOCK_DGRAM; + break; + + case IPPROTO_TCP: + hints.ai_socktype = SOCK_STREAM; + break; + + default: + destroy_server_sec(data); + return NULL; + } + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST; + + ret = getaddrinfo(data->server, "53", &hints, &rp); + if (ret) { + connman_error("Failed to parse server %s address: %s\n", + data->server, gai_strerror(ret)); + freeaddrinfo(rp); + destroy_server_sec(data); + return NULL; + } + + /* Do not blindly copy this code elsewhere; it doesn't loop over the + results using ->ai_next as it should. That's OK in *this* case + because it was a numeric lookup; we *know* there's only one. */ + + data->server_addr_len = rp->ai_addrlen; + + switch (rp->ai_family) { + case AF_INET: + data->server_addr = (struct sockaddr *) + g_try_new0(struct sockaddr_in, 1); + break; + case AF_INET6: + data->server_addr = (struct sockaddr *) + g_try_new0(struct sockaddr_in6, 1); + break; + default: + connman_error("Wrong address family %d", rp->ai_family); + break; + } + if (data->server_addr == NULL) { + freeaddrinfo(rp); + destroy_server_sec(data); + return NULL; + } + memcpy(data->server_addr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(rp); + + if (server_create_socket(data) != 0) { + destroy_server_sec(data); + return NULL; + } + + if (protocol == IPPROTO_UDP) { + /* Enable new servers by default */ + data->enabled = TRUE; + DBG("Adding DNS server %s", data->server); + + data->timeout = g_timeout_add_seconds(30, sec_udp_idle_timeout, + data); + + server_list_sec = g_slist_append(server_list_sec, data); + } - return FALSE; + return data; } +#endif -static struct server_data *create_server(const char *interface, +static struct server_data *create_server(int index, const char *domain, const char *server, int protocol) { - struct addrinfo hints, *rp; struct server_data *data; - int sk, ret; + struct addrinfo hints, *rp; + int ret; + + DBG("index %d server %s", index, server); + + data = g_try_new0(struct server_data, 1); + if (!data) { + connman_error("Failed to allocate server %s data", server); + return NULL; + } - DBG("interface %s server %s", interface, server); + data->index = index; + if (domain) + data->domains = g_list_append(data->domains, g_strdup(domain)); + data->server = g_strdup(server); + data->protocol = protocol; memset(&hints, 0, sizeof(hints)); @@ -756,121 +2819,69 @@ static struct server_data *create_server(const char *interface, break; default: + destroy_server(data); return NULL; } hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_NUMERICHOST; + hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST; - ret = getaddrinfo(server, "53", &hints, &rp); + ret = getaddrinfo(data->server, "53", &hints, &rp); if (ret) { connman_error("Failed to parse server %s address: %s\n", - server, gai_strerror(ret)); + data->server, gai_strerror(ret)); + destroy_server(data); return NULL; } + /* Do not blindly copy this code elsewhere; it doesn't loop over the results using ->ai_next as it should. That's OK in *this* case because it was a numeric lookup; we *know* there's only one. */ - sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (sk < 0) { - connman_error("Failed to create server %s socket", server); - freeaddrinfo(rp); - return NULL; - } + data->server_addr_len = rp->ai_addrlen; - if (interface != NULL) { - if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, - interface, strlen(interface) + 1) < 0) { - connman_error("Failed to bind server %s " - "to interface %s", - server, interface); - freeaddrinfo(rp); - close(sk); - return NULL; - } + switch (rp->ai_family) { + case AF_INET: + data->server_addr = (struct sockaddr *) + g_try_new0(struct sockaddr_in, 1); + break; + case AF_INET6: + data->server_addr = (struct sockaddr *) + g_try_new0(struct sockaddr_in6, 1); + break; + default: + connman_error("Wrong address family %d", rp->ai_family); + break; } - - data = g_try_new0(struct server_data, 1); - if (data == NULL) { - connman_error("Failed to allocate server %s data", server); + if (!data->server_addr) { freeaddrinfo(rp); - close(sk); + destroy_server(data); return NULL; } + memcpy(data->server_addr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(rp); - data->channel = g_io_channel_unix_new(sk); - if (data->channel == NULL) { - connman_error("Failed to create server %s channel", server); - freeaddrinfo(rp); - close(sk); - g_free(data); + if (server_create_socket(data) != 0) { + destroy_server(data); return NULL; } - g_io_channel_set_close_on_unref(data->channel, TRUE); - - if (protocol == IPPROTO_TCP) { - g_io_channel_set_flags(data->channel, G_IO_FLAG_NONBLOCK, NULL); - data->watch = g_io_add_watch(data->channel, - G_IO_OUT | G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR, - tcp_server_event, data); - data->timeout = g_timeout_add_seconds(30, tcp_idle_timeout, - data); - } else - data->watch = g_io_add_watch(data->channel, - G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, - udp_server_event, data); - - data->interface = g_strdup(interface); - if (domain) - data->domains = g_list_append(data->domains, g_strdup(domain)); - data->server = g_strdup(server); - data->protocol = protocol; - - ret = connect(sk, rp->ai_addr, rp->ai_addrlen); - freeaddrinfo(rp); - if (ret < 0) { - if ((protocol == IPPROTO_TCP && errno != EINPROGRESS) || - protocol == IPPROTO_UDP) { - GList *list; - - connman_error("Failed to connect to server %s", server); - if (data->watch > 0) - g_source_remove(data->watch); - if (data->timeout > 0) - g_source_remove(data->timeout); - - g_io_channel_unref(data->channel); - close(sk); - - g_free(data->server); - g_free(data->interface); - for (list = data->domains; list; list = list->next) { - char *domain = list->data; + if (protocol == IPPROTO_UDP) { + if (__connman_service_index_is_default(data->index) || + __connman_service_index_is_split_routing( + data->index)) { + data->enabled = true; + DBG("Adding DNS server %s", data->server); - data->domains = g_list_remove(data->domains, - domain); - g_free(domain); - } - g_free(data); - return NULL; + enable_fallback(false); } - } - - if (protocol == IPPROTO_UDP) { - /* Enable new servers by default */ - data->enabled = TRUE; - connman_info("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, +static bool resolv(struct request_data *req, gpointer request, gpointer name) { GSList *list; @@ -878,42 +2889,49 @@ static gboolean resolv(struct request_data *req, for (list = server_list; list; list = list->next) { struct server_data *data = list->data; - DBG("server %s enabled %d", data->server, data->enabled); - - if (data->enabled == FALSE) + if (data->protocol == IPPROTO_TCP) { + DBG("server %s ignored proto TCP", data->server); continue; + } - if (data->watch == 0 && data->protocol == IPPROTO_UDP) - data->watch = g_io_add_watch(data->channel, - G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, - udp_server_event, data); + debug("server %s enabled %d", data->server, data->enabled); - if (ns_resolv(data, req, request, name) < 0) + if (!data->enabled) continue; + + if (!data->channel && data->protocol == IPPROTO_UDP) { + if (server_create_socket(data) < 0) { + DBG("socket creation failed while resolving"); + continue; + } + } + + if (ns_resolv(data, req, request, name) > 0) + return true; } - return TRUE; + return false; } -static void append_domain(const char *interface, const char *domain) +static void update_domain(int index, const char *domain, bool append) { GSList *list; - DBG("interface %s domain %s", interface, domain); + DBG("index %d domain %s", index, domain); - if (domain == NULL) + if (!domain) return; for (list = server_list; list; list = list->next) { struct server_data *data = list->data; GList *dom_list; char *dom; - gboolean dom_found = FALSE; + bool dom_found = false; - if (data->interface == NULL) + if (data->index < 0) continue; - if (g_str_equal(data->interface, interface) == FALSE) + if (data->index != index) continue; for (dom_list = data->domains; dom_list; @@ -921,98 +2939,150 @@ static void append_domain(const char *interface, const char *domain) dom = dom_list->data; if (g_str_equal(dom, domain)) { - dom_found = TRUE; + dom_found = true; break; } } - if (dom_found == FALSE) { + if (!dom_found && append) { data->domains = g_list_append(data->domains, g_strdup(domain)); + } else if (dom_found && !append) { + data->domains = + g_list_remove(data->domains, dom); + g_free(dom); + } + } +} + +static void append_domain(int index, const char *domain) +{ + update_domain(index, domain, true); +} + +static void remove_domain(int index, const char *domain) +{ + update_domain(index, domain, false); +} + +static void flush_requests(struct server_data *server) +{ + GSList *list; + + list = request_list; + while (list) { + struct request_data *req = list->data; + + list = list->next; + + if (ns_resolv(server, req, req->request, req->name)) { + /* + * 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); } } -int __connman_dnsproxy_append(const char *interface, const char *domain, +int __connman_dnsproxy_append(int index, const char *domain, const char *server) { struct server_data *data; - DBG("interface %s server %s", interface, server); + DBG("index %d server %s", index, server); - if (server == NULL && domain == NULL) + if (!server && !domain) return -EINVAL; - if (server == NULL) { - append_domain(interface, domain); + if (!server) { + append_domain(index, domain); return 0; } - if (g_str_equal(server, "127.0.0.1") == TRUE) + if (g_str_equal(server, "127.0.0.1")) return -ENODEV; - data = find_server(interface, server, IPPROTO_UDP); - if (data != NULL) { - append_domain(interface, domain); + if (g_str_equal(server, "::1")) + return -ENODEV; + + data = find_server(index, server, IPPROTO_UDP); + if (data) { + append_domain(index, domain); return 0; } - data = create_server(interface, domain, server, IPPROTO_UDP); - if (data == NULL) + data = create_server(index, domain, server, IPPROTO_UDP); + if (!data) return -EIO; + flush_requests(data); + return 0; } -static void remove_server(const char *interface, const char *domain, +static void remove_server(int index, const char *domain, const char *server, int protocol) { struct server_data *data; + GSList *list; - data = find_server(interface, server, protocol); - if (data == NULL) + data = find_server(index, server, protocol); + if (!data) return; destroy_server(data); + + for (list = server_list; list; list = list->next) { + struct server_data *data = list->data; + + if (data->index != -1 && data->enabled == true) + return; + } + + enable_fallback(true); } -int __connman_dnsproxy_remove(const char *interface, const char *domain, +int __connman_dnsproxy_remove(int index, const char *domain, const char *server) { - DBG("interface %s server %s", interface, server); + DBG("index %d server %s", index, server); - if (server == NULL) + if (!server && !domain) return -EINVAL; - if (g_str_equal(server, "127.0.0.1") == TRUE) - return -ENODEV; + if (!server) { + remove_domain(index, domain); - remove_server(interface, domain, server, IPPROTO_UDP); - remove_server(interface, domain, server, IPPROTO_TCP); + return 0; + } - return 0; -} + if (g_str_equal(server, "127.0.0.1")) + return -ENODEV; -void __connman_dnsproxy_flush(void) -{ - GSList *list; + if (g_str_equal(server, "::1")) + return -ENODEV; - list = request_pending_list; - while (list) { - struct request_data *req = list->data; + remove_server(index, domain, server, IPPROTO_UDP); + remove_server(index, domain, server, IPPROTO_TCP); - list = list->next; +#if defined TIZEN_EXT + destroy_all_server_sec(); +#endif - request_pending_list = - g_slist_remove(request_pending_list, req); - resolv(req, req->request, req->name); - g_free(req->request); - g_free(req->name); - } + return 0; } -static void dnsproxy_offline_mode(connman_bool_t enabled) +static void dnsproxy_offline_mode(bool enabled) { GSList *list; @@ -1021,333 +3091,781 @@ static void dnsproxy_offline_mode(connman_bool_t enabled) for (list = server_list; list; list = list->next) { struct server_data *data = list->data; - if (enabled == FALSE) { - connman_info("Enabling DNS server %s", data->server); - data->enabled = TRUE; + if (!enabled) { + DBG("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; + DBG("Disabling DNS server %s", data->server); + data->enabled = false; + cache_invalidate(); } } } static void dnsproxy_default_changed(struct connman_service *service) { + bool server_enabled = false; GSList *list; - char *interface; + int index; DBG("service %p", service); - if (service == NULL) { + /* DNS has changed, invalidate the cache */ + cache_invalidate(); + + if (!service) { /* When no services are active, then disable DNS proxying */ - dnsproxy_offline_mode(TRUE); + dnsproxy_offline_mode(true); return; } - interface = connman_service_get_interface(service); - if (interface == NULL) + index = __connman_service_get_index(service); + if (index < 0) return; for (list = server_list; list; list = list->next) { struct server_data *data = list->data; - if (g_strcmp0(data->interface, interface) == 0) { - connman_info("Enabling DNS server %s", data->server); - data->enabled = TRUE; + if (data->index == index) { + DBG("Enabling DNS server %s", data->server); + data->enabled = true; + server_enabled = true; } else { - connman_info("Disabling DNS server %s", data->server); - data->enabled = FALSE; + DBG("Disabling DNS server %s", data->server); + data->enabled = false; } } - g_free(interface); + if (!server_enabled) + enable_fallback(true); + + cache_refresh(); } -static struct connman_notifier dnsproxy_notifier = { +static const struct connman_notifier dnsproxy_notifier = { .name = "dnsproxy", .default_changed = dnsproxy_default_changed, .offline_mode = dnsproxy_offline_mode, }; -static unsigned char opt_edns0_type[2] = { 0x00, 0x29 }; +static const unsigned char opt_edns0_type[2] = { 0x00, 0x29 }; -static int parse_request(unsigned char *buf, int len, +static int parse_request(unsigned char *buf, size_t len, char *name, unsigned int size) { struct domain_hdr *hdr = (void *) buf; uint16_t qdcount = ntohs(hdr->qdcount); + uint16_t ancount = ntohs(hdr->ancount); + uint16_t nscount = ntohs(hdr->nscount); uint16_t arcount = ntohs(hdr->arcount); unsigned char *ptr; - char *last_label = NULL; unsigned int remain, used = 0; - if (len < 12) + if (len < sizeof(*hdr) + sizeof(struct qtype_qclass) || + hdr->qr || qdcount != 1 || ancount || nscount) { + DBG("Dropped DNS request qr %d with len %zd qdcount %d " + "ancount %d nscount %d", hdr->qr, len, qdcount, ancount, + nscount); + + return -EINVAL; + } + + if (!name || !size) return -EINVAL; - DBG("id 0x%04x qr %d opcode %d qdcount %d arcount %d", + debug("id 0x%04x qr %d opcode %d qdcount %d arcount %d", hdr->id, hdr->qr, hdr->opcode, qdcount, arcount); - if (hdr->qr != 0 || qdcount != 1) - return -EINVAL; + name[0] = '\0'; + + ptr = buf + sizeof(struct domain_hdr); + remain = len - sizeof(struct domain_hdr); + + while (remain > 0) { + uint8_t label_len = *ptr; + + if (label_len == 0x00) { + uint8_t class; + struct qtype_qclass *q = + (struct qtype_qclass *)(ptr + 1); + + if (remain < sizeof(*q)) { + DBG("Dropped malformed DNS query"); + return -EINVAL; + } + + class = ntohs(q->qclass); + if (class != 1 && class != 255) { + DBG("Dropped non-IN DNS class %d", class); + return -EINVAL; + } + + ptr += sizeof(*q) + 1; + remain -= (sizeof(*q) + 1); + break; + } + + if (used + label_len + 1 > size) + return -ENOBUFS; + + strncat(name, (char *) (ptr + 1), label_len); + strcat(name, "."); + + used += label_len + 1; + + ptr += label_len + 1; + remain -= label_len + 1; + } + + if (arcount && remain >= sizeof(struct domain_rr) + 1 && !ptr[0] && + ptr[1] == opt_edns0_type[0] && ptr[2] == opt_edns0_type[1]) { + struct domain_rr *edns0 = (struct domain_rr *)(ptr + 1); + + DBG("EDNS0 buffer size %u", ntohs(edns0->class)); + } else if (!arcount && remain) { + DBG("DNS request with %d garbage bytes", remain); + } + + debug("query %s", name); + + return 0; +} + +static void client_reset(struct tcp_partial_client_data *client) +{ + if (!client) + return; + + if (client->channel) { + debug("client %d closing", + g_io_channel_unix_get_fd(client->channel)); + + g_io_channel_unref(client->channel); + client->channel = NULL; + } + + if (client->watch > 0) { + g_source_remove(client->watch); + client->watch = 0; + } + + if (client->timeout > 0) { + g_source_remove(client->timeout); + client->timeout = 0; + } + + g_free(client->buf); + client->buf = NULL; + + client->buf_end = 0; +} + +static unsigned int get_msg_len(unsigned char *buf) +{ + return buf[0]<<8 | buf[1]; +} + +static bool read_tcp_data(struct tcp_partial_client_data *client, + void *client_addr, socklen_t client_addr_len, + int read_len) +{ + char query[TCP_MAX_BUF_LEN]; + struct request_data *req; + int client_sk, err; + unsigned int msg_len; + GSList *list; + bool waiting_for_connect = false; + int qtype = 0; + struct cache_entry *entry; + + client_sk = g_io_channel_unix_get_fd(client->channel); + + if (read_len == 0) { + debug("client %d closed, pending %d bytes", + client_sk, client->buf_end); + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + return false; + } + + debug("client %d received %d bytes", client_sk, read_len); + + client->buf_end += read_len; + + if (client->buf_end < 2) + return true; + + msg_len = get_msg_len(client->buf); + if (msg_len > TCP_MAX_BUF_LEN) { + debug("client %d sent too much data %d", client_sk, msg_len); + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + return false; + } + +read_another: + debug("client %d msg len %d end %d past end %d", client_sk, msg_len, + client->buf_end, client->buf_end - (msg_len + 2)); + + if (client->buf_end < (msg_len + 2)) { + debug("client %d still missing %d bytes", + client_sk, + msg_len + 2 - client->buf_end); + return true; + } + + debug("client %d all data %d received", client_sk, msg_len); + + err = parse_request(client->buf + 2, msg_len, + query, sizeof(query)); + if (err < 0 || (g_slist_length(server_list) == 0)) { + send_response(client_sk, client->buf, msg_len + 2, + NULL, 0, IPPROTO_TCP); + return true; + } + + req = g_try_new0(struct request_data, 1); + if (!req) + return true; + + memcpy(&req->sa, client_addr, client_addr_len); + req->sa_len = client_addr_len; + req->client_sk = client_sk; + req->protocol = IPPROTO_TCP; + req->family = client->family; + + req->srcid = client->buf[2] | (client->buf[3] << 8); + req->dstid = get_id(); + req->altid = get_id(); + req->request_len = msg_len + 2; + + client->buf[2] = req->dstid & 0xff; + client->buf[3] = req->dstid >> 8; + + req->numserv = 0; + req->ifdata = client->ifdata; + req->append_domain = false; + + /* + * Check if the answer is found in the cache before + * creating sockets to the server. + */ + entry = cache_check(client->buf, &qtype, IPPROTO_TCP); + if (entry) { + int ttl_left = 0; + struct cache_data *data; + + debug("cache hit %s type %s", query, qtype == 1 ? "A" : "AAAA"); + if (qtype == 1) + data = entry->ipv4; + else + data = entry->ipv6; + + if (data) { + ttl_left = data->valid_until - time(NULL); + entry->hits++; + + send_cached_response(client_sk, data->data, + data->data_len, NULL, 0, IPPROTO_TCP, + req->srcid, data->answers, ttl_left); + + g_free(req); + goto out; + } else + debug("data missing, ignoring cache for this query"); + } + + for (list = server_list; list; list = list->next) { + struct server_data *data = list->data; + + if (data->protocol != IPPROTO_UDP || !data->enabled) + continue; + + if (!create_server(data->index, NULL, data->server, + IPPROTO_TCP)) + continue; + + waiting_for_connect = true; + } + + if (!waiting_for_connect) { + /* No server is waiting for connect */ + send_response(client_sk, client->buf, + req->request_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) { + send_response(client_sk, client->buf, + req->request_len, NULL, 0, IPPROTO_TCP); + g_free(req); + goto out; + } + memcpy(req->request, client->buf, req->request_len); + + req->name = g_try_malloc0(sizeof(query)); + if (!req->name) { + send_response(client_sk, client->buf, + req->request_len, NULL, 0, IPPROTO_TCP); + g_free(req->request); + g_free(req); + goto out; + } + memcpy(req->name, query, sizeof(query)); + + req->timeout = g_timeout_add_seconds(30, request_timeout, req); + + request_list = g_slist_append(request_list, req); + +out: + if (client->buf_end > (msg_len + 2)) { + debug("client %d buf %p -> %p end %d len %d new %d", + client_sk, + client->buf + msg_len + 2, + client->buf, client->buf_end, + TCP_MAX_BUF_LEN - client->buf_end, + client->buf_end - (msg_len + 2)); + memmove(client->buf, client->buf + msg_len + 2, + TCP_MAX_BUF_LEN - client->buf_end); + client->buf_end = client->buf_end - (msg_len + 2); + + /* + * If we have a full message waiting, just read it + * immediately. + */ + msg_len = get_msg_len(client->buf); + if ((msg_len + 2) == client->buf_end) { + debug("client %d reading another %d bytes", client_sk, + msg_len + 2); + goto read_another; + } + } else { + debug("client %d clearing reading buffer", client_sk); + + client->buf_end = 0; + memset(client->buf, 0, TCP_MAX_BUF_LEN); + + /* + * We received all the packets from client so we must also + * remove the timeout handler here otherwise we might get + * timeout while waiting the results from server. + */ + g_source_remove(client->timeout); + client->timeout = 0; + } + + return true; +} + +static gboolean tcp_client_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct tcp_partial_client_data *client = user_data; + struct sockaddr_in6 client_addr6; + socklen_t client_addr6_len = sizeof(client_addr6); + struct sockaddr_in client_addr4; + socklen_t client_addr4_len = sizeof(client_addr4); + void *client_addr; + socklen_t *client_addr_len; + int len, client_sk; + + client_sk = g_io_channel_unix_get_fd(channel); + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); - memset(name, 0, size); + connman_error("Error with TCP client %d channel", client_sk); + return FALSE; + } - ptr = buf + sizeof(struct domain_hdr); - remain = len - sizeof(struct domain_hdr); + switch (client->family) { + case AF_INET: + client_addr = &client_addr4; + client_addr_len = &client_addr4_len; + break; + case AF_INET6: + client_addr = &client_addr6; + client_addr_len = &client_addr6_len; + break; + default: + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + connman_error("client %p corrupted", client); + return FALSE; + } - while (remain > 0) { - uint8_t len = *ptr; + len = recvfrom(client_sk, client->buf + client->buf_end, + TCP_MAX_BUF_LEN - client->buf_end, 0, + client_addr, client_addr_len); + if (len < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return TRUE; + + debug("client %d cannot read errno %d/%s", client_sk, -errno, + strerror(errno)); + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + return FALSE; + } - if (len == 0x00) { - last_label = (char *) (ptr + 1); - break; - } + return read_tcp_data(client, client_addr, *client_addr_len, len); +} - if (used + len + 1 > size) - return -ENOBUFS; +static gboolean client_timeout(gpointer user_data) +{ + struct tcp_partial_client_data *client = user_data; + int sock; - strncat(name, (char *) (ptr + 1), len); - strcat(name, "."); + sock = g_io_channel_unix_get_fd(client->channel); - used += len + 1; + debug("client %d timeout pending %d bytes", sock, client->buf_end); - ptr += len + 1; - remain -= len + 1; - } + g_hash_table_remove(partial_tcp_req_table, GINT_TO_POINTER(sock)); - if (last_label && arcount && remain >= 9 && last_label[4] == 0 && - !memcmp(last_label + 5, opt_edns0_type, 2)) { - uint16_t edns0_bufsize; + return FALSE; +} - edns0_bufsize = last_label[7] << 8 | last_label[8]; +#if defined TIZEN_EXT +static void recover_listener(GIOChannel *channel, struct listener_data *ifdata) +{ + int sk, index; - DBG("EDNS0 buffer size %u", edns0_bufsize); + index = ifdata->index; - /* This is an evil hack until full TCP support has been - * implemented. - * - * Somtimes the EDNS0 request gets send with a too-small - * buffer size. Since glibc doesn't seem to crash when it - * gets a response biffer then it requested, just bump - * the buffer size up to 4KiB. - */ - if (edns0_bufsize < 0x1000) { - last_label[7] = 0x10; - last_label[8] = 0x00; - } - } + sk = g_io_channel_unix_get_fd(channel); + close(sk); - DBG("query %s", name); + __connman_dnsproxy_remove_listener(index); - return 0; + if (__connman_dnsproxy_add_listener(index) == 0) + DBG("listener %d successfully recovered", index); } +#endif -static gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition, - gpointer user_data) +static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, + struct listener_data *ifdata, int family, + guint *listener_watch) { - 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; - - DBG("condition 0x%x", condition); + int sk, client_sk, len; + unsigned int msg_len; + struct tcp_partial_client_data *client; + struct sockaddr_in6 client_addr6; + socklen_t client_addr6_len = sizeof(client_addr6); + struct sockaddr_in client_addr4; + socklen_t client_addr4_len = sizeof(client_addr4); + void *client_addr; + socklen_t *client_addr_len; + struct timeval tv; + fd_set readfds; + + debug("condition 0x%02x channel %p ifdata %p family %d", + condition, channel, ifdata, family); if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { - if (ifdata->tcp_listener_watch > 0) - g_source_remove(ifdata->tcp_listener_watch); - ifdata->tcp_listener_watch = 0; +#if defined TIZEN_EXT + connman_error("Error %d with TCP listener channel", condition); + + recover_listener(channel, ifdata); +#else + if (*listener_watch > 0) + g_source_remove(*listener_watch); + *listener_watch = 0; connman_error("Error with TCP listener channel"); +#endif - return FALSE; + return false; } sk = g_io_channel_unix_get_fd(channel); - client_sk = accept(sk, (void *)&client_addr, &client_addr_len); - if (client_sk < 0) { - connman_error("Accept failure on TCP listener"); - ifdata->tcp_listener_watch = 0; - return FALSE; + if (family == AF_INET) { + client_addr = &client_addr4; + client_addr_len = &client_addr4_len; + } else { + client_addr = &client_addr6; + client_addr_len = &client_addr6_len; } - len = recv(client_sk, buf, sizeof(buf), 0); - if (len < 2) - return TRUE; - - DBG("Received %d bytes (id 0x%04x)", len, buf[2] | buf[3] << 8); + tv.tv_sec = tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(sk, &readfds); - err = parse_request(buf + 2, len - 2, query, sizeof(query)); - if (err < 0 || (g_slist_length(server_list) == 0)) { - send_response(client_sk, buf, len, NULL, 0, IPPROTO_TCP); - return TRUE; + select(sk + 1, &readfds, NULL, NULL, &tv); + if (FD_ISSET(sk, &readfds)) { + client_sk = accept(sk, client_addr, client_addr_len); + debug("client %d accepted", client_sk); + } else { + debug("No data to read from master %d, waiting.", sk); + return true; } - req = g_try_new0(struct request_data, 1); - if (req == NULL) - return TRUE; + if (client_sk < 0) { + connman_error("Accept failure on TCP listener"); + *listener_watch = 0; + return false; + } - memcpy(&req->sa, &client_addr, client_addr_len); - req->sa_len = client_addr_len; - req->client_sk = client_sk; - req->protocol = IPPROTO_TCP; + fcntl(client_sk, F_SETFL, O_NONBLOCK); - request_id += 2; - if (request_id == 0x0000 || request_id == 0xffff) - request_id += 2; + client = g_hash_table_lookup(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + if (!client) { + client = g_try_new0(struct tcp_partial_client_data, 1); + if (!client) { + close(client_sk); + return false; + } - req->srcid = buf[2] | (buf[3] << 8); - req->dstid = request_id; - req->altid = request_id + 1; - req->request_len = len; + g_hash_table_insert(partial_tcp_req_table, + GINT_TO_POINTER(client_sk), + client); - buf[2] = req->dstid & 0xff; - buf[3] = req->dstid >> 8; + client->channel = g_io_channel_unix_new(client_sk); + g_io_channel_set_close_on_unref(client->channel, TRUE); - req->numserv = 0; - req->ifdata = (struct listener_data *) ifdata; - req->append_domain = FALSE; - request_list = g_slist_append(request_list, req); + client->watch = g_io_add_watch(client->channel, + G_IO_IN, tcp_client_event, + (gpointer)client); - for (list = server_list; list; list = list->next) { - struct server_data *data = list->data; - GList *domains; + client->ifdata = ifdata; - if (data->protocol != IPPROTO_UDP || data->enabled == FALSE) - continue; + debug("client %d created %p", client_sk, client); + } else { + debug("client %d already exists %p", client_sk, client); + } - server = create_server(data->interface, NULL, - data->server, IPPROTO_TCP); + if (!client->buf) { + client->buf = g_try_malloc(TCP_MAX_BUF_LEN); + if (!client->buf) + return false; + } + memset(client->buf, 0, TCP_MAX_BUF_LEN); + client->buf_end = 0; + client->family = family; + + if (client->timeout == 0) + client->timeout = g_timeout_add_seconds(2, client_timeout, + client); + + /* + * Check how much data there is. If all is there, then we can + * proceed normally, otherwise read the bits until everything + * is received or timeout occurs. + */ + len = recv(client_sk, client->buf, TCP_MAX_BUF_LEN, 0); + if (len < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + debug("client %d no data to read, waiting", client_sk); + return true; + } - /* - * 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; + debug("client %d cannot read errno %d/%s", client_sk, -errno, + strerror(errno)); + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + return true; + } - memcpy(req->request, buf, req->request_len); + if (len < 2) { + debug("client %d not enough data to read, waiting", client_sk); + client->buf_end += len; + return true; + } - req->name = g_try_malloc0(sizeof(query)); - if (req->name == NULL) { - g_free(req->request); - return TRUE; - } - memcpy(req->name, query, sizeof(query)); + msg_len = get_msg_len(client->buf); + if (msg_len > TCP_MAX_BUF_LEN) { + debug("client %d invalid message length %u ignoring packet", + client_sk, msg_len); + g_hash_table_remove(partial_tcp_req_table, + GINT_TO_POINTER(client_sk)); + return true; + } - continue; - } + /* + * The packet length bytes do not contain the total message length, + * that is the reason to -2 below. + */ +#if defined TIZEN_EXT + if (msg_len > (unsigned int)(len - 2)) { +#else + if (msg_len != (unsigned int)(len - 2)) { +#endif + debug("client %d sent %d bytes but expecting %u pending %d", + client_sk, len, msg_len + 2, msg_len + 2 - len); - if (req->timeout > 0) - g_source_remove(req->timeout); + client->buf_end += len; + return true; + } - for (domains = data->domains; domains; - domains = domains->next) { - char *dom = domains->data; + return read_tcp_data(client, client_addr, *client_addr_len, len); +} - DBG("Adding domain %s to %s", dom, server->server); +static gboolean tcp4_listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct listener_data *ifdata = user_data; - server->domains = g_list_append(server->domains, - g_strdup(dom)); - } + return tcp_listener_event(channel, condition, ifdata, AF_INET, + &ifdata->tcp4_listener_watch); +} - req->timeout = g_timeout_add_seconds(30, request_timeout, req); - ns_resolv(server, req, buf, query); - } +static gboolean tcp6_listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct listener_data *ifdata = user_data; - return TRUE; + return tcp_listener_event(channel, condition, user_data, AF_INET6, + &ifdata->tcp6_listener_watch); } -static gboolean udp_listener_event(GIOChannel *channel, GIOCondition condition, - gpointer user_data) +static bool udp_listener_event(GIOChannel *channel, GIOCondition condition, + struct listener_data *ifdata, int family, + guint *listener_watch) { unsigned char buf[768]; char query[512]; struct request_data *req; - struct sockaddr_in6 client_addr; - socklen_t client_addr_len = sizeof(client_addr); + struct sockaddr_in6 client_addr6; + socklen_t client_addr6_len = sizeof(client_addr6); + struct sockaddr_in client_addr4; + socklen_t client_addr4_len = sizeof(client_addr4); + void *client_addr; + socklen_t *client_addr_len; int sk, err, len; - struct listener_data *ifdata = user_data; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { +#if defined TIZEN_EXT + connman_error("Error %d with UDP listener channel", condition); + + recover_listener(channel, ifdata); +#else connman_error("Error with UDP listener channel"); - ifdata->udp_listener_watch = 0; - return FALSE; + *listener_watch = 0; +#endif + return false; } sk = g_io_channel_unix_get_fd(channel); - memset(&client_addr, 0, client_addr_len); - len = recvfrom(sk, buf, sizeof(buf), 0, (void *)&client_addr, - &client_addr_len); + if (family == AF_INET) { + client_addr = &client_addr4; + client_addr_len = &client_addr4_len; + } else { + client_addr = &client_addr6; + client_addr_len = &client_addr6_len; + } + + memset(client_addr, 0, *client_addr_len); + len = recvfrom(sk, buf, sizeof(buf), 0, client_addr, client_addr_len); if (len < 2) - return TRUE; + return true; - DBG("Received %d bytes (id 0x%04x)", len, buf[0] | buf[1] << 8); + debug("Received %d bytes (id 0x%04x)", len, buf[0] | buf[1] << 8); err = parse_request(buf, len, query, sizeof(query)); if (err < 0 || (g_slist_length(server_list) == 0)) { - send_response(sk, buf, len, (void *)&client_addr, - client_addr_len, IPPROTO_UDP); - return TRUE; + send_response(sk, buf, len, client_addr, + *client_addr_len, IPPROTO_UDP); + return true; } req = g_try_new0(struct request_data, 1); - if (req == NULL) - return TRUE; + if (!req) + return true; - memcpy(&req->sa, &client_addr, client_addr_len); - req->sa_len = client_addr_len; + memcpy(&req->sa, client_addr, *client_addr_len); + req->sa_len = *client_addr_len; req->client_sk = 0; req->protocol = IPPROTO_UDP; - - request_id += 2; - if (request_id == 0x0000 || request_id == 0xffff) - request_id += 2; + req->family = family; 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; buf[1] = req->dstid >> 8; req->numserv = 0; - req->ifdata = (struct listener_data *) ifdata; + req->ifdata = ifdata; + req->append_domain = false; + + if (resolv(req, buf, query)) { + /* a cached result was sent, so the request can be released */ + g_free(req); + return true; + } + + req->name = g_strdup(query); + req->request = g_malloc(len); + memcpy(req->request, buf, len); +#if defined TIZEN_EXT + DBG("req %p dstid 0x%04x altid 0x%04x", req, req->dstid, req->altid); + req->timeout = g_timeout_add_seconds(30, request_timeout, req); +#else req->timeout = g_timeout_add_seconds(5, request_timeout, req); - req->append_domain = FALSE; +#endif 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) +static gboolean udp4_listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct listener_data *ifdata = user_data; + + return udp_listener_event(channel, condition, ifdata, AF_INET, + &ifdata->udp4_listener_watch); +} + +static gboolean udp6_listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct listener_data *ifdata = user_data; + + return udp_listener_event(channel, condition, user_data, AF_INET6, + &ifdata->udp6_listener_watch); +} + +static GIOChannel *get_listener(int family, int protocol, int index) { GIOChannel *channel; const char *proto; +#if !defined TIZEN_EXT union { struct sockaddr sa; struct sockaddr_in6 sin6; struct sockaddr_in sin; } s; socklen_t slen; - int sk, type, v6only = 0; - int family = AF_INET6; - +#endif + int sk, type; +#if !defined TIZEN_EXT + char *interface; +#endif +#if defined TIZEN_EXT + int option; + int sd_num = 0; + int rv; + int is_socket_inet = 0; +#endif - DBG("interface %s", ifdata->ifname); + debug("family %d protocol %d index %d", family, protocol, index); switch (protocol) { case IPPROTO_UDP: @@ -1361,158 +3879,299 @@ static int create_dns_listener(int protocol, struct listener_data *ifdata) break; default: - return -EINVAL; + return NULL; + } +#if defined TIZEN_EXT + sd_num = sd_listen_fds(0); + DBG("socket type(%s) systemd number of fds(%d)", proto, sd_num); + if(sd_num < 1){ + DBG("fail to get the fd from systemd"); + return NULL; + } + + if(protocol == IPPROTO_TCP) + type = SOCK_STREAM; + else + type = SOCK_DGRAM; + + for(sk = SD_LISTEN_FDS_START; sk < SD_LISTEN_FDS_START+sd_num; ++sk){ + rv = sd_is_socket_inet(sk, family, type, -1, 53); + if(rv > 0){ + DBG("socket fd (%d) is passed by systemd", sk); + is_socket_inet = 1; + break; + } } + if (!is_socket_inet) { + DBG("socket fd is not matched what connman requests"); + return NULL; + } +#else sk = socket(family, type, protocol); if (sk < 0 && family == AF_INET6 && errno == EAFNOSUPPORT) { - connman_error("No IPv6 support; DNS proxy listening only on Legacy IP"); - family = AF_INET; - sk = socket(family, type, protocol); + connman_error("No IPv6 support"); + return NULL; } + if (sk < 0) { connman_error("Failed to create %s listener socket", proto); - return -EIO; + return NULL; } - if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, - ifdata->ifname, - strlen(ifdata->ifname) + 1) < 0) { - connman_error("Failed to bind %s listener interface", proto); - close(sk); - return -EIO; - } - /* Ensure it accepts Legacy IP connections too */ - if (family == AF_INET6 && - setsockopt(sk, SOL_IPV6, IPV6_V6ONLY, - &v6only, sizeof(v6only)) < 0) { - connman_error("Failed to clear V6ONLY on %s listener socket", - proto); + interface = connman_inet_ifname(index); + if (!interface || setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, + interface, + strlen(interface) + 1) < 0) { + connman_error("Failed to bind %s listener interface " + "for %s (%d/%s)", + proto, family == AF_INET ? "IPv4" : "IPv6", + -errno, strerror(errno)); close(sk); - return -EIO; + g_free(interface); + return NULL; } + g_free(interface); - if (family == AF_INET) { + if (family == AF_INET6) { + memset(&s.sin6, 0, sizeof(s.sin6)); + s.sin6.sin6_family = AF_INET6; + s.sin6.sin6_port = htons(53); + slen = sizeof(s.sin6); + + if (__connman_inet_get_interface_address(index, + AF_INET6, + &s.sin6.sin6_addr) < 0) { + /* So we could not find suitable IPv6 address for + * the interface. This could happen if we have + * disabled IPv6 for the interface. + */ + close(sk); + return NULL; + } + + } else if (family == AF_INET) { memset(&s.sin, 0, sizeof(s.sin)); s.sin.sin_family = AF_INET; s.sin.sin_port = htons(53); - s.sin.sin_addr.s_addr = htonl(INADDR_ANY); slen = sizeof(s.sin); + + if (__connman_inet_get_interface_address(index, + AF_INET, + &s.sin.sin_addr) < 0) { + close(sk); + return NULL; + } } else { - memset(&s.sin6, 0, sizeof(s.sin6)); - s.sin6.sin6_family = AF_INET6; - s.sin6.sin6_port = htons(53); - s.sin6.sin6_addr = in6addr_any; - slen = sizeof(s.sin6); + close(sk); + return NULL; } +#endif +#if defined TIZEN_EXT + /* When ConnMan crashed, + * probably DNS listener cannot bind existing address */ + option = 1; + if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) { + connman_error("Failed to set socket option SO_REUSEADDR"); + close(sk); + return NULL; + } +#endif +#if !defined TIZEN_EXT if (bind(sk, &s.sa, slen) < 0) { connman_error("Failed to bind %s listener socket", proto); close(sk); - return -EIO; + return NULL; } +#endif - if (protocol == IPPROTO_TCP && listen(sk, 10) < 0) { - connman_error("Failed to listen on TCP socket"); - close(sk); - return -EIO; + if (protocol == IPPROTO_TCP) { + +#if !defined TIZEN_EXT + if (listen(sk, 10) < 0) { + connman_error("Failed to listen on TCP socket %d/%s", + -errno, strerror(errno)); + close(sk); + return NULL; + } + +#endif + fcntl(sk, F_SETFL, O_NONBLOCK); } channel = g_io_channel_unix_new(sk); - if (channel == NULL) { + if (!channel) { connman_error("Failed to create %s listener channel", proto); close(sk); - return -EIO; + return NULL; } g_io_channel_set_close_on_unref(channel, TRUE); + return channel; +} + +#define UDP_IPv4_FAILED 0x01 +#define TCP_IPv4_FAILED 0x02 +#define UDP_IPv6_FAILED 0x04 +#define TCP_IPv6_FAILED 0x08 +#define UDP_FAILED (UDP_IPv4_FAILED | UDP_IPv6_FAILED) +#define TCP_FAILED (TCP_IPv4_FAILED | TCP_IPv6_FAILED) +#define IPv6_FAILED (UDP_IPv6_FAILED | TCP_IPv6_FAILED) +#define IPv4_FAILED (UDP_IPv4_FAILED | TCP_IPv4_FAILED) + +static int create_dns_listener(int protocol, struct listener_data *ifdata) +{ + int ret = 0; + if (protocol == IPPROTO_TCP) { - ifdata->tcp_listener_channel = channel; - ifdata->tcp_listener_watch = g_io_add_watch(channel, - G_IO_IN, tcp_listener_event, (gpointer) ifdata); + ifdata->tcp4_listener_channel = get_listener(AF_INET, protocol, + ifdata->index); + if (ifdata->tcp4_listener_channel) +#if defined TIZEN_EXT + ifdata->tcp4_listener_watch = + g_io_add_watch(ifdata->tcp4_listener_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + tcp4_listener_event, (gpointer)ifdata); +#else + ifdata->tcp4_listener_watch = + g_io_add_watch(ifdata->tcp4_listener_channel, + G_IO_IN, tcp4_listener_event, + (gpointer)ifdata); +#endif + else + ret |= TCP_IPv4_FAILED; + + ifdata->tcp6_listener_channel = get_listener(AF_INET6, protocol, + ifdata->index); + if (ifdata->tcp6_listener_channel) +#if defined TIZEN_EXT + ifdata->tcp6_listener_watch = + g_io_add_watch(ifdata->tcp6_listener_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + tcp6_listener_event, (gpointer)ifdata); +#else + ifdata->tcp6_listener_watch = + g_io_add_watch(ifdata->tcp6_listener_channel, + G_IO_IN, tcp6_listener_event, + (gpointer)ifdata); +#endif + else + ret |= TCP_IPv6_FAILED; } else { - ifdata->udp_listener_channel = channel; - ifdata->udp_listener_watch = g_io_add_watch(channel, - G_IO_IN, udp_listener_event, (gpointer) ifdata); + ifdata->udp4_listener_channel = get_listener(AF_INET, protocol, + ifdata->index); + if (ifdata->udp4_listener_channel) +#if defined TIZEN_EXT + ifdata->udp4_listener_watch = + g_io_add_watch(ifdata->udp4_listener_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + udp4_listener_event, (gpointer)ifdata); +#else + ifdata->udp4_listener_watch = + g_io_add_watch(ifdata->udp4_listener_channel, + G_IO_IN, udp4_listener_event, + (gpointer)ifdata); +#endif + else + ret |= UDP_IPv4_FAILED; + + ifdata->udp6_listener_channel = get_listener(AF_INET6, protocol, + ifdata->index); + if (ifdata->udp6_listener_channel) +#if defined TIZEN_EXT + ifdata->udp6_listener_watch = + g_io_add_watch(ifdata->udp6_listener_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + udp6_listener_event, (gpointer)ifdata); +#else + ifdata->udp6_listener_watch = + g_io_add_watch(ifdata->udp6_listener_channel, + G_IO_IN, udp6_listener_event, + (gpointer)ifdata); +#endif + else + ret |= UDP_IPv6_FAILED; } - return 0; + return ret; } static void destroy_udp_listener(struct listener_data *ifdata) { - DBG("interface %s", ifdata->ifname); + DBG("index %d", ifdata->index); - if (ifdata->udp_listener_watch > 0) - g_source_remove(ifdata->udp_listener_watch); + if (ifdata->udp4_listener_watch > 0) + g_source_remove(ifdata->udp4_listener_watch); - g_io_channel_unref(ifdata->udp_listener_channel); + if (ifdata->udp6_listener_watch > 0) + g_source_remove(ifdata->udp6_listener_watch); + + if (ifdata->udp4_listener_channel) + g_io_channel_unref(ifdata->udp4_listener_channel); + if (ifdata->udp6_listener_channel) + g_io_channel_unref(ifdata->udp6_listener_channel); } static void destroy_tcp_listener(struct listener_data *ifdata) { - DBG("interface %s", ifdata->ifname); + DBG("index %d", ifdata->index); - if (ifdata->tcp_listener_watch > 0) - g_source_remove(ifdata->tcp_listener_watch); + if (ifdata->tcp4_listener_watch > 0) + g_source_remove(ifdata->tcp4_listener_watch); + if (ifdata->tcp6_listener_watch > 0) + g_source_remove(ifdata->tcp6_listener_watch); - g_io_channel_unref(ifdata->tcp_listener_channel); + if (ifdata->tcp4_listener_channel) + g_io_channel_unref(ifdata->tcp4_listener_channel); + if (ifdata->tcp6_listener_channel) + g_io_channel_unref(ifdata->tcp6_listener_channel); } static int create_listener(struct listener_data *ifdata) { - int err; + int err, index; err = create_dns_listener(IPPROTO_UDP, ifdata); - if (err < 0) - return err; + if ((err & UDP_FAILED) == UDP_FAILED) + return -EIO; - err = create_dns_listener(IPPROTO_TCP, ifdata); - if (err < 0) { + err |= create_dns_listener(IPPROTO_TCP, ifdata); + if ((err & TCP_FAILED) == TCP_FAILED) { destroy_udp_listener(ifdata); - return err; + return -EIO; } - if (g_strcmp0(ifdata->ifname, "lo") == 0) - __connman_resolvfile_append("lo", NULL, "127.0.0.1"); + index = connman_inet_ifindex("lo"); + if (ifdata->index == index) { + if ((err & IPv6_FAILED) != IPv6_FAILED) + __connman_resolvfile_append(index, NULL, "::1"); + + if ((err & IPv4_FAILED) != IPv4_FAILED) + __connman_resolvfile_append(index, NULL, "127.0.0.1"); + } return 0; } static void destroy_listener(struct listener_data *ifdata) { + int index; GSList *list; - if (g_strcmp0(ifdata->ifname, "lo") == 0) - __connman_resolvfile_remove("lo", NULL, "127.0.0.1"); - - for (list = request_pending_list; list; list = list->next) { - struct request_data *req = list->data; - - DBG("Dropping pending request (id 0x%04x -> 0x%04x)", - req->srcid, req->dstid); - - g_free(req->resp); - g_free(req->request); - g_free(req->name); - g_free(req); - list->data = NULL; + index = connman_inet_ifindex("lo"); + if (ifdata->index == index) { + __connman_resolvfile_remove(index, NULL, "127.0.0.1"); + __connman_resolvfile_remove(index, NULL, "::1"); } - 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)", + debug("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; } @@ -1523,67 +4182,100 @@ static void destroy_listener(struct listener_data *ifdata) destroy_udp_listener(ifdata); } -int __connman_dnsproxy_add_listener(const char *interface) +int __connman_dnsproxy_add_listener(int index) { struct listener_data *ifdata; int err; - DBG("interface %s", interface); + DBG("index %d", index); - if (g_hash_table_lookup(listener_table, interface) != NULL) + if (index < 0) + return -EINVAL; + + if (!listener_table) + return -ENOENT; + + if (g_hash_table_lookup(listener_table, GINT_TO_POINTER(index))) return 0; ifdata = g_try_new0(struct listener_data, 1); - if (ifdata == NULL) + if (!ifdata) return -ENOMEM; - ifdata->ifname = g_strdup(interface); - ifdata->udp_listener_channel = NULL; - ifdata->udp_listener_watch = 0; - ifdata->tcp_listener_channel = NULL; - ifdata->tcp_listener_watch = 0; + ifdata->index = index; + ifdata->udp4_listener_channel = NULL; + ifdata->udp4_listener_watch = 0; + ifdata->tcp4_listener_channel = NULL; + ifdata->tcp4_listener_watch = 0; + ifdata->udp6_listener_channel = NULL; + ifdata->udp6_listener_watch = 0; + ifdata->tcp6_listener_channel = NULL; + ifdata->tcp6_listener_watch = 0; err = create_listener(ifdata); if (err < 0) { - connman_error("Couldn't create listener for %s err %d", - interface, err); - g_free(ifdata->ifname); + connman_error("Couldn't create listener for index %d err %d", + index, err); g_free(ifdata); return err; } - g_hash_table_insert(listener_table, ifdata->ifname, ifdata); + g_hash_table_insert(listener_table, GINT_TO_POINTER(ifdata->index), + ifdata); return 0; } -void __connman_dnsproxy_remove_listener(const char *interface) +void __connman_dnsproxy_remove_listener(int index) { struct listener_data *ifdata; - DBG("interface %s", interface); + DBG("index %d", index); + + if (!listener_table) + return; - ifdata = g_hash_table_lookup(listener_table, interface); - if (ifdata == NULL) + ifdata = g_hash_table_lookup(listener_table, GINT_TO_POINTER(index)); + if (!ifdata) return; destroy_listener(ifdata); - g_hash_table_remove(listener_table, interface); + g_hash_table_remove(listener_table, GINT_TO_POINTER(index)); } static void remove_listener(gpointer key, gpointer value, gpointer user_data) { - __connman_dnsproxy_remove_listener(key); + int index = GPOINTER_TO_INT(key); + struct listener_data *ifdata = value; + + DBG("index %d", index); + + destroy_listener(ifdata); +} + +static void free_partial_reqs(gpointer value) +{ + struct tcp_partial_client_data *data = value; + + client_reset(data); + g_free(data); } int __connman_dnsproxy_init(void) { - int err; + int err, index; DBG(""); - listener_table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - err = __connman_dnsproxy_add_listener("lo"); + listener_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, g_free); + + partial_tcp_req_table = g_hash_table_new_full(g_direct_hash, + g_direct_equal, + NULL, + free_partial_reqs); + + index = connman_inet_ifindex("lo"); + err = __connman_dnsproxy_add_listener(index); if (err < 0) return err; @@ -1594,19 +4286,42 @@ int __connman_dnsproxy_init(void) return 0; destroy: - __connman_dnsproxy_remove_listener("lo"); + __connman_dnsproxy_remove_listener(index); g_hash_table_destroy(listener_table); + g_hash_table_destroy(partial_tcp_req_table); return err; } +int __connman_dnsproxy_set_mdns(int index, bool enabled) +{ + return -ENOTSUP; +} + void __connman_dnsproxy_cleanup(void) { DBG(""); + if (cache_timer) { + g_source_remove(cache_timer); + cache_timer = 0; + } + + if (cache) { + g_hash_table_destroy(cache); + cache = NULL; + } + connman_notifier_unregister(&dnsproxy_notifier); g_hash_table_foreach(listener_table, remove_listener, NULL); g_hash_table_destroy(listener_table); + + g_hash_table_destroy(partial_tcp_req_table); + + if (ipv4_resolve) + g_resolv_unref(ipv4_resolve); + if (ipv6_resolve) + g_resolv_unref(ipv6_resolve); }