X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=plugins%2Fdnsproxy.c;h=ff7ce8a174d71f42507741a7ed1bd4d321b54cc9;hb=36b19e423a2d66c0c9d722d4ee2fc65c03163b43;hp=c97553d3dde23e52a27fea6b49f0c09f5b0e86c2;hpb=991fe1e5f6458aa30ee82ad73b2f0411b74b1d3e;p=connman diff --git a/plugins/dnsproxy.c b/plugins/dnsproxy.c index c97553d..ff7ce8a 100644 --- a/plugins/dnsproxy.c +++ b/plugins/dnsproxy.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -36,8 +37,45 @@ #include +#if __BYTE_ORDER == __LITTLE_ENDIAN +struct domain_hdr { + uint16_t id; + uint8_t rd:1; + uint8_t tc:1; + uint8_t aa:1; + uint8_t opcode:4; + uint8_t qr:1; + uint8_t rcode:4; + uint8_t z:3; + uint8_t ra:1; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} __attribute__ ((packed)); +#elif __BYTE_ORDER == __BIG_ENDIAN +struct domain_hdr { + uint16_t id; + uint8_t qr:1; + uint8_t opcode:4; + uint8_t aa:1; + uint8_t tc:1; + uint8_t rd:1; + uint8_t ra:1; + uint8_t z:3; + uint8_t rcode:4; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} __attribute__ ((packed)); +#else +#error "Unknown byte order" +#endif + struct server_data { char *interface; + char *domain; char *server; GIOChannel *channel; guint watch; @@ -46,11 +84,19 @@ struct server_data { struct request_data { struct sockaddr_in sin; socklen_t len; - guint16 id; + guint16 srcid; + guint16 dstid; + guint16 altid; + guint timeout; + guint numserv; + guint numresp; + gpointer resp; + gsize resplen; }; static GSList *server_list = NULL; static GSList *request_list = NULL; +static guint16 request_id = 0x0000; static GIOChannel *listener_channel = NULL; static guint listener_watch = 0; @@ -60,17 +106,17 @@ static struct request_data *find_request(guint16 id) GSList *list; for (list = request_list; list; list = list->next) { - struct request_data *data = list->data; + struct request_data *req = list->data; - if (data->id == id) - return data; + if (req->dstid == id || req->altid == id) + return req; } return NULL; } static struct server_data *find_server(const char *interface, - const char *server) + const char *domain, const char *server) { GSList *list; @@ -83,8 +129,16 @@ static struct server_data *find_server(const char *interface, continue; if (g_str_equal(data->interface, interface) == TRUE && - g_str_equal(data->server, server) == TRUE) - return data; + g_str_equal(data->server, server) == TRUE) { + if (domain == NULL) { + if (data->domain == NULL) + return data; + continue; + } + + if (g_str_equal(data->domain, domain) == TRUE) + return data; + } } return NULL; @@ -93,14 +147,22 @@ static struct server_data *find_server(const char *interface, static gboolean server_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { + struct server_data *data = user_data; struct request_data *req; unsigned char buf[768]; + struct domain_hdr *hdr = (void *) &buf; int sk, err, len; + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + connman_error("Error with server channel"); + data->watch = 0; + return FALSE; + } + sk = g_io_channel_unix_get_fd(channel); len = recv(sk, buf, sizeof(buf), 0); - if (len < 2) + if (len < 12) return TRUE; DBG("Received %d bytes (id 0x%04x)", len, buf[0] | buf[1] << 8); @@ -109,19 +171,46 @@ static gboolean server_event(GIOChannel *channel, GIOCondition condition, if (req == NULL) return TRUE; + DBG("id 0x%04x rcode %d", hdr->id, hdr->rcode); + + buf[0] = req->srcid & 0xff; + buf[1] = req->srcid >> 8; + + req->numresp++; + + if (hdr->rcode == 0 || req->resp == NULL) { + g_free(req->resp); + req->resplen = 0; + + req->resp = g_try_malloc(len); + if (req->resp == NULL) + return TRUE; + + memcpy(req->resp, buf, len); + req->resplen = len; + } + + if (hdr->rcode > 0 && req->numresp < req->numserv) + return TRUE; + + if (req->timeout > 0) + g_source_remove(req->timeout); + request_list = g_slist_remove(request_list, req); sk = g_io_channel_unix_get_fd(listener_channel); - err = sendto(sk, buf, len, 0, (struct sockaddr *) &req->sin, req->len); + err = sendto(sk, req->resp, req->resplen, 0, + (struct sockaddr *) &req->sin, req->len); + g_free(req->resp); g_free(req); return TRUE; } static struct server_data *create_server(const char *interface, - const char *server) + const char *domain, const char *server) { struct server_data *data; struct sockaddr_in sin; @@ -175,6 +264,7 @@ static struct server_data *create_server(const char *interface, server_event, data); data->interface = g_strdup(interface); + data->domain = g_strdup(domain); data->server = g_strdup(server); return data; @@ -189,8 +279,9 @@ static void destroy_server(struct server_data *data) g_io_channel_unref(data->channel); - g_free(data->interface); g_free(data->server); + g_free(data->domain); + g_free(data->interface); g_free(data); } @@ -201,7 +292,10 @@ static int dnsproxy_append(const char *interface, const char *domain, DBG("interface %s server %s", interface, server); - data = create_server(interface, server); + if (g_str_equal(server, "127.0.0.1") == TRUE) + return -ENODEV; + + data = create_server(interface, domain, server); if (data == NULL) return -EIO; @@ -217,7 +311,10 @@ static int dnsproxy_remove(const char *interface, const char *domain, DBG("interface %s server %s", interface, server); - data = find_server(interface, server); + if (g_str_equal(server, "127.0.0.1") == TRUE) + return -ENODEV; + + data = find_server(interface, domain, server); if (data == NULL) return 0; @@ -235,16 +332,174 @@ static struct connman_resolver dnsproxy_resolver = { .remove = dnsproxy_remove, }; +static int parse_request(unsigned char *buf, int len, + char *name, unsigned int size) +{ + struct domain_hdr *hdr = (void *) buf; + uint16_t qdcount = ntohs(hdr->qdcount); + unsigned char *ptr; + char *last_label = NULL; + int label_count = 0; + unsigned int remain, used = 0; + + if (len < 12) + return -EINVAL; + + DBG("id 0x%04x qr %d opcode %d qdcount %d", + hdr->id, hdr->qr, hdr->opcode, qdcount); + + if (hdr->qr != 0 || qdcount != 1) + return -EINVAL; + + memset(name, 0, size); + + ptr = buf + 12; + remain = len - 12; + + while (remain > 0) { + uint8_t len = *ptr; + + if (len == 0x00) { + if (label_count > 0) + last_label = (char *) (ptr + 1); + break; + } + + label_count++; + + if (used + len + 1 > size) + return -ENOBUFS; + + strncat(name, (char *) (ptr + 1), len); + strcat(name, "."); + + used += len + 1; + + ptr += len + 1; + remain -= len + 1; + } + + DBG("query %s (%d labels)", name, label_count); + + return 0; +} + +static void send_response(int sk, unsigned char *buf, int len, + const struct sockaddr *to, socklen_t tolen) +{ + struct domain_hdr *hdr = (void *) buf; + int err; + + if (len < 12) + return; + + DBG("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode); + + hdr->qr = 1; + hdr->rcode = 2; + + hdr->ancount = 0; + hdr->nscount = 0; + hdr->arcount = 0; + + err = sendto(sk, buf, len, 0, to, tolen); +} + +static int append_query(unsigned char *buf, unsigned int size, + const char *query, const char *domain) +{ + unsigned char *ptr = buf; + char *offset; + + DBG("query %s domain %s", query, domain); + + offset = (char *) query; + while (offset != NULL) { + char *tmp; + + tmp = strchr(offset, '.'); + if (tmp == NULL) { + if (strlen(offset) == 0) + break; + *ptr = strlen(offset); + memcpy(ptr + 1, offset, strlen(offset)); + ptr += strlen(offset) + 1; + break; + } + + *ptr = tmp - offset; + memcpy(ptr + 1, offset, tmp - offset); + ptr += tmp - offset + 1; + + offset = tmp + 1; + } + + offset = (char *) domain; + while (offset != NULL) { + char *tmp; + + tmp = strchr(offset, '.'); + if (tmp == NULL) { + if (strlen(offset) == 0) + break; + *ptr = strlen(offset); + memcpy(ptr + 1, offset, strlen(offset)); + ptr += strlen(offset) + 1; + break; + } + + *ptr = tmp - offset; + memcpy(ptr + 1, offset, tmp - offset); + ptr += tmp - offset + 1; + + offset = tmp + 1; + } + + *ptr++ = 0x00; + + return ptr - buf; +} + +static gboolean request_timeout(gpointer user_data) +{ + struct request_data *req = user_data; + + DBG("id 0x%04x", req->srcid); + + request_list = g_slist_remove(request_list, req); + + if (req->resplen > 0 && req->resp != NULL) { + int sk, err; + + sk = g_io_channel_unix_get_fd(listener_channel); + + err = sendto(sk, req->resp, req->resplen, 0, + (struct sockaddr *) &req->sin, req->len); + } + + g_free(req->resp); + g_free(req); + + return FALSE; +} + static gboolean listener_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { GSList *list; unsigned char buf[768]; + char query[512]; struct request_data *req; struct sockaddr_in sin; socklen_t size = sizeof(sin); int sk, err, len; + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + connman_error("Error with listener channel"); + listener_watch = 0; + return FALSE; + } + sk = g_io_channel_unix_get_fd(channel); memset(&sin, 0, sizeof(sin)); @@ -255,31 +510,71 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, DBG("Received %d bytes (id 0x%04x)", len, buf[0] | buf[1] << 8); - if (g_slist_length(server_list) == 0) + err = parse_request(buf, len, query, sizeof(query)); + if (err < 0 || g_slist_length(server_list) == 0) { + send_response(sk, buf, len, (struct sockaddr *) &sin, size); return TRUE; + } - req = find_request(buf[0] | (buf[1] << 8)); - if (req == NULL) { - req = g_try_new0(struct request_data, 1); - if (req == NULL) - return TRUE; + req = g_try_new0(struct request_data, 1); + if (req == NULL) + return TRUE; - memcpy(&req->sin, &sin, sizeof(sin)); - req->len = size; - req->id = buf[0] | (buf[1] << 8); + memcpy(&req->sin, &sin, sizeof(sin)); + req->len = size; - request_list = g_slist_append(request_list, req); - } else { - memcpy(&req->sin, &sin, sizeof(sin)); - req->len = size; - } + request_id += 2; + if (request_id == 0x0000 || request_id == 0xffff) + request_id += 2; + + req->srcid = buf[0] | (buf[1] << 8); + req->dstid = request_id; + req->altid = request_id + 1; + + buf[0] = req->dstid & 0xff; + buf[1] = req->dstid >> 8; + + request_list = g_slist_append(request_list, req); + + req->numserv = 0; + req->timeout = g_timeout_add_seconds(5, request_timeout, req); for (list = server_list; list; list = list->next) { struct server_data *data = list->data; + DBG("server %s domain %s", data->server, data->domain); + sk = g_io_channel_unix_get_fd(data->channel); err = send(sk, buf, len, 0); + + req->numserv++; + + if (data->domain != NULL) { + unsigned char alt[1024]; + struct domain_hdr *hdr = (void *) &alt; + int altlen; + + alt[0] = req->altid & 0xff; + alt[1] = req->altid >> 8; + + memcpy(alt + 2, buf + 2, 10); + hdr->qdcount = htons(1); + + altlen = append_query(alt + 12, sizeof(alt) - 12, + query, data->domain); + if (altlen < 0) + continue; + + alt[altlen + 12] = 0x00; + alt[altlen + 13] = 0x01; + alt[altlen + 14] = 0x00; + alt[altlen + 15] = 0x01; + + err = send(sk, alt, altlen + 12 + 4, 0); + + req->numserv++; + } } return TRUE; @@ -333,6 +628,8 @@ static int create_listener(void) listener_watch = g_io_add_watch(listener_channel, G_IO_IN, listener_event, NULL); + connman_resolver_append("lo", NULL, "127.0.0.1"); + return 0; } @@ -342,15 +639,19 @@ static void destroy_listener(void) DBG(""); + connman_resolver_remove_all("lo"); + if (listener_watch > 0) g_source_remove(listener_watch); for (list = request_list; list; list = list->next) { - struct request_data *data = list->data; + struct request_data *req = list->data; - DBG("Dropping request (id 0x%04x)", data->id); + DBG("Dropping request (id 0x%04x -> 0x%04x)", + req->srcid, req->dstid); - g_free(data); + g_free(req->resp); + g_free(req); list->data = NULL; } @@ -383,4 +684,4 @@ static void dnsproxy_exit(void) } CONNMAN_PLUGIN_DEFINE(dnsproxy, "DNS proxy resolver plugin", VERSION, - dnsproxy_init, dnsproxy_exit) + CONNMAN_PLUGIN_PRIORITY_DEFAULT, dnsproxy_init, dnsproxy_exit)