* Connection Manager
*
* Copyright (C) 2007-2014 Intel Corporation. All rights reserved.
+ * Copyright (C) 2022 Matthias Gerstner of SUSE. 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
#include "connman.h"
-#define debug(fmt...) do { } while (0)
+#ifdef DNSPROXY_DEBUG
+# define debug(fmt...) do { fprintf(stderr, fmt); fprintf(stderr, "\n"); } while (0)
+#else
+# define debug(fmt...) do { } while (0)
+#endif
+
+#define NUM_ARRAY_ELEMENTS(a) sizeof(a) / sizeof(a[0])
#if __BYTE_ORDER == __LITTLE_ENDIAN
struct domain_hdr {
struct cache_entry {
char *key;
bool want_refresh;
- int hits;
+ size_t hits;
struct cache_data *ipv4;
struct cache_data *ipv6;
};
+struct cache_timeout {
+ time_t current_time;
+ time_t max_timeout;
+ bool try_harder;
+};
+
struct domain_question {
uint16_t type;
uint16_t class;
*/
#define MAX_CACHE_SIZE 256
+#define DNS_HEADER_SIZE sizeof(struct domain_hdr)
+#define DNS_HEADER_TCP_EXTRA_BYTES 2
+#define DNS_TCP_HEADER_SIZE DNS_HEADER_SIZE + DNS_HEADER_TCP_EXTRA_BYTES
+#define DNS_QUESTION_SIZE sizeof(struct domain_question)
+#define DNS_RR_SIZE sizeof(struct domain_rr)
+#define DNS_QTYPE_QCLASS_SIZE sizeof(struct qtype_qclass)
+
+enum dns_type {
+ /* IPv4 address 32-bit */
+ DNS_TYPE_A = ns_t_a,
+ /* IPv6 address 128-bit */
+ DNS_TYPE_AAAA = ns_t_aaaa,
+ /* alias to another name */
+ DNS_TYPE_CNAME = ns_t_cname,
+ /* start of a zone of authority */
+ DNS_TYPE_SOA = ns_t_soa
+};
+
+enum dns_class {
+ DNS_CLASS_IN = ns_c_in,
+ DNS_CLASS_ANY = ns_c_any /* only valid for QCLASS fields */
+};
+
static int cache_size;
static GHashTable *cache;
static int cache_refcount;
-static GSList *server_list = NULL;
-static GSList *request_list = NULL;
-static GHashTable *listener_table = NULL;
+static GSList *server_list;
+static GSList *request_list;
+static GHashTable *listener_table;
static time_t next_refresh;
static GHashTable *partial_tcp_req_table;
-static guint cache_timer = 0;
+static guint cache_timer;
+static in_port_t dns_listen_port = 53;
+/* we can keep using the same resolve's */
+static GResolv *ipv4_resolve;
+static GResolv *ipv6_resolve;
static guint16 get_id(void)
{
uint64_t rand;
+ /* TODO: return code is ignored, should we rather abort() on error? */
__connman_util_get_random(&rand);
return rand;
}
-static int protocol_offset(int protocol)
+static size_t protocol_offset(int protocol)
{
switch (protocol) {
case IPPROTO_UDP:
return 0;
case IPPROTO_TCP:
- return 2;
+ return DNS_HEADER_TCP_EXTRA_BYTES;
default:
- return -EINVAL;
+ /* this should never happen */
+ abort();
}
+}
+static const char* protocol_label(int protocol)
+{
+ switch(protocol) {
+ case IPPROTO_UDP:
+ return "UDP";
+ case IPPROTO_TCP:
+ return "TCP";
+ default:
+ return "BAD_PROTOCOL";
+ }
+}
+
+static int socket_type(int protocol, int extra_flags)
+{
+ switch (protocol) {
+ case IPPROTO_UDP:
+ return SOCK_DGRAM | extra_flags;
+ case IPPROTO_TCP:
+ return SOCK_STREAM | extra_flags;
+ default:
+ /* this should never happen */
+ abort();
+ }
}
/*
static struct request_data *find_request(guint16 id)
{
- GSList *list;
-
- for (list = request_list; list; list = list->next) {
+ for (GSList *list = request_list; list; list = list->next) {
struct request_data *req = list->data;
if (req->dstid == id || req->altid == id)
const char *server,
int protocol)
{
- GSList *list;
-
debug("index %d server %s proto %d", index, server, protocol);
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
if (index < 0 && data->index < 0 &&
return NULL;
}
-/* we can keep using the same resolve's */
-static GResolv *ipv4_resolve;
-static GResolv *ipv6_resolve;
-
static void dummy_resolve_func(GResolvResultStatus status,
char **results, gpointer user_data)
{
age = 4;
}
- entry->hits -= age;
- if (entry->hits < 0)
+ if (entry->hits > age)
+ entry->hits -= age;
+ else
entry->hits = 0;
}
-static int dns_name_length(unsigned char *buf)
+static size_t dns_name_length(const unsigned char *buf)
{
if ((buf[0] & NS_CMPRSFLGS) == NS_CMPRSFLGS) /* compressed name */
return 2;
- return strlen((char *)buf) + 1;
+ return strlen((const char *)buf) + 1;
}
-static void update_cached_ttl(unsigned char *buf, int len, int new_ttl)
+static void update_cached_ttl(unsigned char *ptr, int len, int new_ttl)
{
- unsigned char *c;
- uint16_t w;
- int l;
+ size_t name_len;
+ const uint32_t raw_ttl = ntohl((uint32_t)new_ttl);
+
+ if (new_ttl < 0)
+ return;
/* skip the header */
- c = buf + 12;
- len -= 12;
+ ptr += DNS_HEADER_SIZE;
+ len -= DNS_HEADER_SIZE;
- /* skip the query, which is a name and 2 16 bit words */
- l = dns_name_length(c);
- c += l;
- len -= l;
- c += 4;
- len -= 4;
+ if (len < DNS_QUESTION_SIZE + 1)
+ return;
+
+ /* skip the query, which is a name and a struct domain_question */
+ name_len = dns_name_length(ptr);
+
+ ptr += name_len + DNS_QUESTION_SIZE;
+ len -= name_len + DNS_QUESTION_SIZE;
/* now we get the answer records */
while (len > 0) {
+ struct domain_rr *rr = NULL;
+ size_t rr_len;
+
/* first a name */
- l = dns_name_length(c);
- c += l;
- len -= l;
- if (len < 0)
- break;
- /* then type + class, 2 bytes each */
- c += 4;
- len -= 4;
+ name_len = dns_name_length(ptr);
+ ptr += name_len;
+ len -= name_len;
if (len < 0)
break;
- /* now the 4 byte TTL field */
- c[0] = new_ttl >> 24 & 0xff;
- c[1] = new_ttl >> 16 & 0xff;
- c[2] = new_ttl >> 8 & 0xff;
- c[3] = new_ttl & 0xff;
- c += 4;
- len -= 4;
- if (len < 0)
+ rr = (void*)ptr;
+ if (len < sizeof(*rr))
+ /* incomplete record */
break;
- /* now the 2 byte rdlen field */
- w = c[0] << 8 | c[1];
- c += w + 2;
- len -= w + 2;
+ /* update the TTL field */
+ memcpy(&rr->ttl, &raw_ttl, sizeof(raw_ttl));
+
+ /* skip to the next record */
+ rr_len = sizeof(*rr) + ntohs(rr->rdlen);
+ ptr += rr_len;
+ len -= rr_len;
}
}
-static void send_cached_response(int sk, unsigned char *buf, int len,
+static void send_cached_response(int sk, const unsigned char *ptr, size_t len,
const struct sockaddr *to, socklen_t tolen,
int protocol, int id, uint16_t answers, int ttl)
{
- struct domain_hdr *hdr;
- unsigned char *ptr = buf;
- int err, offset, dns_len, adj_len = len - 2;
-
+ struct domain_hdr *hdr = NULL;
+ int err;
+ const size_t offset = protocol_offset(protocol);
/*
* 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;
- }
+ const size_t skip_bytes = offset ? 0 : DNS_HEADER_TCP_EXTRA_BYTES;
+ ptr += skip_bytes;
+ len -= skip_bytes;
+ const size_t dns_len = protocol == IPPROTO_UDP ? len : ntohs(*((uint16_t*)ptr));
- if (len < 12)
+
+ if (len < DNS_HEADER_SIZE)
return;
hdr = (void *) (ptr + offset);
/* if this is a negative reply, we are authoritative */
if (answers == 0)
hdr->aa = 1;
- else
+ else {
+ const int adj_len = len - 2;
update_cached_ttl((unsigned char *)hdr, adj_len, ttl);
+ }
- debug("sk %d id 0x%04x answers %d ptr %p length %d dns %d",
+ debug("sk %d id 0x%04x answers %d ptr %p length %zd dns %zd",
sk, hdr->id, answers, ptr, len, dns_len);
err = sendto(sk, ptr, len, MSG_NOSIGNAL, to, tolen);
if (err < 0) {
connman_error("Cannot send cached DNS response: %s",
strerror(errno));
- return;
}
-
- 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",
+ else if (err != len || dns_len != (len - offset))
+ debug("Packet length mismatch, sent %d wanted %zd dns %zd",
err, len, dns_len);
}
int protocol)
{
struct domain_hdr *hdr;
- int err, offset = protocol_offset(protocol);
+ int err;
+ const size_t offset = protocol_offset(protocol);
+ const size_t send_size = DNS_HEADER_SIZE + offset;
debug("sk %d", sk);
- if (offset < 0)
- return;
-
- if (len < sizeof(*hdr) + offset)
+ if (len < send_size)
return;
hdr = (void *) (buf + offset);
if (offset) {
buf[0] = 0;
- buf[1] = sizeof(*hdr);
+ buf[1] = DNS_HEADER_SIZE;
}
debug("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode);
hdr->nscount = 0;
hdr->arcount = 0;
- err = sendto(sk, buf, sizeof(*hdr) + offset, MSG_NOSIGNAL, to, tolen);
+ err = sendto(sk, buf, send_size, MSG_NOSIGNAL, to, tolen);
if (err < 0) {
connman_error("Failed to send DNS response to %d: %s",
sk, strerror(errno));
- return;
}
}
{
struct request_data *req = user_data;
struct sockaddr *sa;
- int sk;
+ int sk = -1;
if (!req)
return FALSE;
} else if (req->protocol == IPPROTO_TCP) {
sk = req->client_sk;
sa = NULL;
- } else
+ }
+
+ if (sk < 0)
goto out;
if (req->resplen > 0 && req->resp) {
* "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);
+ sendto(sk, req->resp, req->resplen, MSG_NOSIGNAL,
+ sa, req->sa_len);
} else if (req->request) {
/*
* There was not reply from server at all.
*/
- struct domain_hdr *hdr;
-
- hdr = (void *)(req->request + protocol_offset(req->protocol));
+ struct domain_hdr *hdr = (void *)(req->request + protocol_offset(req->protocol));
hdr->id = req->srcid;
- if (sk >= 0)
- send_response(sk, req->request, req->request_len,
- sa, req->sa_len, req->protocol);
+ send_response(sk, req->request, req->request_len,
+ sa, req->sa_len, req->protocol);
}
/*
return FALSE;
}
-static int append_query(unsigned char *buf, unsigned int size,
- const char *query, const char *domain)
+static int append_data(unsigned char *buf, size_t size, const char *data)
{
unsigned char *ptr = buf;
- int len;
+ size_t len;
- debug("query %s domain %s", query, domain);
-
- while (query) {
- const char *tmp;
+ while (true) {
+ const char *dot = strchr(data, '.');
+ len = dot ? dot - data : strlen(data);
- tmp = strchr(query, '.');
- if (!tmp) {
- len = strlen(query);
- if (len == 0)
- break;
- *ptr = len;
- memcpy(ptr + 1, query, len);
- ptr += len + 1;
+ if (len == 0)
break;
- }
+ else if (size < len + 1)
+ return -1;
+
+ *ptr = len;
+ memcpy(ptr + 1, data, len);
+ ptr += len + 1;
+ size -= len + 1;
- *ptr = tmp - query;
- memcpy(ptr + 1, query, tmp - query);
- ptr += tmp - query + 1;
+ if (!dot)
+ break;
- query = tmp + 1;
+ data = dot + 1;
}
- while (domain) {
- const char *tmp;
+ return ptr - buf;
+}
- tmp = strchr(domain, '.');
- if (!tmp) {
- len = strlen(domain);
- if (len == 0)
- break;
- *ptr = len;
- memcpy(ptr + 1, domain, len);
- ptr += len + 1;
- break;
- }
+static int append_query(unsigned char *buf, size_t size,
+ const char *query, const char *domain)
+{
+ size_t added;
+ size_t left_size = size;
+ int res;
- *ptr = tmp - domain;
- memcpy(ptr + 1, domain, tmp - domain);
- ptr += tmp - domain + 1;
+ debug("query %s domain %s", query, domain);
- domain = tmp + 1;
- }
+ res = append_data(buf, left_size, query);
+ if (res < 0)
+ return -1;
+ left_size -= res;
- *ptr++ = 0x00;
+ res = append_data(buf + res, left_size, domain);
+ if (res < 0)
+ return -1;
+ left_size -= res;
- return ptr - buf;
+ if (left_size == 0)
+ return -1;
+
+ added = size - left_size;
+ *(buf + added) = 0x00;
+
+ return added;
}
-static bool cache_check_is_valid(struct cache_data *data,
- time_t current_time)
+static bool cache_check_is_valid(struct cache_data *data, time_t current_time)
{
if (!data)
return false;
-
- if (data->cache_until < current_time)
+ else if (data->cache_until < current_time)
return false;
return true;
}
+static void cache_free_ipv4(struct cache_entry *entry)
+{
+ if (!entry->ipv4)
+ return;
+
+ g_free(entry->ipv4->data);
+ g_free(entry->ipv4);
+ entry->ipv4 = NULL;
+}
+
+static void cache_free_ipv6(struct cache_entry *entry)
+{
+ if (!entry->ipv6)
+ return;
+
+ g_free(entry->ipv6->data);
+ g_free(entry->ipv6);
+ entry->ipv6 = NULL;
+}
+
/*
* remove stale cached entries so that they can be refreshed
*/
{
time_t current_time = time(NULL);
- if (!cache_check_is_valid(entry->ipv4, current_time)
- && entry->ipv4) {
+ if (entry->ipv4 && !cache_check_is_valid(entry->ipv4, current_time)) {
debug("cache timeout \"%s\" type A", entry->key);
- g_free(entry->ipv4->data);
- g_free(entry->ipv4);
- entry->ipv4 = NULL;
-
+ cache_free_ipv4(entry);
}
- if (!cache_check_is_valid(entry->ipv6, current_time)
- && entry->ipv6) {
+ if (entry->ipv6 && !cache_check_is_valid(entry->ipv6, current_time)) {
debug("cache timeout \"%s\" type AAAA", entry->key);
- g_free(entry->ipv6->data);
- g_free(entry->ipv6);
- entry->ipv6 = NULL;
+ cache_free_ipv6(entry);
}
}
-static uint16_t cache_check_validity(char *question, uint16_t type,
+static bool cache_check_validity(const char *question, uint16_t type,
struct cache_entry *entry)
{
- time_t current_time = time(NULL);
- 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 = true;
+ struct cache_data *cached_ip = NULL, *other_ip = NULL;
+ const time_t current_time = time(NULL);
+ bool want_refresh;
cache_enforce_validity(entry);
switch (type) {
- case 1: /* IPv4 */
- if (!cache_check_is_valid(entry->ipv4, current_time)) {
- debug("cache %s \"%s\" type A", entry->ipv4 ?
- "timeout" : "entry missing", question);
-
- if (want_refresh)
- entry->want_refresh = true;
+ case DNS_TYPE_A: /* IPv4 */
+ cached_ip = entry->ipv4;
+ other_ip = entry->ipv6;
+ break;
- /*
- * We do not remove cache entry if there is still
- * valid IPv6 entry found in the cache.
- */
- if (!cache_check_is_valid(entry->ipv6, current_time) && !want_refresh) {
- g_hash_table_remove(cache, question);
- type = 0;
- }
- }
+ case DNS_TYPE_AAAA: /* IPv6 */
+ cached_ip = entry->ipv6;
+ other_ip = entry->ipv4;
break;
+ default:
+ return false;
+ }
- case 28: /* IPv6 */
- if (!cache_check_is_valid(entry->ipv6, current_time)) {
- debug("cache %s \"%s\" type AAAA", entry->ipv6 ?
- "timeout" : "entry missing", question);
+ /*
+ * if we have a popular entry, we want a refresh instead of
+ * total destruction of the entry.
+ */
+ want_refresh = entry->hits > 2 ? true : false;
- if (want_refresh)
- entry->want_refresh = true;
+ if (!cache_check_is_valid(cached_ip, current_time)) {
+ debug("cache %s \"%s\" type %s",
+ cached_ip ? "timeout" : "entry missing",
+ question,
+ cached_ip == entry->ipv4 ? "A" : "AAAA");
- if (!cache_check_is_valid(entry->ipv4, current_time) && !want_refresh) {
- g_hash_table_remove(cache, question);
- type = 0;
- }
+ if (want_refresh)
+ entry->want_refresh = true;
+ /*
+ * We do not remove cache entry if there is still a
+ * valid entry for another IP version found in the cache.
+ */
+ else if (!cache_check_is_valid(other_ip, current_time)) {
+ g_hash_table_remove(cache, question);
+ return false;
}
- break;
}
- return type;
+ return true;
}
static void cache_element_destroy(gpointer 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);
- }
+ cache_free_ipv4(entry);
+ cache_free_ipv6(entry);
g_free(entry->key);
g_free(entry);
+ /* TODO: this would be a worrying condition. Does this ever happen? */
if (--cache_size < 0)
cache_size = 0;
}
g_hash_table_destroy(cache);
cache = NULL;
+ cache_size = 0;
}
return FALSE;
static void create_cache(void)
{
- if (__sync_fetch_and_add(&cache_refcount, 1) == 0)
+ 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);
+ cache_size = 0;
+ }
}
-static struct cache_entry *cache_check(gpointer request, int *qtype, int proto)
+static struct cache_entry *cache_check(gpointer request, uint16_t *qtype, int proto)
{
- char *question;
- struct cache_entry *entry;
- struct domain_question *q;
- uint16_t type;
- 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);
- type = ntohs(q->type);
+ const char *question = request + protocol_offset(proto) + DNS_HEADER_SIZE;
+ const size_t offset = strlen(question) + 1;
+ const struct domain_question *q = (void *) (question + offset);
+ const uint16_t type = ntohs(q->type);
+ struct cache_entry *entry;
/* We only cache either A (1) or AAAA (28) requests */
- if (type != 1 && type != 28)
+ if (type != DNS_TYPE_A && type != DNS_TYPE_AAAA)
return NULL;
if (!cache) {
if (!entry)
return NULL;
- type = cache_check_validity(question, type, entry);
- if (type == 0)
+ if (!cache_check_validity(question, type, entry))
return NULL;
*qtype = type;
* format so that we can cache the wire format string directly.
*/
static int get_name(int counter,
- unsigned char *pkt, unsigned char *start, unsigned char *max,
+ const unsigned char *pkt, const unsigned char *start, const unsigned char *max,
unsigned char *output, int output_max, int *output_len,
- unsigned char **end, char *name, size_t max_name, int *name_len)
+ const unsigned char **end, char *name, size_t max_name, int *name_len)
{
- unsigned char *p;
+ const unsigned char *p = start;
/* Limit recursion to 10 (this means up to 10 labels in domain name) */
if (counter > 10)
return -EINVAL;
- p = start;
while (*p) {
if ((*p & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
- uint16_t offset = (*p & 0x3F) * 256 + *(p + 1);
+ const uint16_t offset = (*p & 0x3F) * 256 + *(p + 1);
if (offset >= max - pkt)
return -ENOBUFS;
if (pkt + label_len > max)
return -ENOBUFS;
-
- if (*output_len > output_max)
+ else if (*output_len > output_max)
return -ENOBUFS;
-
- if ((*name_len + 1 + label_len + 1) > max_name)
+ else if ((*name_len + 1 + label_len + 1) > max_name)
return -ENOBUFS;
/*
return 0;
}
-static int parse_rr(unsigned char *buf, unsigned char *start,
- unsigned char *max,
- unsigned char *response, unsigned int *response_size,
- uint16_t *type, uint16_t *class, int *ttl, int *rdlen,
- unsigned char **end,
+static int parse_rr(const unsigned char *buf, const unsigned char *start,
+ const unsigned char *max,
+ unsigned char *response, size_t *response_size,
+ uint16_t *type, uint16_t *class, int *ttl, uint16_t *rdlen,
+ const unsigned char **end,
char *name, size_t max_name)
{
struct domain_rr *rr;
- int err, offset;
+ size_t offset;
int name_len = 0, output_len = 0, max_rsp = *response_size;
+ int err = get_name(0, buf, start, max, response, max_rsp,
+ &output_len, end, name, max_name, &name_len);
- err = get_name(0, buf, start, max, response, max_rsp,
- &output_len, end, name, max_name, &name_len);
if (err < 0)
return err;
offset = output_len;
- if ((unsigned int) offset > *response_size)
+ if (offset > *response_size)
return -ENOBUFS;
rr = (void *) (*end);
if (*ttl < 0)
return -EINVAL;
- memcpy(response + offset, *end, sizeof(struct domain_rr));
+ memcpy(response + offset, *end, DNS_RR_SIZE);
- offset += sizeof(struct domain_rr);
- *end += sizeof(struct domain_rr);
+ offset += DNS_RR_SIZE;
+ *end += DNS_RR_SIZE;
- if ((unsigned int) (offset + *rdlen) > *response_size)
+ if ((offset + *rdlen) > *response_size)
return -ENOBUFS;
memcpy(response + offset, *end, *rdlen);
*end += *rdlen;
-
*response_size = offset + *rdlen;
return 0;
}
-static bool check_alias(GSList *aliases, char *name)
+static bool check_alias(GSList *aliases, const char *name)
{
- GSList *list;
-
if (aliases) {
- for (list = aliases; list; list = list->next) {
- int len = strlen((char *)list->data);
- if (strncmp((char *)list->data, name, len) == 0)
+ for (GSList *list = aliases; list; list = list->next) {
+ const char *cmpname = (const char*)list->data;
+ if (strncmp(cmpname, name, NS_MAXDNAME) == 0)
return true;
}
}
return false;
}
-static int parse_response(unsigned char *buf, int buflen,
- char *question, int qlen,
+/*
+ * Parses the DNS response packet found in 'buf' consisting of 'buflen' bytes.
+ *
+ * The parsed question label, response type and class, ttl and number of
+ * answer sections are output parameters. The response output buffer will
+ * receive all matching resource records to be cached.
+ *
+ * Return value is < 0 on error (negative errno) or zero on success.
+ */
+static int parse_response(const unsigned char *buf, size_t buflen,
+ char *question, size_t qlen,
uint16_t *type, uint16_t *class, int *ttl,
- unsigned char *response, unsigned int *response_len,
+ unsigned char *response, size_t *response_len,
uint16_t *answers)
{
struct domain_hdr *hdr = (void *) buf;
struct domain_question *q;
- unsigned char *ptr;
- uint16_t qdcount = ntohs(hdr->qdcount);
- uint16_t ancount = ntohs(hdr->ancount);
- int err, i;
- uint16_t qtype, qclass;
- unsigned char *next = NULL;
- unsigned int maxlen = *response_len;
- GSList *aliases = NULL, *list;
- char name[NS_MAXDNAME + 1];
-
- if (buflen < 12)
+ uint16_t qtype;
+ int err = -ENOMSG;
+ uint16_t ancount, qclass;
+ GSList *aliases = NULL;
+ const size_t maxlen = *response_len;
+
+ *response_len = 0;
+ *answers = 0;
+
+ if (buflen < DNS_HEADER_SIZE)
return -EINVAL;
+ const uint16_t qdcount = ntohs(hdr->qdcount);
+ const unsigned char *ptr = buf + DNS_HEADER_SIZE;
+ const unsigned char *eptr = buf + buflen;
+
debug("qr %d qdcount %d", hdr->qr, qdcount);
/* We currently only cache responses where question count is 1 */
if (hdr->qr != 1 || qdcount != 1)
return -EINVAL;
- ptr = buf + sizeof(struct domain_hdr);
-
- strncpy(question, (char *) ptr, qlen);
+ /*
+ * NOTE: currently the *caller* ensures that the `question' buffer is
+ * always zero terminated.
+ */
+ strncpy(question, (const char *) ptr, MIN(qlen, buflen - DNS_HEADER_SIZE));
qlen = strlen(question);
ptr += qlen + 1; /* skip \0 */
+ if ((eptr - ptr) < DNS_QUESTION_SIZE)
+ return -EINVAL;
+
q = (void *) ptr;
qtype = ntohs(q->type);
/* We cache only A and AAAA records */
- if (qtype != 1 && qtype != 28)
+ if (qtype != DNS_TYPE_A && qtype != DNS_TYPE_AAAA)
return -ENOMSG;
- qclass = ntohs(q->class);
-
- ptr += 2 + 2; /* ptr points now to answers */
+ ptr += DNS_QUESTION_SIZE; /* advance to answers section */
- err = -ENOMSG;
- *response_len = 0;
- *answers = 0;
-
- memset(name, 0, sizeof(name));
+ ancount = ntohs(hdr->ancount);
+ qclass = ntohs(q->class);
/*
* We have a bunch of answers (like A, AAAA, CNAME etc) to
* resource records. Only A and AAAA records are cached, all
* the other records in answers are skipped.
*/
- for (i = 0; i < ancount; i++) {
+ for (uint16_t i = 0; i < ancount; i++) {
+ char name[NS_MAXDNAME + 1] = {0};
/*
* Get one address at a time to this buffer.
* The max size of the answer is
* 4 (ttl) + 2 (rdlen) + addr (16 or 4) = 28
* for A or AAAA record.
* For CNAME the size can be bigger.
+ * TODO: why are we using the MAXCDNAME constant as buffer
+ * size then?
*/
- unsigned char rsp[NS_MAXCDNAME];
- unsigned int rsp_len = sizeof(rsp) - 1;
- int ret, rdlen;
-
- memset(rsp, 0, sizeof(rsp));
+ unsigned char rsp[NS_MAXCDNAME] = {0};
+ size_t rsp_len = sizeof(rsp) - 1;
+ const unsigned char *next = NULL;
+ uint16_t rdlen;
- ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len,
+ int ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len,
type, class, ttl, &rdlen, &next, name,
sizeof(name) - 1);
if (ret != 0) {
err = ret;
- goto out;
+ break;
}
+ /* set pointer to the next RR for the next iteration */
+ ptr = next;
+
/*
- * Now rsp contains compressed or uncompressed resource
+ * Now rsp contains a compressed or an uncompressed resource
* record. Next we check if this record answers the question.
* The name var contains the uncompressed label.
* One tricky bit is the CNAME records as they alias
* looking for.
*/
if (*class != qclass) {
- ptr = next;
- next = NULL;
continue;
}
* address of ipv6.l.google.com. For caching purposes this
* should not cause any issues.
*/
- if (*type == 5 && strncmp(question, name, qlen) == 0) {
+ if (*type == DNS_TYPE_CNAME && strncmp(question, name, qlen) == 0) {
/*
* So now the alias answered the question. This is
* not very useful from caching point of view as
* question. We need to find the real A/AAAA record
* of the alias and cache that.
*/
- unsigned char *end = NULL;
+ const unsigned char *end = NULL;
int name_len = 0, output_len = 0;
memset(rsp, 0, sizeof(rsp));
name, sizeof(name) - 1, &name_len);
if (ret != 0) {
/* just ignore the error at this point */
- ptr = next;
- next = NULL;
continue;
}
*/
aliases = g_slist_prepend(aliases, g_strdup(name));
- ptr = next;
- next = NULL;
continue;
- }
-
- if (*type == qtype) {
+ } else if (*type == qtype) {
/*
* We found correct type (A or AAAA)
*/
*/
if (*response_len + rsp_len > maxlen) {
err = -ENOBUFS;
- goto out;
+ break;
}
memcpy(response + *response_len, rsp, rsp_len);
*response_len += rsp_len;
err = 0;
}
}
-
- ptr = next;
- next = NULL;
}
-out:
- for (list = aliases; list; list = list->next)
+ for (GSList *list = aliases; list; list = list->next)
g_free(list->data);
g_slist_free(aliases);
return err;
}
-struct cache_timeout {
- time_t current_time;
- int max_timeout;
- int try_harder;
-};
-
static gboolean cache_check_entry(gpointer key, gpointer value,
gpointer user_data)
{
struct cache_timeout *data = user_data;
struct cache_entry *entry = value;
- int max_timeout;
+ time_t max_timeout;
/* Scale the number of hits by half as part of cache aging */
static void cache_cleanup(void)
{
- static int max_timeout;
- struct cache_timeout data;
+ static time_t max_timeout;
+ struct cache_timeout data = {
+ .current_time = time(NULL),
+ .max_timeout = 0,
+ .try_harder = false
+ };
int count = 0;
- data.current_time = time(NULL);
- data.max_timeout = 0;
- data.try_harder = 0;
-
/*
* In the first pass, we only remove entries that have timed out.
* We use a cache of the first time to expire to do this only
* we also expire entries with a low hit count,
* while aging the hit count at the same time.
*/
- data.try_harder = 1;
+ data.try_harder = true;
if (count == 0)
count = g_hash_table_foreach_remove(cache, cache_check_entry,
&data);
entry->want_refresh = true;
/* delete the cached data */
- if (entry->ipv4) {
- g_free(entry->ipv4->data);
- g_free(entry->ipv4);
- entry->ipv4 = NULL;
- }
-
- if (entry->ipv6) {
- g_free(entry->ipv6->data);
- g_free(entry->ipv6);
- entry->ipv6 = NULL;
- }
+ cache_free_ipv4(entry);
+ cache_free_ipv6(entry);
/* keep the entry if we want it refreshed, delete it otherwise */
- if (entry->want_refresh)
- return FALSE;
- else
- return TRUE;
+ return entry->want_refresh ? FALSE : TRUE;
}
/*
static void cache_refresh_entry(struct cache_entry *entry)
{
-
cache_enforce_validity(entry);
- if (entry->hits > 2 && !entry->ipv4)
- entry->want_refresh = true;
- if (entry->hits > 2 && !entry->ipv6)
+ if (entry->hits > 2 && (!entry->ipv4 || !entry->ipv6))
entry->want_refresh = true;
if (entry->want_refresh) {
- char *c;
char dns_name[NS_MAXDNAME + 1];
+ char *c;
+
entry->want_refresh = false;
/* turn a DNS name into a hostname with dots */
strncpy(dns_name, entry->key, NS_MAXDNAME);
c = dns_name;
- while (c && *c) {
- int jump;
- jump = *c;
+ while (*c) {
+ /* fetch the size of the current component and replace
+ it by a dot */
+ int jump = *c;
*c = '.';
c += jump + 1;
}
g_hash_table_foreach(cache, cache_refresh_iterator, NULL);
}
-static int reply_query_type(unsigned char *msg, int len)
+static int reply_query_type(const unsigned char *msg, int len)
{
- unsigned char *c;
- int l;
- int type;
-
/* skip the header */
- c = msg + sizeof(struct domain_hdr);
- len -= sizeof(struct domain_hdr);
+ const unsigned char *c = msg + DNS_HEADER_SIZE;
+ int type;
+ len -= DNS_HEADER_SIZE;
if (len < 0)
return 0;
- /* now the query, which is a name and 2 16 bit words */
- l = dns_name_length(c);
- c += l;
+ /* now the query, which is a name and 2 16 bit words for type and class */
+ c += dns_name_length(c);
+
type = c[0] << 8 | c[1];
return type;
}
-static int cache_update(struct server_data *srv, unsigned char *msg,
- unsigned int msg_len)
+/*
+ * update the cache with the DNS reply found in msg
+ */
+static int cache_update(struct server_data *srv, const unsigned char *msg, size_t msg_len)
{
- int offset = protocol_offset(srv->protocol);
- int err, qlen, ttl = 0;
+ const size_t offset = protocol_offset(srv->protocol);
+ int err, ttl = 0;
+ uint16_t *lenhdr;
+ size_t qlen;
+ bool is_new_entry = false;
uint16_t answers = 0, type = 0, class = 0;
struct domain_hdr *hdr = (void *)(msg + offset);
- struct domain_question *q;
+ struct domain_question *q = NULL;
struct cache_entry *entry;
struct cache_data *data;
char question[NS_MAXDNAME + 1];
unsigned char response[NS_MAXDNAME + 1];
- unsigned char *ptr;
- unsigned int rsplen;
- bool new_entry = true;
- time_t current_time;
+ unsigned char *ptr = NULL;
+ size_t rsplen = sizeof(response) - 1;
+ const time_t current_time = time(NULL);
if (cache_size >= MAX_CACHE_SIZE) {
cache_cleanup();
return 0;
}
- current_time = time(NULL);
-
/* don't do a cache refresh more than twice a minute */
if (next_refresh < current_time) {
cache_refresh();
next_refresh = current_time + 30;
}
- if (offset < 0)
- return 0;
-
- debug("offset %d hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode);
+ debug("offset %zd hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode);
/* Continue only if response code is 0 (=ok) */
if (hdr->rcode != ns_r_noerror)
if (!cache)
create_cache();
- rsplen = sizeof(response) - 1;
question[sizeof(question) - 1] = '\0';
-
err = parse_response(msg + offset, msg_len - offset,
question, sizeof(question) - 1,
&type, &class, &ttl,
*/
if ((err == -ENOMSG || err == -ENOBUFS) &&
reply_query_type(msg + offset,
- msg_len - offset) == 28) {
+ msg_len - offset) == DNS_TYPE_AAAA) {
entry = g_hash_table_lookup(cache, question);
if (entry && entry->ipv4 && !entry->ipv6) {
- int cache_offset = 0;
+ struct cache_data *data = g_try_new(struct cache_data, 1);
- data = g_try_new(struct cache_data, 1);
if (!data)
return -ENOMEM;
data->inserted = entry->ipv4->inserted;
data->type = type;
data->answers = ntohs(hdr->ancount);
data->timeout = entry->ipv4->timeout;
- if (srv->protocol == IPPROTO_UDP)
- cache_offset = 2;
- data->data_len = msg_len + cache_offset;
- data->data = ptr = g_malloc(data->data_len);
- ptr[0] = (data->data_len - 2) / 256;
- ptr[1] = (data->data_len - 2) - ptr[0] * 256;
- if (srv->protocol == IPPROTO_UDP)
- ptr += 2;
+ data->data_len = msg_len +
+ (offset ? 0 : DNS_HEADER_TCP_EXTRA_BYTES);
+ data->data = g_malloc(data->data_len);
+ ptr = data->data;
+ if (srv->protocol == IPPROTO_UDP) {
+ /* add the two bytes length header also for
+ * UDP responses */
+ lenhdr = (void*)ptr;
+ *lenhdr = htons(data->data_len -
+ DNS_HEADER_TCP_EXTRA_BYTES);
+ ptr += DNS_HEADER_TCP_EXTRA_BYTES;
+ }
data->valid_until = entry->ipv4->valid_until;
data->cache_until = entry->ipv4->cache_until;
memcpy(ptr, msg, msg_len);
* we will get a "hit" when we serve the response
* out of the cache
*/
- entry->hits--;
- if (entry->hits < 0)
- entry->hits = 0;
+ entry->hits = entry->hits ? entry->hits - 1 : 0;
return 0;
}
}
if (err < 0 || ttl == 0)
return 0;
- qlen = strlen(question);
-
/*
* If the cache contains already data, check if the
* type of the cached data is the same and do not add
* This is needed so that we can cache both A and AAAA
* records for the same name.
*/
+
entry = g_hash_table_lookup(cache, question);
+ data = NULL;
+ is_new_entry = !entry;
+
if (!entry) {
entry = g_try_new(struct cache_entry, 1);
if (!entry)
entry->want_refresh = false;
entry->hits = 0;
- if (type == 1)
- entry->ipv4 = data;
- else
- entry->ipv6 = data;
} else {
- if (type == 1 && entry->ipv4)
+ if (type == DNS_TYPE_A && entry->ipv4)
return 0;
-
- if (type == 28 && entry->ipv6)
+ else if (type == DNS_TYPE_AAAA && entry->ipv6)
return 0;
data = g_try_new(struct cache_data, 1);
if (!data)
return -ENOMEM;
- if (type == 1)
- entry->ipv4 = data;
- else
- entry->ipv6 = data;
-
/*
* compensate for the hit we'll get for serving
* the response out of the cache
*/
- entry->hits--;
- if (entry->hits < 0)
- entry->hits = 0;
-
- new_entry = false;
+ entry->hits = entry->hits ? entry->hits - 1 : 0;
}
+ if (type == DNS_TYPE_A)
+ entry->ipv4 = data;
+ else
+ entry->ipv6 = data;
+
if (ttl < MIN_CACHE_TTL)
ttl = MIN_CACHE_TTL;
data->type = type;
data->answers = answers;
data->timeout = ttl;
+ data->valid_until = current_time + ttl;
+
+ qlen = strlen(question);
/*
- * 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.
+ * We allocate the extra TCP header bytes 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;
+ data->data_len = DNS_TCP_HEADER_SIZE + qlen + 1 + 2 + 2 + rsplen;
+ data->data = g_malloc(data->data_len);
+ if (!data->data) {
+ g_free(entry->key);
+ g_free(data);
+ g_free(entry);
+ return -ENOMEM;
+ }
/*
* Restrict the cached DNS record TTL to some sane value
data->cache_until = round_down_ttl(current_time + ttl, ttl);
- if (!data->data) {
- g_free(entry->key);
- g_free(data);
- g_free(entry);
- return -ENOMEM;
- }
+ ptr = data->data;
/*
* We cache the two extra bytes at the start of the message
- * in a TCP packet. When sending UDP packet, we skip the first
+ * in a TCP packet. When sending UDP packet, we pad 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);
+ lenhdr = (void*)ptr;
+ *lenhdr = htons(data->data_len - DNS_HEADER_TCP_EXTRA_BYTES);
+ ptr += DNS_HEADER_TCP_EXTRA_BYTES;
- ptr[0] = (data->data_len - 2) / 256;
- ptr[1] = (data->data_len - 2) - ptr[0] * 256;
- if (srv->protocol == IPPROTO_UDP)
- ptr += 2;
+ memcpy(ptr, hdr, DNS_HEADER_SIZE);
+ ptr += DNS_HEADER_SIZE;
- memcpy(ptr + offset + 12, question, qlen + 1); /* copy also the \0 */
+ memcpy(ptr, question, qlen + 1); /* copy also the \0 */
+ ptr += qlen + 1;
- q = (void *) (ptr + offset + 12 + qlen + 1);
+ q = (void *)ptr;
q->type = htons(type);
q->class = htons(class);
- memcpy(ptr + offset + 12 + qlen + 1 + sizeof(struct domain_question),
- response, rsplen);
+ ptr += DNS_QUESTION_SIZE;
+
+ memcpy(ptr, response, rsplen);
- if (new_entry) {
+ if (is_new_entry) {
g_hash_table_replace(cache, entry->key, entry);
cache_size++;
}
debug("cache %d %squestion \"%s\" type %d ttl %d size %zd packet %u "
"dns len %u",
- cache_size, new_entry ? "new " : "old ",
+ cache_size, is_new_entry ? "new " : "old ",
question, type, ttl,
sizeof(*entry) + sizeof(*data) + data->data_len + qlen,
data->data_len,
return 0;
}
-static int ns_resolv(struct server_data *server, struct request_data *req,
- gpointer request, gpointer name)
+/*
+ * attempts to answer the given request from cached replies.
+ *
+ * returns:
+ * > 0 on cache hit (answer is already sent out to client)
+ * == 0 on cache miss
+ * < 0 on error condition (errno)
+ */
+static int ns_try_resolv_from_cache(
+ struct request_data *req, gpointer request, const char *lookup)
{
- GList *list;
- int sk, err, type = 0;
- char *dot, *lookup = (char *) name;
- struct cache_entry *entry;
+ uint16_t type = 0;
+ int ttl_left;
+ struct cache_data *data;
+ struct cache_entry *entry = cache_check(request, &type, req->protocol);
+ if (!entry)
+ return 0;
- entry = cache_check(request, &type, req->protocol);
- if (entry) {
- int ttl_left = 0;
- struct cache_data *data;
+ debug("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA");
- debug("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA");
- if (type == 1)
- data = entry->ipv4;
- else
- data = entry->ipv6;
+ data = type == DNS_TYPE_A ? entry->ipv4 : entry->ipv6;
- if (data) {
- ttl_left = data->valid_until - time(NULL);
- entry->hits++;
- }
+ if (!data)
+ return 0;
+
+ ttl_left = data->valid_until - time(NULL);
+ entry->hits++;
- if (data && req->protocol == IPPROTO_TCP) {
+ switch(req->protocol) {
+ case IPPROTO_TCP:
send_cached_response(req->client_sk, data->data,
data->data_len, NULL, 0, IPPROTO_TCP,
req->srcid, data->answers, ttl_left);
return 1;
- }
-
- if (data && req->protocol == IPPROTO_UDP) {
+ case IPPROTO_UDP: {
int udp_sk = get_req_udp_socket(req);
if (udp_sk < 0)
}
}
+ return -EINVAL;
+}
+
+static int ns_resolv(struct server_data *server, struct request_data *req,
+ gpointer request, gpointer name)
+{
+ int sk = -1;
+ const char *lookup = (const char *)name;
+ int err = ns_try_resolv_from_cache(req, request, lookup);
+
+ if (err > 0)
+ /* cache hit */
+ return 1;
+ else if (err != 0)
+ /* error other than cache miss, don't continue */
+ return err;
+
+ /* forward request to real DNS server */
sk = g_io_channel_unix_get_fd(server->channel);
err = sendto(sk, request, req->request_len, MSG_NOSIGNAL,
req->numserv++;
/* If we have more than one dot, we don't add domains */
- dot = strchr(lookup, '.');
- if (dot && dot != lookup + strlen(lookup) - 1)
- return 0;
+ {
+ const char *dot = strchr(lookup, '.');
+ if (dot && dot != lookup + strlen(lookup) - 1)
+ return 0;
+ }
if (server->domains && server->domains->data)
req->append_domain = true;
- for (list = server->domains; list; list = list->next) {
- char *domain;
+ for (GList *list = server->domains; list; list = list->next) {
+ int domlen, altlen;
unsigned char alt[1024];
- struct domain_hdr *hdr = (void *) &alt;
- int altlen, domlen, offset;
-
- domain = list->data;
+ const char *domain = list->data;
+ const size_t offset = protocol_offset(server->protocol);
+ struct domain_hdr *hdr = (void *) (&alt[0] + offset);
if (!domain)
continue;
- offset = protocol_offset(server->protocol);
- if (offset < 0)
- return offset;
-
domlen = strlen(domain) + 1;
+
if (domlen < 5)
return -EINVAL;
- alt[offset] = req->altid & 0xff;
- alt[offset + 1] = req->altid >> 8;
+ memcpy(alt + offset, &req->altid, sizeof(req->altid));
- memcpy(alt + offset + 2, request + offset + 2, 10);
+ memcpy(alt + offset + 2, request + offset + 2, DNS_HEADER_SIZE - 2);
hdr->qdcount = htons(1);
- altlen = append_query(alt + offset + 12, sizeof(alt) - 12,
+ altlen = append_query(alt + offset + DNS_HEADER_SIZE, sizeof(alt) - DNS_HEADER_SIZE - offset,
name, domain);
if (altlen < 0)
return -EINVAL;
- altlen += 12;
+ altlen += DNS_HEADER_SIZE;
+ altlen += offset;
- memcpy(alt + offset + altlen,
- request + offset + altlen - domlen,
- req->request_len - altlen - offset + domlen);
+ memcpy(alt + altlen,
+ request + altlen - domlen,
+ req->request_len - altlen + domlen);
if (server->protocol == IPPROTO_TCP) {
- int req_len = req->request_len + domlen - 2;
-
- alt[0] = (req_len >> 8) & 0xff;
- alt[1] = req_len & 0xff;
+ uint16_t req_len = req->request_len + domlen - DNS_HEADER_TCP_EXTRA_BYTES;
+ uint16_t *len_hdr = (void*)alt;
+ *len_hdr = htons(req_len);
}
debug("req %p dstid 0x%04x altid 0x%04x", req, req->dstid,
return 0;
}
-static char *convert_label(char *start, char *end, char *ptr, char *uptr,
+static bool convert_label(const char *start, const char *end, const char *ptr, char *uptr,
int remaining_len, int *used_comp, int *used_uncomp)
{
- int pos, comp_pos;
+ int comp_pos;
char name[NS_MAXLABEL];
- pos = dn_expand((u_char *)start, (u_char *)end, (u_char *)ptr,
+ const int pos = dn_expand((const u_char *)start, (const u_char *)end, (const u_char *)ptr,
name, NS_MAXLABEL);
if (pos < 0) {
debug("uncompress error [%d/%s]", errno, strerror(errno));
- goto out;
+ return false;
}
/*
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;
+ return false;
}
*used_comp = pos;
*used_uncomp = comp_pos;
- return ptr;
-
-out:
- return NULL;
+ return true;
}
-static char *uncompress(int16_t field_count, char *start, char *end,
- char *ptr, char *uncompressed, int uncomp_len,
+static const char* uncompress(int16_t field_count, const char *start, const char *end,
+ const char *ptr, char *uncompressed, int uncomp_len,
char **uncompressed_ptr)
{
char *uptr = *uncompressed_ptr; /* position in result buffer */
if (!convert_label(start, end, ptr, name, NS_MAXLABEL,
&pos, &comp_pos))
- goto out;
+ return NULL;
/*
* Copy the uncompressed resource record, type, class and \0 to
ulen = strlen(name) + 1;
if ((uptr + ulen) > uncomp_end)
- goto out;
+ return NULL;
memcpy(uptr, name, ulen);
debug("pos %d ulen %d left %d name %s", pos, ulen,
*/
if ((uptr + NS_RRFIXEDSZ) > uncomp_end) {
debug("uncompressed data too large for buffer");
- goto out;
+ return NULL;
}
memcpy(uptr, ptr, NS_RRFIXEDSZ);
dns_type = uptr[0] << 8 | uptr[1];
dns_class = uptr[2] << 8 | uptr[3];
- if (dns_class != ns_c_in)
- goto out;
+ if (dns_class != DNS_CLASS_IN)
+ return NULL;
ptr += NS_RRFIXEDSZ;
uptr += NS_RRFIXEDSZ;
* Typically this portion is also compressed
* so we need to uncompress it also when necessary.
*/
- if (dns_type == ns_t_cname) {
+ if (dns_type == DNS_TYPE_CNAME) {
if (!convert_label(start, end, ptr, uptr,
uncomp_len - (uptr - uncompressed),
&pos, &comp_pos))
- goto out;
+ return NULL;
uptr[-2] = comp_pos << 8;
uptr[-1] = comp_pos & 0xff;
uptr += comp_pos;
ptr += pos;
- } else if (dns_type == ns_t_a || dns_type == ns_t_aaaa) {
+ } else if (dns_type == DNS_TYPE_A || dns_type == DNS_TYPE_AAAA) {
dlen = uptr[-2] << 8 | uptr[-1];
if ((ptr + dlen) > end || (uptr + dlen) > uncomp_end) {
debug("data len %d too long", dlen);
- goto out;
+ return NULL;
}
memcpy(uptr, ptr, dlen);
uptr += dlen;
ptr += dlen;
- } else if (dns_type == ns_t_soa) {
+ } else if (dns_type == DNS_TYPE_SOA) {
int total_len = 0;
char *len_ptr;
if (!convert_label(start, end, ptr, uptr,
uncomp_len - (uptr - uncompressed),
&pos, &comp_pos))
- goto out;
+ return NULL;
total_len += comp_pos;
len_ptr = &uptr[-2];
if (!convert_label(start, end, ptr, uptr,
uncomp_len - (uptr - uncompressed),
&pos, &comp_pos))
- goto out;
+ return NULL;
total_len += comp_pos;
ptr += pos;
*/
if ((uptr + 20) > uncomp_end || (ptr + 20) > end) {
debug("soa record too long");
- goto out;
+ return NULL;
}
memcpy(uptr, ptr, 20);
uptr += 20;
}
return ptr;
-
-out:
- return NULL;
}
-static int strip_domains(char *name, char *answers, int maxlen)
+/*
+ * removes the qualified domain name part from the given answer sections
+ * starting at 'answers', consisting of 'length' bytes.
+ *
+ * 'name' points the start of the unqualified host label including the leading
+ * length octet.
+ *
+ * returns the new (possibly shorter) length of remaining payload in the
+ * answers buffer, or a negative (errno) value to indicate error conditions.
+ */
+static int strip_domains(const char *name, char *answers, size_t length)
{
uint16_t data_len;
- int name_len = strlen(name);
- char *ptr, *start = answers, *end = answers + maxlen;
+ struct domain_rr *rr;
+ /* length of the name label including the length header octet */
+ const size_t name_len = strlen(name);
+ const char *end = answers + length;
- while (maxlen > 0) {
- ptr = strstr(answers, name);
+ while (answers < end) {
+ char *ptr = strstr(answers, name);
if (ptr) {
char *domain = ptr + name_len;
+ /* this now points to the domain part length octet. */
if (*domain) {
- int domain_len = strlen(domain);
+ /*
+ * length of the rest of the labels up to the
+ * null label (zero byte).
+ */
+ const size_t domain_len = strlen(domain);
+ char *remaining = domain + domain_len;
- memmove(answers + name_len,
- domain + domain_len,
- end - (domain + domain_len));
+ /*
+ * now shift the rest of the answer sections
+ * to the left to get rid of the domain label
+ * part
+ */
+ memmove(ptr + name_len,
+ remaining,
+ end - remaining);
end -= domain_len;
- maxlen -= domain_len;
+ length -= domain_len;
}
}
+ /* skip to the next answer section */
+
+ /* the labels up to the root null label */
answers += strlen(answers) + 1;
- answers += 2 + 2 + 4; /* skip type, class and ttl fields */
+ /* the fixed part of the RR */
+ rr = (void*)answers;
+ if (answers + sizeof(*rr) > end)
+ return -EINVAL;
+ data_len = htons(rr->rdlen);
+ /* skip the rest of the RR */
+ answers += sizeof(*rr);
+ answers += data_len;
+ }
+
+ if (answers > end)
+ return -EINVAL;
+
+ return length;
+}
+
+/*
+ * Removes domain names from replies, if one has been appended during
+ * forwarding to the real DNS server.
+ *
+ * Returns:
+ * < 0 on error (abort processing reply)
+ * == 0 if the reply should be forwarded unmodified
+ * > 0 returns a new reply buffer in *new_reply on success. The return value
+ * indicates the new length of the data in *new_reply.
+ */
+static int dns_reply_fixup_domains(
+ const char *reply, size_t reply_len,
+ const size_t offset,
+ struct request_data *req,
+ char **new_reply)
+{
+ char uncompressed[NS_MAXDNAME];
+ char *uptr, *answers;
+ size_t fixed_len;
+ int new_an_len;
+ const struct domain_hdr *hdr = (void *)(reply + offset);
+ const char *eom = reply + reply_len;
+ const uint16_t header_len = offset + DNS_HEADER_SIZE;
+ /* full header plus at least one byte for the hostname length */
+ if (reply_len < header_len + 1)
+ return -EINVAL;
+
+ const uint16_t section_counts[] = {
+ hdr->ancount,
+ hdr->nscount,
+ hdr->arcount
+ };
+
+ /*
+ * length octet of the hostname.
+ * ->hostname.domain.net
+ */
+ const char *ptr = reply + header_len;
+ const uint8_t host_len = *ptr;
+ const char *domain = ptr + host_len + 1;
+ if (domain >= eom)
+ return -EINVAL;
+
+ const uint16_t domain_len = host_len ? strnlen(domain, eom - domain) : 0;
+
+ /*
+ * 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.
+ */
+ const struct qtype_qclass *qtc = (void*)(domain + domain_len + 1);
+ if (((const char*)(qtc + 1)) > eom)
+ return -EINVAL;
+
+ const uint16_t dns_type = ntohs(qtc->qtype);
+ const uint16_t dns_class = ntohs(qtc->qclass);
+
+ if (domain_len == 0) {
+ /* nothing to do */
+ return 0;
+ }
+
+ /* TODO: This condition looks wrong. It should probably be
+ *
+ * (dns_type != A && dns_type != AAAA) || dns_class != IN
+ *
+ * doing so, however, changes the behaviour of dnsproxy, e.g. MX
+ * records will be passed back to the client, but without the
+ * adjustment of the appended domain name.
+ */
+ if (dns_type != DNS_TYPE_A && dns_type != DNS_TYPE_AAAA &&
+ dns_class != DNS_CLASS_IN) {
+ debug("Pass msg dns type %d class %d", dns_type, dns_class);
+ return 0;
+ }
+
+ /*
+ * 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.
+ */
+
+ /* NOTE: length checks up and including to qtype_qclass have already
+ been done above */
+
+ /*
+ * First copy host (without domain name) into tmp buffer.
+ */
+ uptr = &uncompressed[0];
+ memcpy(uptr, ptr, host_len + 1);
+
+ uptr[host_len + 1] = '\0'; /* host termination */
+ uptr += host_len + 2;
+
+ /*
+ * Copy type and class fields of the question.
+ */
+ memcpy(uptr, qtc, sizeof(*qtc));
+
+ /*
+ * ptr points to answers after this
+ */
+ ptr = (void*)(qtc + 1);
+ uptr += sizeof(*qtc);
+ 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.
+ */
+
+ for (size_t i = 0; i < NUM_ARRAY_ELEMENTS(section_counts); i++) {
+ ptr = uncompress(ntohs(section_counts[i]), reply + offset, eom,
+ ptr, uncompressed, NS_MAXDNAME, &uptr);
+ if (!ptr) {
+ /* failed to uncompress, pass on as is
+ * (TODO: good idea?) */
+ return 0;
+ }
+ }
+
+ /*
+ * The uncompressed buffer now contains an 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_an_len = strip_domains(uncompressed, answers, uptr - answers);
+ if (new_an_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.
+ *
+ * TODO: only create a bigger buffer if actually necessary, pass
+ * allocation size of input buffer via additional parameter.
+ */
- data_len = answers[0] << 8 | answers[1];
- answers += 2; /* skip the length field */
+ reply_len = header_len + new_an_len + fixed_len;
- if (answers + data_len > end)
- return -EINVAL;
+ *new_reply = g_try_malloc(reply_len);
+ if (!*new_reply)
+ return -ENOMEM;
- answers += data_len;
- maxlen -= answers - ptr;
- }
+ memcpy(*new_reply, reply, header_len);
+ memcpy(*new_reply + header_len, uncompressed, new_an_len + fixed_len);
- return end - start;
+ return reply_len;
}
-static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
- struct server_data *data)
+static struct request_data* lookup_request(
+ const unsigned char *reply, size_t len, int protocol)
{
- struct domain_hdr *hdr;
+ const size_t offset = protocol_offset(protocol);
struct request_data *req;
- int dns_id, sk, err, offset = protocol_offset(protocol);
+ struct domain_hdr *hdr = (void *)(reply + offset);
- if (offset < 0)
- return offset;
- if (reply_len < 0)
- return -EINVAL;
- if (reply_len < offset + 1)
- return -EINVAL;
- if ((size_t)reply_len < sizeof(struct domain_hdr))
- return -EINVAL;
+ debug("Received %zd bytes (id 0x%04x)", len, hdr->id);
- hdr = (void *)(reply + offset);
- dns_id = reply[offset] | reply[offset + 1] << 8;
+ if (len < DNS_HEADER_SIZE + offset)
+ return NULL;
- debug("Received %d bytes (id 0x%04x)", reply_len, dns_id);
+ req = find_request(hdr->id);
- req = find_request(dns_id);
if (!req)
- return -EINVAL;
+ return NULL;
debug("req %p dstid 0x%04x altid 0x%04x rcode %d",
req, req->dstid, req->altid, hdr->rcode);
- reply[offset] = req->srcid & 0xff;
- reply[offset + 1] = req->srcid >> 8;
-
req->numresp++;
- if (hdr->rcode == ns_r_noerror || !req->resp) {
- unsigned char *new_reply = NULL;
+ return req;
+}
+
+static int forward_dns_reply(char *reply, size_t reply_len, int protocol,
+ struct server_data *data, struct request_data *req)
+{
+ const size_t offset = protocol_offset(protocol);
+ struct domain_hdr *hdr = (void *)(reply + offset);
+ int err, sk;
+
+ /* replace with original request ID from our client */
+ hdr->id = req->srcid;
+ if (hdr->rcode == ns_r_noerror || !req->resp) {
/*
- * 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.
+ * If the domain name was appended 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.
+ * 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, payload_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;
- char *domain;
-
- /*
- * ptr points to the first char of the hostname.
- * ->hostname.domain.net
- */
- header_len = offset + sizeof(struct domain_hdr);
- if (reply_len < header_len)
- return -EINVAL;
- payload_len = reply_len - header_len;
-
- ptr = (char *)reply + header_len;
-
- host_len = *ptr;
- domain = ptr + 1 + host_len;
- if (domain > eom)
- return -EINVAL;
-
- if (host_len > 0)
- domain_len = strnlen(domain, eom - domain);
-
- /*
- * 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;
-
- if (ptr + (dns_type_pos + 3) > eom)
- return -EINVAL;
- 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;
-
- if (len > payload_len)
- return -EINVAL;
- /*
- * 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;
- if (ptr + NS_QFIXEDSZ > eom)
- return -EINVAL;
- memcpy(uptr, ptr, NS_QFIXEDSZ);
-
- /*
- * ptr points to answers after this
- */
- ptr += NS_QFIXEDSZ;
- uptr += NS_QFIXEDSZ;
- answers = uptr;
- fixed_len = answers - uncompressed;
- if (ptr + offset > eom)
- return -EINVAL;
-
- /*
- * 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);
+ char *new_reply = NULL;
+ if (req->append_domain && ntohs(hdr->qdcount) == 1) {
+ const int fixup_res = dns_reply_fixup_domains(
+ reply, reply_len,
+ offset, req, &new_reply);
+ if (fixup_res < 0) {
+ /* error occured */
+ return fixup_res;
+ } else if (fixup_res > 0 && new_reply) {
+ /* new reply length */
+ reply_len = fixup_res;
reply = new_reply;
+ } else {
+ /* keep message as is */
}
}
- pass:
g_free(req->resp);
req->resplen = 0;
memcpy(req->resp, reply, reply_len);
req->resplen = reply_len;
- cache_update(data, reply, reply_len);
+ cache_update(data, (unsigned char*)reply, reply_len);
g_free(new_reply);
}
-out:
if (req->numresp < req->numserv) {
if (hdr->rcode > ns_r_noerror) {
return -EINVAL;
err = sendto(sk, req->resp, req->resplen, 0,
&req->sa, req->sa_len);
} else {
- uint16_t tcp_len = htons(req->resplen - 2);
+ const uint16_t tcp_len = htons(req->resplen - DNS_HEADER_TCP_EXTRA_BYTES);
/* correct TCP message length */
memcpy(req->resp, &tcp_len, sizeof(tcp_len));
sk = req->client_sk;
else
debug("proto %d sent %d bytes to %d", protocol, err, sk);
- destroy_request_data(req);
-
return err;
}
gpointer user_data)
{
unsigned char buf[4096];
- int sk, len;
+ int sk, res;
+ ssize_t len;
struct server_data *data = user_data;
+ struct request_data *req;
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
connman_error("Error with UDP server %s", data->server);
}
sk = g_io_channel_unix_get_fd(channel);
-
len = recv(sk, buf, sizeof(buf), 0);
- forward_dns_reply(buf, len, IPPROTO_UDP, data);
+ if (len <= 0)
+ return TRUE;
+
+ req = lookup_request(buf, len, IPPROTO_UDP);
+
+ if (!req)
+ /* invalid / corrupt request */
+ return TRUE;
+
+ res = forward_dns_reply((char*)buf, len, IPPROTO_UDP, data, req);
+
+ /* on success or no further responses are expected, destroy the req */
+ if (res == 0 || req->numresp >= req->numserv)
+ destroy_request_data(req);
return TRUE;
}
static gboolean tcp_server_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
- int sk;
+ struct request_data *req;
struct server_data *server = user_data;
-
- sk = g_io_channel_unix_get_fd(channel);
+ int sk = g_io_channel_unix_get_fd(channel);
if (sk == 0)
return FALSE;
list = request_list;
while (list) {
- struct request_data *req = list->data;
struct domain_hdr *hdr;
+ req = list->data;
list = list->next;
if (req->protocol == IPPROTO_UDP)
continue;
-
- if (!req->request)
+ else if (!req->request)
continue;
/*
if (req->numserv && --(req->numserv))
continue;
- hdr = (void *) (req->request + 2);
+ hdr = (void *)(req->request + DNS_HEADER_TCP_EXTRA_BYTES);
hdr->id = req->srcid;
send_response(req->client_sk, req->request,
req->request_len, NULL, 0, IPPROTO_TCP);
}
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->index, server->server,
- IPPROTO_UDP);
+ struct server_data *udp_server = find_server(
+ server->index, server->server,
+ IPPROTO_UDP);
if (udp_server) {
- for (domains = udp_server->domains; domains;
+ for (GList *domains = udp_server->domains; domains;
domains = domains->next) {
- char *dom = domains->data;
+ const char *dom = domains->data;
debug("Adding domain %s to %s",
dom, server->server);
server->connected = true;
server_list = g_slist_append(server_list, server);
- for (list = request_list; list; ) {
- struct request_data *req = list->data;
+ /* don't advance the list in the for loop, because we might
+ * need to delete elements while iterating through it */
+ for (GSList *list = request_list; list; ) {
int status;
+ req = list->data;
if (req->protocol == IPPROTO_UDP) {
list = list->next;
request_list = g_slist_remove(request_list, req);
destroy_request_data(req);
continue;
- }
-
- if (status < 0) {
+ } else if (status < 0) {
list = list->next;
continue;
}
} else if (condition & G_IO_IN) {
struct partial_reply *reply = server->incoming_reply;
int bytes_recv;
+ int res;
if (!reply) {
- unsigned char reply_len_buf[2];
uint16_t reply_len;
- bytes_recv = recv(sk, reply_len_buf, 2, MSG_PEEK);
+ bytes_recv = recv(sk, &reply_len, sizeof(reply_len), MSG_PEEK);
if (!bytes_recv) {
goto hangup;
} else if (bytes_recv < 0) {
connman_error("DNS proxy error %s",
strerror(errno));
goto hangup;
- } else if (bytes_recv < 2)
+ } else if (bytes_recv < sizeof(reply_len))
return TRUE;
- reply_len = reply_len_buf[1] | reply_len_buf[0] << 8;
- reply_len += 2;
+ /* the header contains the length of the message
+ * excluding the two length bytes */
+ reply_len = ntohs(reply_len) + DNS_HEADER_TCP_EXTRA_BYTES;
debug("TCP reply %d bytes from %d", reply_len, sk);
return TRUE;
reply->len = reply_len;
+ /* we only peeked the two length bytes, so we have to
+ receive the complete message below proper. */
reply->received = 0;
server->incoming_reply = reply;
reply->received += bytes_recv;
}
- forward_dns_reply(reply->buf, reply->received, IPPROTO_TCP,
- server);
+ req = lookup_request(reply->buf, reply->received, IPPROTO_TCP);
+
+ if (!req)
+ /* invalid / corrupt request */
+ return TRUE;
+
+ res = forward_dns_reply((char*)reply->buf, reply->received, IPPROTO_TCP, server, req);
g_free(reply);
server->incoming_reply = NULL;
- destroy_server(server);
+ /* on success or if no further responses are expected close
+ * connection */
+ if (res == 0 || req->numresp >= req->numserv) {
+ destroy_request_data(req);
+ destroy_server(server);
+ return FALSE;
+ }
- return FALSE;
+ /*
+ * keep the TCP connection open, there are more
+ * requests to be answered
+ */
+ return TRUE;
}
return TRUE;
{
struct server_data *server = user_data;
- debug("");
+ debug("\n");
if (!server)
return FALSE;
static int server_create_socket(struct server_data *data)
{
- int sk, err;
+ int err;
char *interface;
+ int sk = socket(data->server_addr->sa_family,
+ data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM,
+ data->protocol);
debug("index %d server %s proto %d", data->index,
data->server, data->protocol);
- sk = socket(data->server_addr->sa_family,
- data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM,
- data->protocol);
if (sk < 0) {
err = errno;
connman_error("Failed to create server %s socket",
static void enable_fallback(bool enable)
{
- GSList *list;
-
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
if (data->index != -1)
}
}
+static unsigned int get_enabled_server_number(void)
+{
+ GSList *list;
+ unsigned int result = 0;
+
+ for (list = server_list; list; list = list->next) {
+ struct server_data *data = list->data;
+
+ if (data->index != -1 && data->enabled == true)
+ result++;
+ }
+ return result;
+}
+
static struct server_data *create_server(int index,
const char *domain, const char *server,
int protocol)
{
- struct server_data *data;
+ struct server_data *data = g_try_new0(struct server_data, 1);
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->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_socktype = socket_type(protocol, 0);
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST;
DBG("Adding DNS server %s", data->server);
enable_fallback(false);
+ } else if (data->index == -1 && get_enabled_server_number() == 0) {
+ data->enabled = true;
+ DBG("Adding fallback DNS server %s", data->server);
}
server_list = g_slist_append(server_list, data);
static bool resolv(struct request_data *req,
gpointer request, gpointer name)
{
- GSList *list;
-
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
if (data->protocol == IPPROTO_TCP) {
static void update_domain(int index, const char *domain, bool append)
{
- GSList *list;
-
DBG("index %d domain %s", index, domain);
if (!domain)
return;
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
- GList *dom_list;
char *dom = NULL;
bool dom_found = false;
if (data->index < 0)
continue;
-
- if (data->index != index)
+ else if (data->index != index)
continue;
- for (dom_list = data->domains; dom_list;
+ for (GList *dom_list = data->domains; dom_list;
dom_list = dom_list->next) {
dom = dom_list->data;
static void flush_requests(struct server_data *server)
{
- GSList *list;
-
- list = request_list;
+ GSList *list = request_list;
while (list) {
struct request_data *req = list->data;
const char *server)
{
struct server_data *data;
-
DBG("index %d server %s", index, server);
- if (!server && !domain)
- return -EINVAL;
-
if (!server) {
- append_domain(index, domain);
-
- return 0;
+ if (!domain) {
+ return -EINVAL;
+ } else {
+ append_domain(index, domain);
+ return 0;
+ }
}
if (g_str_equal(server, "127.0.0.1"))
return -ENODEV;
-
- if (g_str_equal(server, "::1"))
+ else if (g_str_equal(server, "::1"))
return -ENODEV;
data = find_server(index, server, IPPROTO_UDP);
return 0;
}
-static void remove_server(int index, const char *domain,
- const char *server, int protocol)
+static void remove_server(int index, const char *server, int protocol)
{
struct server_data *data;
- GSList *list;
data = find_server(index, server, protocol);
if (!data)
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);
+ if (get_enabled_server_number() == 0)
+ enable_fallback(true);
}
int __connman_dnsproxy_remove(int index, const char *domain,
{
DBG("index %d server %s", index, server);
- if (!server && !domain)
- return -EINVAL;
-
if (!server) {
- remove_domain(index, domain);
-
- return 0;
+ if (!domain) {
+ return -EINVAL;
+ } else {
+ remove_domain(index, domain);
+ return 0;
+ }
}
if (g_str_equal(server, "127.0.0.1"))
return -ENODEV;
-
- if (g_str_equal(server, "::1"))
+ else if (g_str_equal(server, "::1"))
return -ENODEV;
- remove_server(index, domain, server, IPPROTO_UDP);
- remove_server(index, domain, server, IPPROTO_TCP);
+ remove_server(index, server, IPPROTO_UDP);
+ remove_server(index, server, IPPROTO_TCP);
return 0;
}
static void dnsproxy_offline_mode(bool enabled)
{
- GSList *list;
-
DBG("enabled %d", enabled);
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
if (!enabled) {
static void dnsproxy_default_changed(struct connman_service *service)
{
- bool server_enabled = false;
- GSList *list;
- int index;
- int vpn_index;
+ bool any_server_enabled = false;
+ int index, vpn_index;
DBG("service %p", service);
*/
vpn_index = __connman_connection_get_vpn_index(index);
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
if (data->index == index) {
DBG("Enabling DNS server %s", data->server);
data->enabled = true;
- server_enabled = true;
+ any_server_enabled = true;
} else if (data->index == vpn_index) {
DBG("Enabling DNS server of VPN %s", data->server);
data->enabled = true;
}
}
- if (!server_enabled)
+ if (!any_server_enabled)
enable_fallback(true);
cache_refresh();
.service_state_changed = dnsproxy_service_state_changed,
};
-static const unsigned char opt_edns0_type[2] = { 0x00, 0x29 };
-
+/*
+ * Parses the given request buffer. `buf´ is expected to be the start of the
+ * domain_hdr structure i.e. the TCP length header is not handled by this
+ * function.
+ * Returns the ascii string dot representation of the query in `name´, which
+ * must be able to hold `size´ bytes.
+ *
+ * Returns < 0 on error (errno) or zero on success.
+ */
static int parse_request(unsigned char *buf, size_t len,
- char *name, unsigned int size)
+ char *name, size_t size)
{
+ static const unsigned char OPT_EDNS0_TYPE[2] = { 0x00, 0x29 };
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;
- unsigned int remain, used = 0;
-
- 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);
+ uint16_t qdcount, ancount, nscount, arcount;
+ unsigned char *ptr = buf + DNS_HEADER_SIZE;
+ size_t remain = len - DNS_HEADER_SIZE;
+ size_t used = 0;
+ if (len < DNS_HEADER_SIZE + DNS_QTYPE_QCLASS_SIZE) {
+ DBG("Dropped DNS request with short length %zd", len);
return -EINVAL;
}
if (!name || !size)
return -EINVAL;
+ qdcount = ntohs(hdr->qdcount);
+ ancount = ntohs(hdr->ancount);
+ nscount = ntohs(hdr->nscount);
+ arcount = ntohs(hdr->arcount);
+
+ if (hdr->qr || qdcount != 1 || ancount || nscount) {
+ DBG("Dropped DNS request with bad flags/counts qr %d "
+ "with len %zd qdcount %d ancount %d nscount %d",
+ hdr->qr, len, qdcount, ancount, nscount);
+
+ return -EINVAL;
+ }
+
debug("id 0x%04x qr %d opcode %d qdcount %d arcount %d",
hdr->id, hdr->qr, hdr->opcode,
qdcount, arcount);
name[0] = '\0';
- ptr = buf + sizeof(struct domain_hdr);
- remain = len - sizeof(struct domain_hdr);
-
+ /* parse DNS query string into `name' out parameter */
while (remain > 0) {
uint8_t label_len = *ptr;
if (label_len == 0x00) {
- uint8_t class;
- struct qtype_qclass *q =
- (struct qtype_qclass *)(ptr + 1);
+ struct qtype_qclass *q = (struct qtype_qclass *)(ptr + 1);
+ uint16_t class;
if (remain < sizeof(*q)) {
DBG("Dropped malformed DNS query");
}
class = ntohs(q->qclass);
- if (class != 1 && class != 255) {
+ if (class != DNS_CLASS_IN && class != DNS_CLASS_ANY) {
DBG("Dropped non-IN DNS class %d", class);
return -EINVAL;
}
strcat(name, ".");
used += label_len + 1;
-
ptr += label_len + 1;
remain -= label_len + 1;
}
- if (arcount && remain >= sizeof(struct domain_rr) + 1 && !ptr[0] &&
- ptr[1] == opt_edns0_type[0] && ptr[2] == opt_edns0_type[1]) {
+ if (arcount && remain >= DNS_RR_SIZE + 1 && !ptr[0] &&
+ ptr[1] == OPT_EDNS0_TYPE[0] && ptr[2] == OPT_EDNS0_TYPE[1]) {
struct domain_rr *edns0 = (struct domain_rr *)(ptr + 1);
DBG("EDNS0 buffer size %u", ntohs(edns0->class));
} else if (!arcount && remain) {
- DBG("DNS request with %d garbage bytes", remain);
+ DBG("DNS request with %zd garbage bytes", remain);
}
debug("query %s", name);
client->buf_end = 0;
}
-static unsigned int get_msg_len(unsigned char *buf)
+static size_t get_msg_len(const unsigned char *buf)
{
return buf[0]<<8 | buf[1];
}
{
char query[TCP_MAX_BUF_LEN];
struct request_data *req;
- int client_sk, err;
- unsigned int msg_len;
- GSList *list;
+ struct domain_hdr *hdr;
+ int client_sk = g_io_channel_unix_get_fd(client->channel);
+ int err;
+ size_t msg_len;
bool waiting_for_connect = false;
- int qtype = 0;
+ uint16_t 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);
client->buf_end += read_len;
- if (client->buf_end < 2)
+ /* we need at least the message length header */
+ if (client->buf_end < DNS_HEADER_TCP_EXTRA_BYTES)
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);
+ debug("client %d sent too much data %zd", 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,
+ debug("client %d msg len %zd end %d past end %zd", 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",
+ debug("client %d still missing %zd bytes",
client_sk,
msg_len + 2 - client->buf_end);
return true;
}
- debug("client %d all data %d received", client_sk, msg_len);
+ debug("client %d all data %zd received", client_sk, msg_len);
- err = parse_request(client->buf + 2, msg_len,
- query, sizeof(query));
+ err = parse_request(client->buf + DNS_HEADER_TCP_EXTRA_BYTES,
+ msg_len, query, sizeof(query));
if (err < 0 || (g_slist_length(server_list) == 0)) {
- send_response(client_sk, client->buf, msg_len + 2,
+ send_response(client_sk, client->buf,
+ msg_len + DNS_HEADER_TCP_EXTRA_BYTES,
NULL, 0, IPPROTO_TCP);
return true;
}
req->protocol = IPPROTO_TCP;
req->family = client->family;
- req->srcid = client->buf[2] | (client->buf[3] << 8);
+ hdr = (void*)(client->buf + DNS_HEADER_TCP_EXTRA_BYTES);
+
+ memcpy(&req->srcid, &hdr->id, sizeof(req->srcid));
req->dstid = get_id();
req->altid = get_id();
- req->request_len = msg_len + 2;
+ req->request_len = msg_len + DNS_HEADER_TCP_EXTRA_BYTES;
- client->buf[2] = req->dstid & 0xff;
- client->buf[3] = req->dstid >> 8;
+ /* replace ID the request for forwarding */
+ memcpy(&hdr->id, &req->dstid, sizeof(hdr->id));
req->numserv = 0;
req->ifdata = client->ifdata;
*/
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;
+ debug("cache hit %s type %s", query, qtype == DNS_TYPE_A ? "A" : "AAAA");
+ struct cache_data *data = qtype == DNS_TYPE_A ?
+ entry->ipv4 : entry->ipv6;
if (data) {
- ttl_left = data->valid_until - time(NULL);
+ int ttl_left = data->valid_until - time(NULL);
entry->hits++;
send_cached_response(client_sk, data->data,
debug("data missing, ignoring cache for this query");
}
- for (list = server_list; list; list = list->next) {
+ for (GSList *list = server_list; list; list = list->next) {
struct server_data *data = list->data;
if (data->protocol != IPPROTO_UDP || !data->enabled)
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",
+ if (client->buf_end > (msg_len + DNS_HEADER_TCP_EXTRA_BYTES)) {
+ debug("client %d buf %p -> %p end %d len %d new %zd",
client_sk,
client->buf + msg_len + 2,
client->buf, client->buf_end,
*/
msg_len = get_msg_len(client->buf);
if ((msg_len + 2) == client->buf_end) {
- debug("client %d reading another %d bytes", client_sk,
+ debug("client %d reading another %zd bytes", client_sk,
msg_len + 2);
goto read_another;
}
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);
+ int 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,
return FALSE;
}
+ 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;
+
switch (client->family) {
case AF_INET:
client_addr = &client_addr4;
return FALSE;
}
- len = recvfrom(client_sk, client->buf + client->buf_end,
+ const int 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) {
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);
+ int sock = g_io_channel_unix_get_fd(client->channel);
debug("client %d timeout pending %d bytes", sock, client->buf_end);
struct listener_data *ifdata, int family,
guint *listener_watch)
{
- int sk, client_sk, len;
- unsigned int msg_len;
+ int sk = -1, client_sk = -1;
+ int recv_len;
+ size_t msg_len;
+ fd_set readfds;
+ struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
+
struct tcp_partial_client_data *client;
struct sockaddr_in6 client_addr6;
socklen_t client_addr6_len = sizeof(client_addr6);
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);
client_addr_len = &client_addr6_len;
}
- tv.tv_sec = tv.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(sk, &readfds);
+ /* TODO: check select return code */
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 {
+ if (!FD_ISSET(sk, &readfds)) {
debug("No data to read from master %d, waiting.", sk);
return true;
}
+ client_sk = accept(sk, client_addr, client_addr_len);
if (client_sk < 0) {
connman_error("Accept failure on TCP listener");
*listener_watch = 0;
return false;
}
+ debug("client %d accepted", client_sk);
fcntl(client_sk, F_SETFL, O_NONBLOCK);
- client = g_hash_table_lookup(partial_tcp_req_table,
- GINT_TO_POINTER(client_sk));
+ 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) {
* 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) {
+ recv_len = recv(client_sk, client->buf, TCP_MAX_BUF_LEN, 0);
+ if (recv_len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
debug("client %d no data to read, waiting", client_sk);
return true;
return true;
}
- if (len < 2) {
+ if (recv_len < DNS_HEADER_TCP_EXTRA_BYTES) {
debug("client %d not enough data to read, waiting", client_sk);
- client->buf_end += len;
+ client->buf_end += recv_len;
return true;
}
msg_len = get_msg_len(client->buf);
if (msg_len > TCP_MAX_BUF_LEN) {
- debug("client %d invalid message length %u ignoring packet",
+ debug("client %d invalid message length %zd ignoring packet",
client_sk, msg_len);
g_hash_table_remove(partial_tcp_req_table,
GINT_TO_POINTER(client_sk));
* The packet length bytes do not contain the total message length,
* that is the reason to -2 below.
*/
- if (msg_len != (unsigned int)(len - 2)) {
- debug("client %d sent %d bytes but expecting %u pending %d",
- client_sk, len, msg_len + 2, msg_len + 2 - len);
+ if (msg_len != (size_t)(recv_len - DNS_HEADER_TCP_EXTRA_BYTES)) {
+ debug("client %d sent %d bytes but expecting %zd pending %zd",
+ client_sk, recv_len, msg_len + 2, msg_len + 2 - recv_len);
- client->buf_end += len;
+ client->buf_end += recv_len;
return true;
}
- return read_tcp_data(client, client_addr, *client_addr_len, len);
+ return read_tcp_data(client, client_addr, *client_addr_len, recv_len);
}
static gboolean tcp4_listener_event(GIOChannel *channel, GIOCondition condition,
{
unsigned char buf[768];
char query[512];
- struct request_data *req;
+ struct request_data *req = NULL;
+ struct domain_hdr *hdr = NULL;
+ int sk = -1, err, len;
+
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;
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
connman_error("Error with UDP listener channel");
return false;
}
- sk = g_io_channel_unix_get_fd(channel);
-
if (family == AF_INET) {
client_addr = &client_addr4;
client_addr_len = &client_addr4_len;
}
memset(client_addr, 0, *client_addr_len);
+ sk = g_io_channel_unix_get_fd(channel);
len = recvfrom(sk, buf, sizeof(buf), 0, client_addr, client_addr_len);
if (len < 2)
return true;
req->protocol = IPPROTO_UDP;
req->family = family;
- req->srcid = buf[0] | (buf[1] << 8);
+ hdr = (void*)buf;
+
+ req->srcid = hdr->id;
req->dstid = get_id();
req->altid = get_id();
req->request_len = len;
- buf[0] = req->dstid & 0xff;
- buf[1] = req->dstid >> 8;
+ hdr->id = req->dstid;
req->numserv = 0;
req->ifdata = ifdata;
static GIOChannel *get_listener(int family, int protocol, int index)
{
- GIOChannel *channel;
- const char *proto;
+ GIOChannel *channel = NULL;
union {
struct sockaddr sa;
struct sockaddr_in6 sin6;
struct sockaddr_in sin;
} s;
socklen_t slen;
- int sk, type;
+ const char *proto = protocol_label(protocol);
+ const int type = socket_type(protocol, SOCK_CLOEXEC);
char *interface;
+ int sk = socket(family, type, protocol);
debug("family %d protocol %d index %d", family, protocol, index);
- switch (protocol) {
- case IPPROTO_UDP:
- proto = "UDP";
- type = SOCK_DGRAM | SOCK_CLOEXEC;
- break;
-
- case IPPROTO_TCP:
- proto = "TCP";
- type = SOCK_STREAM | SOCK_CLOEXEC;
- break;
-
- default:
- return NULL;
- }
-
- sk = socket(family, type, protocol);
- if (sk < 0 && family == AF_INET6 && errno == EAFNOSUPPORT) {
- connman_error("No IPv6 support");
- return NULL;
- }
-
if (sk < 0) {
- connman_error("Failed to create %s listener socket", proto);
+ if (family == AF_INET6 && errno == EAFNOSUPPORT) {
+ connman_error("No IPv6 support");
+ } else {
+ connman_error("Failed to create %s listener socket", proto);
+ }
return NULL;
}
if (family == AF_INET6) {
memset(&s.sin6, 0, sizeof(s.sin6));
s.sin6.sin6_family = AF_INET6;
- s.sin6.sin6_port = htons(53);
+ s.sin6.sin6_port = htons(dns_listen_port);
slen = sizeof(s.sin6);
if (__connman_inet_get_interface_address(index,
} 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_port = htons(dns_listen_port);
slen = sizeof(s.sin);
if (__connman_inet_get_interface_address(index,
}
if (protocol == IPPROTO_TCP) {
-
if (listen(sk, 10) < 0) {
connman_error("Failed to listen on TCP socket %d/%s",
-errno, strerror(errno));
static int create_listener(struct listener_data *ifdata)
{
- int err, index;
-
- err = create_dns_listener(IPPROTO_UDP, ifdata);
+ int err = create_dns_listener(IPPROTO_UDP, ifdata);
if ((err & UDP_FAILED) == UDP_FAILED)
return -EIO;
return -EIO;
}
- index = connman_inet_ifindex("lo");
+ int index = connman_inet_ifindex("lo");
if (ifdata->index == index) {
if ((err & IPv6_FAILED) != IPv6_FAILED)
__connman_resolvfile_append(index, NULL, "::1");
static void destroy_listener(struct listener_data *ifdata)
{
- int index;
- GSList *list;
+ int index = connman_inet_ifindex("lo");
- index = connman_inet_ifindex("lo");
if (ifdata->index == index) {
__connman_resolvfile_remove(index, NULL, "127.0.0.1");
__connman_resolvfile_remove(index, NULL, "::1");
}
- for (list = request_list; list; list = list->next) {
+ for (GSList *list = request_list; list; list = list->next) {
struct request_data *req = list->data;
debug("Dropping request (id 0x%04x -> 0x%04x)",
void __connman_dnsproxy_remove_listener(int index)
{
struct listener_data *ifdata;
-
DBG("index %d", index);
if (!listener_table)
return err;
err = connman_notifier_register(&dnsproxy_notifier);
- if (err < 0)
- goto destroy;
-
- return 0;
+ if (err < 0) {
+ __connman_dnsproxy_remove_listener(index);
+ g_hash_table_destroy(listener_table);
+ g_hash_table_destroy(partial_tcp_req_table);
-destroy:
- __connman_dnsproxy_remove_listener(index);
- g_hash_table_destroy(listener_table);
- g_hash_table_destroy(partial_tcp_req_table);
+ return err;
+ }
- return err;
+ return 0;
}
int __connman_dnsproxy_set_mdns(int index, bool enabled)
if (ipv6_resolve)
g_resolv_unref(ipv6_resolve);
}
+
+void __connman_dnsproxy_set_listen_port(unsigned int port)
+{
+ dns_listen_port = port;
+}