X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fdnsproxy.c;h=7956e7fb17bdf335e665bcf441c39f5ffcc80ea0;hb=6d2cf90574c81cd30340116270e17af058703afb;hp=f56c30cb292cab9c53ebdcd3f9d98f98c637809f;hpb=4884c249bf7f098bbffd1a704a0a5ade573bee54;p=platform%2Fupstream%2Fconnman.git diff --git a/src/dnsproxy.c b/src/dnsproxy.c old mode 100644 new mode 100755 index f56c30c..7956e7f --- a/src/dnsproxy.c +++ b/src/dnsproxy.c @@ -2,7 +2,7 @@ * * Connection Manager * - * Copyright (C) 2007-2012 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,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +41,13 @@ #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; @@ -75,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; @@ -82,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; }; @@ -102,6 +118,7 @@ struct request_data { socklen_t sa_len; int client_sk; int protocol; + int family; guint16 srcid; guint16 dstid; guint16 altid; @@ -115,15 +132,35 @@ 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 { @@ -139,7 +176,7 @@ struct cache_data { struct cache_entry { char *key; - int want_refresh; + bool want_refresh; int hits; struct cache_data *ipv4; struct cache_data *ipv6; @@ -158,11 +195,20 @@ struct domain_rr { } __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. */ @@ -181,11 +227,30 @@ 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) { @@ -237,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; } @@ -281,27 +346,27 @@ static void refresh_dns_entry(struct cache_entry *entry, char *name) { int age = 1; - if (ipv4_resolve == NULL) { + 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 == NULL) { + if (!ipv6_resolve) { ipv6_resolve = g_resolv_new(0); g_resolv_set_address_family(ipv6_resolve, AF_INET6); - g_resolv_add_nameserver(ipv6_resolve, "127.0.0.1", 53, 0); + g_resolv_add_nameserver(ipv6_resolve, "::1", 53, 0); } - if (entry->ipv4 == NULL) { - DBG("Refresing A record for %s", name); + 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 == NULL) { - DBG("Refresing AAAA record for %s", name); + if (!entry->ipv6) { + debug("Refreshing AAAA record for %s", name); g_resolv_lookup_hostname(ipv6_resolve, name, dummy_resolve_func, NULL); age = 4; @@ -316,14 +381,13 @@ static int dns_name_length(unsigned char *buf) { if ((buf[0] & NS_CMPRSFLGS) == NS_CMPRSFLGS) /* compressed name */ return 2; - return strlen((char *)buf); + return strlen((char *)buf) + 1; } static void update_cached_ttl(unsigned char *buf, int len, int new_ttl) { unsigned char *c; - uint32_t *i; - uint16_t *w; + uint16_t w; int l; /* skip the header */ @@ -353,17 +417,19 @@ static void update_cached_ttl(unsigned char *buf, int len, int new_ttl) break; /* now the 4 byte TTL field */ - i = (uint32_t *)c; - *i = htonl(new_ttl); + 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 = (uint16_t *)c; - c += ntohs(*w) + 2; - len -= ntohs(*w) + 2; + w = c[0] << 8 | c[1]; + c += w + 2; + len -= w + 2; } } @@ -372,120 +438,194 @@ static void send_cached_response(int sk, unsigned char *buf, int len, int protocol, int id, uint16_t answers, int ttl) { struct domain_hdr *hdr; - int err, offset = protocol_offset(protocol); + unsigned char *ptr = buf; + int err, offset, dns_len, adj_len = len - 2; - if (offset < 0) + /* + * The cached packet contains always the TCP offset (two bytes) + * so skip them for UDP. + */ + switch (protocol) { + case IPPROTO_UDP: + ptr += 2; + len -= 2; + dns_len = len; + offset = 0; + break; + case IPPROTO_TCP: + offset = 2; + dns_len = ptr[0] * 256 + ptr[1]; + break; + default: return; + } if (len < 12) return; - hdr = (void *) (buf + offset); + hdr = (void *) (ptr + offset); hdr->id = id; hdr->qr = 1; - hdr->rcode = 0; + 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 this is a negative reply, we are authoritative */ if (answers == 0) hdr->aa = 1; else - update_cached_ttl(buf, len, ttl); + update_cached_ttl((unsigned char *)hdr, adj_len, ttl); - DBG("id 0x%04x answers %d", hdr->id, answers); + 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, buf, len, 0, to, tolen); + err = sendto(sk, ptr, len, MSG_NOSIGNAL, to, tolen); if (err < 0) { connman_error("Cannot send cached DNS response: %s", strerror(errno)); return; } + + if (err != len || (dns_len != (len - 2) && protocol == IPPROTO_TCP) || + (dns_len != len && protocol == IPPROTO_UDP)) + debug("Packet length mismatch, sent %d wanted %d dns %d", + err, len, dns_len); } -static void send_response(int sk, unsigned char *buf, int 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 !defined TIZEN_EXT + if (offset) { + buf[0] = 0; + buf[1] = sizeof(*hdr); + } +#endif - 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; +#if !defined TIZEN_EXT + hdr->qdcount = 0; +#endif hdr->ancount = 0; hdr->nscount = 0; hdr->arcount = 0; - err = sendto(sk, buf, len, 0, to, tolen); +#if defined TIZEN_EXT + err = sendto(sk, buf, len, MSG_NOSIGNAL, to, tolen); +#else + err = sendto(sk, buf, sizeof(*hdr) + offset, MSG_NOSIGNAL, to, tolen); +#endif 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); + hdr = (void *)(req->request + protocol_offset(req->protocol)); + hdr->id = req->srcid; - } else if (req->protocol == IPPROTO_UDP) { - int sk; - - 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; } @@ -496,13 +636,13 @@ static int append_query(unsigned char *buf, unsigned int size, unsigned char *ptr = buf; int len; - DBG("query %s domain %s", query, domain); + debug("query %s domain %s", query, domain); - while (query != NULL) { + while (query) { const char *tmp; tmp = strchr(query, '.'); - if (tmp == NULL) { + if (!tmp) { len = strlen(query); if (len == 0) break; @@ -519,11 +659,11 @@ static int append_query(unsigned char *buf, unsigned int size, query = tmp + 1; } - while (domain != NULL) { + while (domain) { const char *tmp; tmp = strchr(domain, '.'); - if (tmp == NULL) { + if (!tmp) { len = strlen(domain); if (len == 0) break; @@ -545,16 +685,16 @@ static int append_query(unsigned char *buf, unsigned int size, return ptr - buf; } -static gboolean cache_check_is_valid(struct cache_data *data, +static bool cache_check_is_valid(struct cache_data *data, time_t current_time) { - if (data == NULL) - return FALSE; + if (!data) + return false; if (data->cache_until < current_time) - return FALSE; + return false; - return TRUE; + return true; } /* @@ -564,18 +704,18 @@ static void cache_enforce_validity(struct cache_entry *entry) { time_t current_time = time(NULL); - if (cache_check_is_valid(entry->ipv4, current_time) == FALSE + if (!cache_check_is_valid(entry->ipv4, current_time) && entry->ipv4) { - DBG("cache timeout \"%s\" type A", entry->key); + debug("cache timeout \"%s\" type A", entry->key); g_free(entry->ipv4->data); g_free(entry->ipv4); entry->ipv4 = NULL; } - if (cache_check_is_valid(entry->ipv6, current_time) == FALSE + if (!cache_check_is_valid(entry->ipv6, current_time) && entry->ipv6) { - DBG("cache timeout \"%s\" type AAAA", entry->key); + debug("cache timeout \"%s\" type AAAA", entry->key); g_free(entry->ipv6->data); g_free(entry->ipv6); entry->ipv6 = NULL; @@ -586,32 +726,31 @@ static uint16_t cache_check_validity(char *question, uint16_t type, struct cache_entry *entry) { time_t current_time = time(NULL); - int want_refresh = 0; + bool want_refresh = false; /* * if we have a popular entry, we want a refresh instead of * total destruction of the entry. */ if (entry->hits > 2) - want_refresh = 1; + want_refresh = true; cache_enforce_validity(entry); switch (type) { case 1: /* IPv4 */ - if (cache_check_is_valid(entry->ipv4, current_time) == FALSE) { - DBG("cache %s \"%s\" type A", entry->ipv4 ? + if (!cache_check_is_valid(entry->ipv4, current_time)) { + debug("cache %s \"%s\" type A", entry->ipv4 ? "timeout" : "entry missing", question); if (want_refresh) - entry->want_refresh = 1; + entry->want_refresh = true; /* * We do not remove cache entry if there is still * valid IPv6 entry found in the cache. */ - if (cache_check_is_valid(entry->ipv6, current_time) - == FALSE && want_refresh == FALSE) { + if (!cache_check_is_valid(entry->ipv6, current_time) && !want_refresh) { g_hash_table_remove(cache, question); type = 0; } @@ -619,15 +758,14 @@ static uint16_t cache_check_validity(char *question, uint16_t type, break; case 28: /* IPv6 */ - if (cache_check_is_valid(entry->ipv6, current_time) == FALSE) { - DBG("cache %s \"%s\" type AAAA", entry->ipv6 ? + if (!cache_check_is_valid(entry->ipv6, current_time)) { + debug("cache %s \"%s\" type AAAA", entry->ipv6 ? "timeout" : "entry missing", question); if (want_refresh) - entry->want_refresh = 1; + entry->want_refresh = true; - if (cache_check_is_valid(entry->ipv4, current_time) - == FALSE && want_refresh == FALSE) { + if (!cache_check_is_valid(entry->ipv4, current_time) && !want_refresh) { g_hash_table_remove(cache, question); type = 0; } @@ -638,13 +776,69 @@ static uint16_t cache_check_validity(char *question, uint16_t type, return type; } -static struct cache_entry *cache_check(gpointer request, int *qtype) +static void cache_element_destroy(gpointer value) +{ + struct cache_entry *entry = value; + + if (!entry) + return; + + if (entry->ipv4) { + g_free(entry->ipv4->data); + g_free(entry->ipv4); + } + + 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 gboolean try_remove_cache(gpointer user_data) +{ + cache_timer = 0; + + if (__sync_fetch_and_sub(&cache_refcount, 1) == 1) { + debug("No cache users, removing it."); + + g_hash_table_destroy(cache); + cache = NULL; + } + + return FALSE; +} + +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); +} + +static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) { - char *question = request + 12; + char *question; struct cache_entry *entry; struct domain_question *q; uint16_t type; - int offset; + int offset, proto_offset; + + if (!request) + return NULL; + + proto_offset = protocol_offset(proto); + if (proto_offset < 0) + return NULL; + + question = request + proto_offset + 12; offset = strlen(question) + 1; q = (void *) (question + offset); @@ -654,8 +848,13 @@ static struct cache_entry *cache_check(gpointer request, int *qtype) if (type != 1 && type != 28) return NULL; + if (!cache) { + create_cache(); + return NULL; + } + entry = g_hash_table_lookup(cache, question); - if (entry == NULL) + if (!entry) return NULL; type = cache_check_validity(question, type, entry); @@ -676,7 +875,7 @@ static struct cache_entry *cache_check(gpointer request, int *qtype) 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, int *name_len) + unsigned char **end, char *name, size_t max_name, int *name_len) { unsigned char *p; @@ -692,12 +891,12 @@ static int get_name(int counter, if (offset >= max - pkt) return -ENOBUFS; - if (*end == NULL) + if (!*end) *end = p + 2; return get_name(counter + 1, pkt, pkt + offset, max, output, output_max, output_len, end, - name, name_len); + name, max_name, name_len); } else { unsigned label_len = *p; @@ -707,6 +906,9 @@ static int get_name(int counter, 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. @@ -722,7 +924,7 @@ static int get_name(int counter, p += label_len + 1; - if (*end == NULL) + if (!*end) *end = p; if (p >= max) @@ -738,14 +940,14 @@ static int parse_rr(unsigned char *buf, unsigned char *start, unsigned char *response, unsigned int *response_size, uint16_t *type, uint16_t *class, int *ttl, int *rdlen, unsigned char **end, - char *name) + 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, &name_len); + &output_len, end, name, max_name, &name_len); if (err < 0) return err; @@ -756,7 +958,7 @@ static int parse_rr(unsigned char *buf, unsigned char *start, rr = (void *) (*end); - if (rr == NULL) + if (!rr) return -EINVAL; *type = ntohs(rr->type); @@ -784,19 +986,19 @@ static int parse_rr(unsigned char *buf, unsigned char *start, return 0; } -static gboolean check_alias(GSList *aliases, char *name) +static bool check_alias(GSList *aliases, char *name) { GSList *list; - if (aliases != NULL) { + 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 true; } } - return FALSE; + return false; } static int parse_response(unsigned char *buf, int buflen, @@ -820,7 +1022,7 @@ static int parse_response(unsigned char *buf, int buflen, if (buflen < 12) return -EINVAL; - DBG("qr %d qdcount %d", hdr->qr, qdcount); + debug("qr %d qdcount %d", hdr->qr, qdcount); /* We currently only cache responses where question count is 1 */ if (hdr->qr != 1 || qdcount != 1) @@ -847,6 +1049,8 @@ static int parse_response(unsigned char *buf, int buflen, *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 @@ -869,7 +1073,8 @@ static int parse_response(unsigned char *buf, int buflen, memset(rsp, 0, sizeof(rsp)); ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len, - type, class, ttl, &rdlen, &next, name); + type, class, ttl, &rdlen, &next, name, + sizeof(name) - 1); if (ret != 0) { err = ret; goto out; @@ -923,7 +1128,7 @@ static int parse_response(unsigned char *buf, int buflen, * of the alias and cache that. */ unsigned char *end = NULL; - int name_len = 0, output_len; + int name_len = 0, output_len = 0; memset(rsp, 0, sizeof(rsp)); rsp_len = sizeof(rsp) - 1; @@ -935,7 +1140,7 @@ static int parse_response(unsigned char *buf, int buflen, */ ret = get_name(0, buf, next - rdlen, buf + buflen, rsp, rsp_len, &output_len, &end, - name, &name_len); + name, sizeof(name) - 1, &name_len); if (ret != 0) { /* just ignore the error at this point */ ptr = next; @@ -960,8 +1165,8 @@ static int parse_response(unsigned char *buf, int buflen, /* * We found correct type (A or AAAA) */ - if (check_alias(aliases, name) == TRUE || - (aliases == NULL && strncmp(question, name, + if (check_alias(aliases, name) || + (!aliases && strncmp(question, name, qlen) == 0)) { /* * We found an alias or the name of the rr @@ -1016,7 +1221,7 @@ static gboolean cache_check_entry(gpointer key, gpointer value, * remove both from the cache. */ - if (entry->ipv4 != NULL && entry->ipv4->timeout > 0) { + if (entry->ipv4 && entry->ipv4->timeout > 0) { max_timeout = entry->ipv4->cache_until; if (max_timeout > data->max_timeout) data->max_timeout = max_timeout; @@ -1025,7 +1230,7 @@ static gboolean cache_check_entry(gpointer key, gpointer value, return TRUE; } - if (entry->ipv6 != NULL && entry->ipv6->timeout > 0) { + if (entry->ipv6 && entry->ipv6->timeout > 0) { max_timeout = entry->ipv6->cache_until; if (max_timeout > data->max_timeout) data->max_timeout = max_timeout; @@ -1063,7 +1268,7 @@ static void cache_cleanup(void) count = g_hash_table_foreach_remove(cache, cache_check_entry, &data); } - DBG("removed %d in the first pass", count); + debug("removed %d in the first pass", count); /* * In the second pass, if the first pass turned up blank, @@ -1097,7 +1302,7 @@ static gboolean cache_invalidate_entry(gpointer key, gpointer value, /* if anything is not expired, mark the entry for refresh */ if (entry->hits > 0 && (entry->ipv4 || entry->ipv6)) - entry->want_refresh = 1; + entry->want_refresh = true; /* delete the cached data */ if (entry->ipv4) { @@ -1127,9 +1332,9 @@ static gboolean cache_invalidate_entry(gpointer key, gpointer value, */ static void cache_invalidate(void) { - DBG("Invalidating the DNS cache %p", cache); + debug("Invalidating the DNS cache %p", cache); - if (cache == NULL) + if (!cache) return; g_hash_table_foreach_remove(cache, cache_invalidate_entry, NULL); @@ -1140,15 +1345,15 @@ static void cache_refresh_entry(struct cache_entry *entry) cache_enforce_validity(entry); - if (entry->hits > 2 && entry->ipv4 == NULL) - entry->want_refresh = 1; - if (entry->hits > 2 && entry->ipv6 == NULL) - entry->want_refresh = 1; + if (entry->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 = 0; + entry->want_refresh = false; /* turn a DNS name into a hostname with dots */ strncpy(dns_name, entry->key, NS_MAXDNAME); @@ -1159,7 +1364,7 @@ static void cache_refresh_entry(struct cache_entry *entry) *c = '.'; c += jump + 1; } - DBG("Refreshing %s\n", dns_name); + debug("Refreshing %s\n", dns_name); /* then refresh the hostname */ refresh_dns_entry(entry, &dns_name[1]); } @@ -1175,7 +1380,7 @@ static void cache_refresh_iterator(gpointer key, gpointer value, static void cache_refresh(void) { - if (cache == NULL) + if (!cache) return; g_hash_table_foreach(cache, cache_refresh_iterator, NULL); @@ -1184,7 +1389,6 @@ static void cache_refresh(void) static int reply_query_type(unsigned char *msg, int len) { unsigned char *c; - uint16_t *w; int l; int type; @@ -1196,10 +1400,9 @@ static int reply_query_type(unsigned char *msg, int len) return 0; /* now the query, which is a name and 2 16 bit words */ - l = dns_name_length(c) + 1; + l = dns_name_length(c); c += l; - w = (uint16_t *) c; - type = ntohs(*w); + type = c[0] << 8 | c[1]; return type; } @@ -1210,6 +1413,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, 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; @@ -1217,7 +1421,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, unsigned char response[NS_MAXDNAME + 1]; unsigned char *ptr; unsigned int rsplen; - gboolean new_entry = TRUE; + bool new_entry = true; time_t current_time; if (cache_size >= MAX_CACHE_SIZE) { @@ -1234,13 +1438,17 @@ static int cache_update(struct server_data *srv, unsigned char *msg, 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 (msg[3] & 0x0f) + if (hdr->rcode != ns_r_noerror) return 0; - if (offset < 0) - return 0; + if (!cache) + create_cache(); rsplen = sizeof(response) - 1; question[sizeof(question) - 1] = '\0'; @@ -1256,21 +1464,30 @@ static int cache_update(struct server_data *srv, unsigned char *msg, * to cache the negative response. */ if ((err == -ENOMSG || err == -ENOBUFS) && - reply_query_type(msg, msg_len) == 28) { + reply_query_type(msg + offset, + msg_len - offset) == 28) { entry = g_hash_table_lookup(cache, question); - if (entry && entry->ipv4 && entry->ipv6 == NULL) { + if (entry && entry->ipv4 && !entry->ipv6) { + int cache_offset = 0; + data = g_try_new(struct cache_data, 1); - if (data == NULL) + if (!data) return -ENOMEM; data->inserted = entry->ipv4->inserted; data->type = type; - data->answers = msg[5]; + data->answers = ntohs(hdr->ancount); data->timeout = entry->ipv4->timeout; - data->data_len = msg_len; - data->data = ptr = g_malloc(msg_len); + if (srv->protocol == IPPROTO_UDP) + cache_offset = 2; + data->data_len = msg_len + cache_offset; + data->data = ptr = g_malloc(data->data_len); + ptr[0] = (data->data_len - 2) / 256; + ptr[1] = (data->data_len - 2) - ptr[0] * 256; + if (srv->protocol == IPPROTO_UDP) + ptr += 2; data->valid_until = entry->ipv4->valid_until; data->cache_until = entry->ipv4->cache_until; - memcpy(data->data, msg, msg_len); + memcpy(ptr, msg, msg_len); entry->ipv6 = data; /* * we will get a "hit" when we serve the response @@ -1296,20 +1513,20 @@ static int cache_update(struct server_data *srv, unsigned char *msg, * records for the same name. */ entry = g_hash_table_lookup(cache, question); - if (entry == NULL) { + if (!entry) { entry = g_try_new(struct cache_entry, 1); - if (entry == NULL) + if (!entry) return -ENOMEM; data = g_try_new(struct cache_data, 1); - if (data == NULL) { + if (!data) { g_free(entry); return -ENOMEM; } entry->key = g_strdup(question); entry->ipv4 = entry->ipv6 = NULL; - entry->want_refresh = 0; + entry->want_refresh = false; entry->hits = 0; if (type == 1) @@ -1317,14 +1534,14 @@ static int cache_update(struct server_data *srv, unsigned char *msg, else entry->ipv6 = data; } else { - if (type == 1 && entry->ipv4 != NULL) + if (type == 1 && entry->ipv4) return 0; - if (type == 28 && entry->ipv6 != NULL) + if (type == 28 && entry->ipv6) return 0; data = g_try_new(struct cache_data, 1); - if (data == NULL) + if (!data) return -ENOMEM; if (type == 1) @@ -1340,7 +1557,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, if (entry->hits < 0) entry->hits = 0; - new_entry = FALSE; + new_entry = false; } if (ttl < MIN_CACHE_TTL) @@ -1350,7 +1567,12 @@ static int cache_update(struct server_data *srv, unsigned char *msg, data->type = type; data->answers = answers; data->timeout = ttl; - data->data_len = 12 + qlen + 1 + 2 + 2 + rsplen; + /* + * The "2" in start of the length is the TCP offset. We allocate it + * here even for UDP packet because it simplifies the sending + * of cached packet. + */ + data->data_len = 2 + 12 + qlen + 1 + 2 + 2 + rsplen; data->data = ptr = g_malloc(data->data_len); data->valid_until = current_time + ttl; @@ -1363,31 +1585,51 @@ static int cache_update(struct server_data *srv, unsigned char *msg, data->cache_until = round_down_ttl(current_time + ttl, ttl); - if (data->data == NULL) { + if (!data->data) { g_free(entry->key); g_free(data); g_free(entry); return -ENOMEM; } - memcpy(ptr, msg, 12); - memcpy(ptr + 12, question, qlen + 1); /* copy also the \0 */ + /* + * We cache the two extra bytes at the start of the message + * in a TCP packet. When sending UDP packet, we skip the first + * two bytes. This way we do not need to know the format + * (UDP/TCP) of the cached message. + */ + 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; - q = (void *) (ptr + 12 + qlen + 1); + 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 + 12 + qlen + 1 + sizeof(struct domain_question), + memcpy(ptr + offset + 12 + qlen + 1 + sizeof(struct domain_question), response, rsplen); - if (new_entry == TRUE) { + if (new_entry) { g_hash_table_replace(cache, entry->key, entry); cache_size++; } - DBG("cache %d %squestion \"%s\" type %d ttl %d size %zd", + 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); + 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; } @@ -1400,12 +1642,12 @@ static int ns_resolv(struct server_data *server, struct request_data *req, char *dot, *lookup = (char *) name; struct cache_entry *entry; - entry = cache_check(request, &type); - if (entry != NULL) { + entry = cache_check(request, &type, req->protocol); + if (entry) { int ttl_left = 0; struct cache_data *data; - DBG("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA"); + debug("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA"); if (type == 1) data = entry->ipv4; else @@ -1416,19 +1658,20 @@ static int ns_resolv(struct server_data *server, struct request_data *req, entry->hits++; } - if (data != NULL && req->protocol == IPPROTO_TCP) { + 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 != NULL && req->protocol == IPPROTO_UDP) { - int sk; - sk = g_io_channel_unix_get_fd( - req->ifdata->udp_listener_channel); + if (data && req->protocol == IPPROTO_UDP) { + int udp_sk = get_req_udp_socket(req); + + if (udp_sk < 0) + return -EIO; - send_cached_response(sk, data->data, + send_cached_response(udp_sk, data->data, data->data_len, &req->sa, req->sa_len, IPPROTO_UDP, req->srcid, data->answers, ttl_left); @@ -1436,21 +1679,52 @@ static int ns_resolv(struct server_data *server, struct request_data *req, } } +#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 = send(sk, request, req->request_len, 0); - if (err < 0) + 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 != NULL && dot != lookup + strlen(lookup) - 1) + if (dot && dot != lookup + strlen(lookup) - 1) return 0; - if (server->domains != NULL && server->domains->data != NULL) - req->append_domain = TRUE; + if (server->domains && server->domains->data) + req->append_domain = true; for (list = server->domains; list; list = list->next) { char *domain; @@ -1460,7 +1734,7 @@ static int ns_resolv(struct server_data *server, struct request_data *req, domain = list->data; - if (domain == NULL) + if (!domain) continue; offset = protocol_offset(server->protocol); @@ -1495,7 +1769,10 @@ static int ns_resolv(struct server_data *server, struct request_data *req, alt[1] = req_len & 0xff; } - err = send(sk, alt, req->request_len + domlen, 0); + 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; @@ -1505,175 +1782,494 @@ static int ns_resolv(struct server_data *server, struct request_data *req, return 0; } -static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, - struct server_data *data) +static char *convert_label(char *start, char *end, char *ptr, char *uptr, + int remaining_len, int *used_comp, int *used_uncomp) { - struct domain_hdr *hdr; - struct request_data *req; - int dns_id, sk, err, offset = protocol_offset(protocol); - struct listener_data *ifdata; - - if (offset < 0) - return offset; + 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; + } - hdr = (void *)(reply + offset); - dns_id = reply[offset] | reply[offset + 1] << 8; + /* + * 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; + } - DBG("Received %d bytes (id 0x%04x)", reply_len, dns_id); + *used_comp = pos; + *used_uncomp = comp_pos; - req = find_request(dns_id); - if (req == NULL) - return -EINVAL; + return ptr; - DBG("id 0x%04x rcode %d", hdr->id, hdr->rcode); +out: + return NULL; +} - ifdata = req->ifdata; +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 */ - reply[offset] = req->srcid & 0xff; - reply[offset + 1] = req->srcid >> 8; + debug("count %d ptr %p end %p uptr %p", field_count, ptr, end, uptr); - req->numresp++; + 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 (hdr->rcode == 0 || req->resp == NULL) { + if (!convert_label(start, end, ptr, name, NS_MAXLABEL, + &pos, &comp_pos)) + goto out; /* - * If the domain name was append - * remove it before forwarding the reply. + * Copy the uncompressed resource record, type, class and \0 to + * tmp buffer. */ - if (req->append_domain == TRUE) { - unsigned char *ptr; - uint8_t host_len; - unsigned int domain_len; - /* - * 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); + ulen = strlen(name); + strncpy(uptr, name, uncomp_len - (uptr - uncompressed)); - /* - * Remove the domain name and replace it by the end - * of reply. Check if the domain is really there - * before trying to copy the data. The domain_len can - * be 0 because if the original query did not contain - * a domain name, then we are sending two packets, - * first without the domain name and the second packet - * with domain name. The append_domain is set to true - * even if we sent the first packet without domain - * name. In this case we end up in this branch. - */ - if (domain_len > 0) { - memcpy(ptr + host_len + 1, - ptr + host_len + domain_len + 1, - reply_len - (ptr - reply + domain_len)); + debug("pos %d ulen %d left %d name %s", pos, ulen, + (int)(uncomp_len - (uptr - uncompressed)), uptr); - reply_len = reply_len - domain_len; - } - } + uptr += ulen; + *uptr++ = '\0'; - g_free(req->resp); - req->resplen = 0; + ptr += pos; - req->resp = g_try_malloc(reply_len); - if (req->resp == NULL) - return -ENOMEM; + /* + * We copy also the fixed portion of the result (type, class, + * ttl, address length and the address) + */ + memcpy(uptr, ptr, NS_RRFIXEDSZ); - memcpy(req->resp, reply, reply_len); - req->resplen = reply_len; + dns_type = uptr[0] << 8 | uptr[1]; + dns_class = uptr[2] << 8 | uptr[3]; - cache_update(data, reply, reply_len); - } + if (dns_class != ns_c_in) + goto out; - if (hdr->rcode > 0 && req->numresp < req->numserv) - return -EINVAL; + ptr += NS_RRFIXEDSZ; + uptr += NS_RRFIXEDSZ; - if (req->timeout > 0) - g_source_remove(req->timeout); + /* + * 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; - request_list = g_slist_remove(request_list, req); + uptr[-2] = comp_pos << 8; + uptr[-1] = comp_pos & 0xff; - 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); - } else { - sk = req->client_sk; - err = send(sk, req->resp, req->resplen, 0); - close(sk); - } + uptr += comp_pos; + ptr += pos; - g_free(req->resp); - g_free(req); + } else if (dns_type == ns_t_a || dns_type == ns_t_aaaa) { + dlen = uptr[-2] << 8 | uptr[-1]; - return err; -} + if (ptr + dlen > end) { + debug("data len %d too long", dlen); + goto out; + } -static void cache_element_destroy(gpointer value) -{ - struct cache_entry *entry = value; + memcpy(uptr, ptr, dlen); + uptr += dlen; + ptr += dlen; - if (entry == NULL) - return; + } else if (dns_type == ns_t_soa) { + int total_len = 0; + char *len_ptr; - if (entry->ipv4 != NULL) { - g_free(entry->ipv4->data); - g_free(entry->ipv4); - } + /* Primary name server expansion */ + if (!convert_label(start, end, ptr, uptr, + uncomp_len - (uptr - uncompressed), + &pos, &comp_pos)) + goto out; - if (entry->ipv6 != NULL) { - g_free(entry->ipv6->data); - g_free(entry->ipv6); + 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; } - g_free(entry->key); - g_free(entry); + return ptr; - if (--cache_size < 0) - cache_size = 0; +out: + return NULL; } -static gboolean try_remove_cache(gpointer user_data) +static int strip_domains(char *name, char *answers, int maxlen) { - if (__sync_fetch_and_sub(&cache_refcount, 1) == 1) { - DBG("No cache users, removing it."); + uint16_t data_len; + int name_len = strlen(name); + char *ptr, *start = answers, *end = answers + maxlen; - g_hash_table_destroy(cache); - cache = NULL; + 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 FALSE; + return end - start; } -static void destroy_server(struct server_data *server) +static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, + struct server_data *data) { - GList *list; + struct domain_hdr *hdr; + struct request_data *req; + int dns_id, sk, err, offset = protocol_offset(protocol); + + if (offset < 0) + return offset; - DBG("interface %s server %s", server->interface, server->server); + hdr = (void *)(reply + offset); + dns_id = reply[offset] | reply[offset + 1] << 8; - server_list = g_slist_remove(server_list, server); + debug("Received %d bytes (id 0x%04x)", reply_len, dns_id); - if (server->watch > 0) - g_source_remove(server->watch); + req = find_request(dns_id); + if (!req) + return -EINVAL; - if (server->timeout > 0) - g_source_remove(server->timeout); + debug("req %p dstid 0x%04x altid 0x%04x rcode %d", + req, req->dstid, req->altid, hdr->rcode); - g_io_channel_unref(server->channel); + reply[offset] = req->srcid & 0xff; + reply[offset + 1] = req->srcid >> 8; - if (server->protocol == IPPROTO_UDP) - connman_info("Removing DNS server %s", server->server); + req->numresp++; - g_free(server->incoming_reply); - g_free(server->server); - for (list = server->domains; list; list = list->next) { - char *domain = list->data; + if (hdr->rcode == ns_r_noerror || !req->resp) { + unsigned char *new_reply = NULL; - server->domains = g_list_remove(server->domains, domain); - g_free(domain); + /* + * 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 defined TIZEN_EXT + if (!req->resp) { + g_free(new_reply); + return -ENOMEM; + } +#else + if (!req->resp) + return -ENOMEM; +#endif + + memcpy(req->resp, reply, reply_len); + req->resplen = reply_len; + + cache_update(data, reply, reply_len); + + g_free(new_reply); } - g_free(server->interface); + +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 = 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, MSG_NOSIGNAL); + } + + 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 server_destroy_socket(struct server_data *data) +{ + debug("index %d server %s proto %d", data->index, + data->server, data->protocol); + + if (data->watch > 0) { + g_source_remove(data->watch); + data->watch = 0; + } + + 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; + } + + g_free(data->incoming_reply); + data->incoming_reply = NULL; +} + +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); + + server_list = g_slist_remove(server_list, server); + server_destroy_socket(server); + + if (server->protocol == IPPROTO_UDP && server->enabled) + debug("Removing DNS server %s", server->server); + + g_free(server->server); + 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. @@ -1684,7 +2280,8 @@ static void destroy_server(struct server_data *server) * without any good reason. The small delay allows the new RDNSS to * create a new DNS server instance and the refcount does not go to 0. */ - g_timeout_add_seconds(3, try_remove_cache, NULL); + if (cache && !cache_timer) + cache_timer = g_timeout_add_seconds(3, try_remove_cache, NULL); g_free(server); } @@ -1693,24 +2290,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)) { 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, data); - 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; } @@ -1728,7 +2335,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 @@ -1737,14 +2344,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; /* @@ -1771,16 +2380,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, @@ -1788,7 +2398,7 @@ hangup: } } - server->connected = TRUE; + server->connected = true; server_list = g_slist_append(server_list, server); if (server->timeout > 0) { @@ -1796,29 +2406,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); - if (ns_resolv(server, req, req->request, - req->name) > 0) { - /* We sent cached result so no need for timeout - * handler. - */ - if (req->timeout > 0) { - g_source_remove(req->timeout); - req->timeout = 0; - } - } + list = list->next; + } + + if (no_request_sent) { + destroy_server(server); + return FALSE; } } else if (condition & G_IO_IN) { @@ -1845,7 +2474,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) @@ -1892,9 +2521,9 @@ static gboolean tcp_idle_timeout(gpointer user_data) { struct server_data *server = user_data; - DBG(""); + debug(""); - if (server == NULL) + if (!server) return FALSE; destroy_server(server); @@ -1902,82 +2531,56 @@ static gboolean tcp_idle_timeout(gpointer user_data) return FALSE; } -static struct server_data *create_server(const char *interface, - const char *domain, const char *server, - int protocol) +static int server_create_socket(struct server_data *data) { - struct addrinfo hints, *rp; - struct server_data *data; - int sk, ret; - - DBG("interface %s server %s", interface, server); - - 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: - return NULL; - } - hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_NUMERICHOST; + int sk, err; + char *interface; - ret = getaddrinfo(server, "53", &hints, &rp); - if (ret) { - connman_error("Failed to parse server %s address: %s\n", - server, gai_strerror(ret)); - 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. */ + debug("index %d server %s proto %d", data->index, + data->server, data->protocol); - sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + sk = socket(data->server_addr->sa_family, + data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, + data->protocol); if (sk < 0) { - connman_error("Failed to create server %s socket", server); - freeaddrinfo(rp); - return NULL; + err = errno; + connman_error("Failed to create server %s socket", + data->server); + server_destroy_socket(data); + return -err; } - if (interface != NULL) { + debug("sk %d", sk); + + interface = connman_inet_ifname(data->index); + if (interface) { if (setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, - interface, strlen(interface) + 1) < 0) { + interface, + strlen(interface) + 1) < 0) { + err = errno; connman_error("Failed to bind server %s " "to interface %s", - server, interface); - freeaddrinfo(rp); + data->server, interface); close(sk); - return NULL; + server_destroy_socket(data); + g_free(interface); + return -err; } - } - - data = g_try_new0(struct server_data, 1); - if (data == NULL) { - connman_error("Failed to allocate server %s data", server); - freeaddrinfo(rp); - close(sk); - return NULL; + g_free(interface); } data->channel = g_io_channel_unix_new(sk); - if (data->channel == NULL) { - connman_error("Failed to create server %s channel", server); - freeaddrinfo(rp); + if (!data->channel) { + connman_error("Failed to create server %s channel", + data->server); close(sk); - g_free(data); - return NULL; + server_destroy_socket(data); + return -ENOMEM; } g_io_channel_set_close_on_unref(data->channel, TRUE); - if (protocol == IPPROTO_TCP) { + 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, @@ -1989,62 +2592,311 @@ static struct server_data *create_server(const char *interface, 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); + if (connect(sk, data->server_addr, data->server_addr_len) < 0) { + err = errno; - g_free(data->server); - g_free(data->interface); - for (list = data->domains; list; list = list->next) { - char *domain = list->data; + if ((data->protocol == IPPROTO_TCP && errno != EINPROGRESS) || + data->protocol == IPPROTO_UDP) { - data->domains = g_list_remove(data->domains, - domain); - g_free(domain); - } - g_free(data); - return NULL; + connman_error("Failed to connect to server %s", + data->server); + server_destroy_socket(data); + return -err; } } - 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); + 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_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; - connman_info("Adding DNS server %s", data->server); + DBG("Adding DNS server %s", data->server); - server_list = g_slist_append(server_list, data); + data->timeout = g_timeout_add_seconds(30, sec_udp_idle_timeout, + data); - return data; + server_list_sec = g_slist_append(server_list_sec, data); } - return NULL; + return data; +} +#endif + +static struct server_data *create_server(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) { + 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(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)); + 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. */ + + 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) { + freeaddrinfo(rp); + destroy_server(data); + return NULL; + } + memcpy(data->server_addr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(rp); + + if (server_create_socket(data) != 0) { + destroy_server(data); + return NULL; + } + + 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); + + enable_fallback(false); + } + + server_list = g_slist_append(server_list, data); + } + + return data; } -static gboolean resolv(struct request_data *req, +static bool resolv(struct request_data *req, gpointer request, gpointer name) { GSList *list; @@ -2052,42 +2904,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->protocol == IPPROTO_TCP) { + DBG("server %s ignored proto TCP", data->server); + continue; + } + + debug("server %s enabled %d", data->server, data->enabled); - if (data->enabled == FALSE) + if (!data->enabled) 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); + 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; + 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; @@ -2095,98 +2954,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; @@ -2195,14 +3106,14 @@ 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(); } } @@ -2210,337 +3121,849 @@ static void dnsproxy_offline_mode(connman_bool_t enabled) static void dnsproxy_default_changed(struct connman_service *service) { + bool server_enabled = false; GSList *list; - char *interface; + int index; DBG("service %p", service); /* DNS has changed, invalidate the cache */ cache_invalidate(); - if (service == NULL) { + 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 void dnsproxy_service_state_changed(struct connman_service *service, + enum connman_service_state state) +{ + GSList *list; + int index; + + switch (state) { + case CONNMAN_SERVICE_STATE_DISCONNECT: + case CONNMAN_SERVICE_STATE_IDLE: + break; + case CONNMAN_SERVICE_STATE_ASSOCIATION: + case CONNMAN_SERVICE_STATE_CONFIGURATION: + case CONNMAN_SERVICE_STATE_FAILURE: + case CONNMAN_SERVICE_STATE_ONLINE: + case CONNMAN_SERVICE_STATE_READY: + case CONNMAN_SERVICE_STATE_UNKNOWN: + return; + } + + index = __connman_service_get_index(service); + list = server_list; + + while (list) { + struct server_data *data = list->data; + + /* Get next before the list is changed by destroy_server() */ + list = list->next; + + if (data->index == index) { + DBG("removing server data of index %d", index); + destroy_server(data); + } + } +} + +static const struct connman_notifier dnsproxy_notifier = { .name = "dnsproxy", .default_changed = dnsproxy_default_changed, .offline_mode = dnsproxy_offline_mode, + .service_state_changed = dnsproxy_service_state_changed, }; -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 len = *ptr; + uint8_t label_len = *ptr; + + if (label_len == 0x00) { + uint8_t class; + struct qtype_qclass *q = + (struct qtype_qclass *)(ptr + 1); - if (len == 0x00) { - last_label = (char *) (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 + len + 1 > size) + if (used + label_len + 1 > size) return -ENOBUFS; - strncat(name, (char *) (ptr + 1), len); + strncat(name, (char *) (ptr + 1), label_len); strcat(name, "."); - used += len + 1; + used += label_len + 1; - ptr += len + 1; - remain -= len + 1; + ptr += label_len + 1; + remain -= label_len + 1; } - if (last_label && arcount && remain >= 9 && last_label[4] == 0 && - !memcmp(last_label + 5, opt_edns0_type, 2)) { - uint16_t edns0_bufsize; + 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); - edns0_bufsize = last_label[7] << 8 | last_label[8]; + DBG("EDNS0 buffer size %u", ntohs(edns0->class)); + } else if (!arcount && remain) { + DBG("DNS request with %d garbage bytes", remain); + } - DBG("EDNS0 buffer size %u", edns0_bufsize); + debug("query %s", name); - /* 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; - } + 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; } - DBG("query %s", name); + if (client->watch > 0) { + g_source_remove(client->watch); + client->watch = 0; + } - return 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 gboolean tcp_listener_event(GIOChannel *channel, GIOCondition condition, - gpointer user_data) +static unsigned int get_msg_len(unsigned char *buf) { - unsigned char buf[768]; - char query[512]; + 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; - struct server_data *server; - int sk, client_sk, len, err; - struct sockaddr_in6 client_addr; - socklen_t client_addr_len = sizeof(client_addr); + int client_sk, err; + unsigned int msg_len; GSList *list; - struct listener_data *ifdata = user_data; + 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)); + + connman_error("Error with TCP client %d channel", client_sk); + return FALSE; + } + + 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; + } + + 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; + } + + return read_tcp_data(client, client_addr, *client_addr_len, len); +} + +static gboolean client_timeout(gpointer user_data) +{ + struct tcp_partial_client_data *client = user_data; + int sock; + + sock = g_io_channel_unix_get_fd(client->channel); + + debug("client %d timeout pending %d bytes", sock, client->buf_end); + + g_hash_table_remove(partial_tcp_req_table, GINT_TO_POINTER(sock)); + + return FALSE; +} + +#if defined TIZEN_EXT +static void recover_listener(GIOChannel *channel, struct listener_data *ifdata) +{ + int sk, index; + + index = ifdata->index; + + sk = g_io_channel_unix_get_fd(channel); + close(sk); + + __connman_dnsproxy_remove_listener(index); - DBG("condition 0x%x", condition); + if (__connman_dnsproxy_add_listener(index) == 0) + DBG("listener %d successfully recovered", index); +} +#endif + +static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, + struct listener_data *ifdata, int family, + guint *listener_watch) +{ + 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 (family == AF_INET) { + client_addr = &client_addr4; + client_addr_len = &client_addr4_len; + } else { + client_addr = &client_addr6; + client_addr_len = &client_addr6_len; + } + + tv.tv_sec = tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(sk, &readfds); + + 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; + } + if (client_sk < 0) { connman_error("Accept failure on TCP listener"); - ifdata->tcp_listener_watch = 0; - return FALSE; + *listener_watch = 0; + return false; } - len = recv(client_sk, buf, sizeof(buf), 0); - if (len < 2) - return TRUE; + fcntl(client_sk, F_SETFL, O_NONBLOCK); - DBG("Received %d bytes (id 0x%04x)", len, buf[2] | buf[3] << 8); + 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; + } - 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; - } + g_hash_table_insert(partial_tcp_req_table, + GINT_TO_POINTER(client_sk), + client); - req = g_try_new0(struct request_data, 1); - if (req == NULL) - return TRUE; + client->channel = g_io_channel_unix_new(client_sk); + g_io_channel_set_close_on_unref(client->channel, TRUE); - memcpy(&req->sa, &client_addr, client_addr_len); - req->sa_len = client_addr_len; - req->client_sk = client_sk; - req->protocol = IPPROTO_TCP; + client->watch = g_io_add_watch(client->channel, + G_IO_IN, tcp_client_event, + (gpointer)client); - request_id += 2; - if (request_id == 0x0000 || request_id == 0xffff) - request_id += 2; + client->ifdata = ifdata; - req->srcid = buf[2] | (buf[3] << 8); - req->dstid = request_id; - req->altid = request_id + 1; - req->request_len = len; + debug("client %d created %p", client_sk, client); + } else { + debug("client %d already exists %p", client_sk, client); + } + + 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; - buf[2] = req->dstid & 0xff; - buf[3] = req->dstid >> 8; + if (client->timeout == 0) + client->timeout = g_timeout_add_seconds(2, client_timeout, + client); - req->numserv = 0; - req->ifdata = (struct listener_data *) ifdata; - req->append_domain = FALSE; - request_list = g_slist_append(request_list, req); + /* + * 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; + } - for (list = server_list; list; list = list->next) { - struct server_data *data = list->data; - GList *domains; + 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; + } - if (data->protocol != IPPROTO_UDP || data->enabled == FALSE) - continue; + if (len < 2) { + debug("client %d not enough data to read, waiting", client_sk); + client->buf_end += len; + return true; + } - server = create_server(data->interface, NULL, - data->server, IPPROTO_TCP); + 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; + } - /* - * 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; + /* + * 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); - memcpy(req->request, buf, req->request_len); + 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)); + return read_tcp_data(client, client_addr, *client_addr_len, len); +} - continue; - } +static gboolean tcp4_listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct listener_data *ifdata = user_data; - if (req->timeout > 0) - g_source_remove(req->timeout); + return tcp_listener_event(channel, condition, ifdata, AF_INET, + &ifdata->tcp4_listener_watch); +} + +static gboolean tcp6_listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct listener_data *ifdata = user_data; - for (domains = data->domains; domains; - domains = domains->next) { - char *dom = domains->data; + return tcp_listener_event(channel, condition, user_data, AF_INET6, + &ifdata->tcp6_listener_watch); +} - DBG("Adding domain %s to %s", dom, server->server); +#if defined TIZEN_EXT +struct request_data * create_request(int sk, unsigned char *buf, size_t len, + const struct sockaddr *to, socklen_t tolen, + int protocol) +{ + struct request_data *req; + req = g_try_new0(struct request_data, 1); + if (!req) + return NULL; + memcpy(&req->sa, to, tolen); + req->sa_len = tolen; + req->client_sk = sk; + req->protocol = protocol; + req->request_len = len; + req->request = g_malloc(len); + memcpy(req->request, buf, len); + return req; - server->domains = g_list_append(server->domains, - g_strdup(dom)); - } +} +static gboolean send_response_timeout (gpointer user_data) +{ + struct request_data *req = user_data; - req->timeout = g_timeout_add_seconds(30, request_timeout, req); - if (ns_resolv(server, req, buf, query) > 0) { - if (req->timeout > 0) { - g_source_remove(req->timeout); - req->timeout = 0; - } - } - } + send_response(req->client_sk, req->request,(size_t) req->request_len, (const struct sockaddr *)&req->sa, + (socklen_t)req->sa_len, req->protocol); + g_free(req->request); + g_free(req); + + return FALSE; - return TRUE; } +#endif -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; +#if defined TIZEN_EXT + /** TEMP Fix + * Reason: In tizen6.0 same code working fine because it seems some delay in 6.0 platform + * where as in tizen6.5 this loop is continuously executing due to this unable to receive + * the response from telephony deamon and wpa_supplicant. To stop continuously execution + * of this code added 10ms delay. + */ + req = create_request(sk, buf, len, client_addr, + *client_addr_len, IPPROTO_UDP); + if(!req) + return true;; + g_timeout_add(10, send_response_timeout, req); +#else + send_response(sk, buf, len, client_addr, + *client_addr_len, IPPROTO_UDP); +#endif + 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->append_domain = FALSE; + req->ifdata = ifdata; + req->append_domain = false; - if (resolv(req, buf, query) == TRUE) { + if (resolv(req, buf, query)) { /* a cached result was sent, so the request can be released */ g_free(req); - return TRUE; + 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); +#endif request_list = g_slist_append(request_list, req); - return TRUE; + 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: @@ -2554,159 +3977,297 @@ 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); + 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; - } - /* 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); - 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->udp4_listener_watch > 0) + g_source_remove(ifdata->udp4_listener_watch); - if (ifdata->udp_listener_watch > 0) - g_source_remove(ifdata->udp_listener_watch); + if (ifdata->udp6_listener_watch > 0) + g_source_remove(ifdata->udp6_listener_watch); - g_io_channel_unref(ifdata->udp_listener_channel); + 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"); - - return 0; -} + index = connman_inet_ifindex("lo"); + if (ifdata->index == index) { + if ((err & IPv6_FAILED) != IPv6_FAILED) + __connman_resolvfile_append(index, NULL, "::1"); -static void destroy_request_data(struct request_data *req) -{ - if (req->timeout > 0) - g_source_remove(req->timeout); + if ((err & IPv4_FAILED) != IPv4_FAILED) + __connman_resolvfile_append(index, NULL, "127.0.0.1"); + } - g_free(req->resp); - g_free(req->request); - g_free(req->name); - g_free(req); + 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); - destroy_request_data(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); destroy_request_data(req); list->data = NULL; @@ -2719,72 +4280,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 (index < 0) + return -EINVAL; + + if (!listener_table) + return -ENOENT; - if (g_hash_table_lookup(listener_table, interface) != NULL) + 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); - ifdata = g_hash_table_lookup(listener_table, interface); - if (ifdata == NULL) + if (!listener_table) + return; + + 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) { - const char *interface = key; + int index = GPOINTER_TO_INT(key); struct listener_data *ifdata = value; - DBG("interface %s", interface); + 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; @@ -2795,19 +4384,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); }