--- /dev/null
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "android_dns_proxy.h"
+
+#include <hydra.h>
+#include <threading/rwlock.h>
+#include <collections/hashtable.h>
+#include <processing/jobs/callback_job.h>
+
+/**
+ * Timeout in seconds for sockets (i.e. not used for x seconds -> delete)
+ */
+#define SOCKET_TIMEOUT 30
+
+typedef struct private_android_dns_proxy_t private_android_dns_proxy_t;
+
+struct private_android_dns_proxy_t {
+
+ /**
+ * Public interface
+ */
+ android_dns_proxy_t public;
+
+ /**
+ * Mapping from source address to sockets
+ */
+ hashtable_t *sockets;
+
+ /**
+ * Registered callback
+ */
+ dns_proxy_response_cb_t cb;
+
+ /**
+ * Data passed to callback
+ */
+ void *data;
+
+ /**
+ * Lock used to synchronize access to the private members
+ */
+ rwlock_t *lock;
+};
+
+/**
+ * Data for proxy sockets
+ */
+typedef struct {
+ private_android_dns_proxy_t *proxy;
+ time_t last_use;
+ host_t *src;
+ int fd;
+} proxy_socket_t;
+
+/**
+ * Destroy a socket
+ */
+static void socket_destroy(proxy_socket_t *this)
+{
+ this->src->destroy(this->src);
+ if (this->fd != -1)
+ {
+ close(this->fd);
+ }
+ free(this);
+}
+
+/**
+ * Hash a proxy socket by src address
+ */
+static u_int socket_hash(host_t *src)
+{
+ u_int16_t port = src->get_port(src);
+ return chunk_hash_inc(src->get_address(src),
+ chunk_hash(chunk_from_thing(port)));
+}
+
+/**
+ * Compare proxy sockets by src address
+ */
+static bool socket_equals(host_t *a, host_t *b)
+{
+ return a->equals(a, b);
+}
+
+/**
+ * Opens a UDP socket for the given address family
+ */
+static int open_socket(int family)
+{
+ int skt;
+
+ skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
+ if (skt < 0)
+ {
+ DBG1(DBG_NET, "could not open proxy socket: %s", strerror(errno));
+ return -1;
+ }
+ if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface,
+ skt, family))
+ {
+ DBG1(DBG_NET, "installing bypass policy for proxy socket failed");
+ }
+ return skt;
+}
+
+/**
+ * Create a proxy socket for the given source
+ */
+static proxy_socket_t *create_socket(private_android_dns_proxy_t *this,
+ host_t *src)
+{
+ proxy_socket_t *skt;
+
+ INIT(skt,
+ .proxy = this,
+ .src = src->clone(src),
+ .fd = open_socket(src->get_family(src)),
+ );
+ if (skt->fd == -1)
+ {
+ socket_destroy(skt);
+ return NULL;
+ }
+ return skt;
+}
+
+CALLBACK(handle_response, bool,
+ proxy_socket_t *this, int fd, watcher_event_t event)
+{
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+ char buf[4096];
+ ssize_t len;
+ host_t *src;
+
+ len = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&addr,
+ &addr_len);
+ if (len > 0)
+ {
+ ip_packet_t *packet;
+
+ src = host_create_from_sockaddr((sockaddr_t*)&addr);
+ if (!src)
+ {
+ DBG1(DBG_NET, "failed to parse source address");
+ return TRUE;
+ }
+ packet = ip_packet_create_udp_from_data(src, this->src,
+ chunk_create(buf, len));
+ if (!packet)
+ {
+ DBG1(DBG_NET, "failed to parse DNS response");
+ return TRUE;
+ }
+ this->proxy->lock->read_lock(this->proxy->lock);
+ this->last_use = time_monotonic(NULL);
+ if (this->proxy->cb)
+ {
+ this->proxy->cb(this->proxy->data, packet);
+ }
+ else
+ {
+ packet->destroy(packet);
+ }
+ this->proxy->lock->unlock(this->proxy->lock);
+ }
+ else if (errno != EWOULDBLOCK)
+ {
+ DBG1(DBG_NET, "receiving DNS response failed: %s", strerror(errno));
+ }
+ return TRUE;
+}
+
+CALLBACK(handle_timeout, job_requeue_t,
+ proxy_socket_t *this)
+{
+ time_t now, diff;
+
+ now = time_monotonic(NULL);
+ this->proxy->lock->write_lock(this->proxy->lock);
+ diff = now - this->last_use;
+ if (diff >= SOCKET_TIMEOUT)
+ {
+ this->proxy->sockets->remove(this->proxy->sockets, this->src);
+ lib->watcher->remove(lib->watcher, this->fd);
+ this->proxy->lock->unlock(this->proxy->lock);
+ socket_destroy(this);
+ return JOB_REQUEUE_NONE;
+ }
+ this->proxy->lock->unlock(this->proxy->lock);
+ return JOB_RESCHEDULE(SOCKET_TIMEOUT - diff);
+}
+
+METHOD(android_dns_proxy_t, handle, bool,
+ private_android_dns_proxy_t *this, ip_packet_t *packet)
+{
+ proxy_socket_t *skt;
+ host_t *dst, *src;
+ chunk_t data;
+
+ if (packet->get_next_header(packet) != IPPROTO_UDP)
+ {
+ return FALSE;
+ }
+ dst = packet->get_destination(packet);
+ if (dst->get_port(dst) != 53)
+ { /* no DNS packet */
+ return FALSE;
+ }
+ src = packet->get_source(packet);
+ this->lock->write_lock(this->lock);
+ skt = this->sockets->get(this->sockets, src);
+ if (!skt)
+ {
+ skt = create_socket(this, src);
+ if (!skt)
+ {
+ this->lock->unlock(this->lock);
+ return FALSE;
+ }
+ this->sockets->put(this->sockets, skt->src, skt);
+ lib->watcher->add(lib->watcher, skt->fd, WATCHER_READ, handle_response,
+ skt);
+ lib->scheduler->schedule_job(lib->scheduler,
+ (job_t*)callback_job_create(handle_timeout, skt,
+ NULL, (callback_job_cancel_t)return_false), SOCKET_TIMEOUT);
+ }
+ skt->last_use = time_monotonic(NULL);
+ data = packet->get_payload(packet);
+ /* remove UDP header */
+ data = chunk_skip(data, 8);
+ if (sendto(skt->fd, data.ptr, data.len, 0, dst->get_sockaddr(dst),
+ *dst->get_sockaddr_len(dst)) != data.len)
+ {
+ DBG1(DBG_NET, "sending DNS request failed: %s", strerror(errno));
+ }
+ this->lock->unlock(this->lock);
+ return TRUE;
+}
+
+METHOD(android_dns_proxy_t, register_cb, void,
+ private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb, void *data)
+{
+ this->lock->write_lock(this->lock);
+ this->cb = cb;
+ this->data = data;
+ this->lock->unlock(this->lock);
+}
+
+METHOD(android_dns_proxy_t, unregister_cb, void,
+ private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb)
+{
+ this->lock->write_lock(this->lock);
+ if (this->cb == cb)
+ {
+ this->cb = NULL;
+ }
+ this->lock->unlock(this->lock);
+}
+
+METHOD(android_dns_proxy_t, destroy, void,
+ private_android_dns_proxy_t *this)
+{
+ this->sockets->destroy_function(this->sockets, (void*)socket_destroy);
+ this->lock->destroy(this->lock);
+ free(this);
+}
+
+/**
+ * Described in header.
+ */
+android_dns_proxy_t *android_dns_proxy_create()
+{
+ private_android_dns_proxy_t *this;
+
+ INIT(this,
+ .public = {
+ .handle = _handle,
+ .register_cb = _register_cb,
+ .unregister_cb = _unregister_cb,
+ .destroy = _destroy,
+ },
+ .sockets = hashtable_create((hashtable_hash_t)socket_hash,
+ (hashtable_equals_t)socket_equals, 4),
+ .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
+ );
+
+ return &this->public;
+}
--- /dev/null
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**
+ * @defgroup android_dns_proxy android_dns_proxy
+ * @{ @ingroup android_backend
+ */
+
+#ifndef ANDROID_DNS_PROXY_H_
+#define ANDROID_DNS_PROXY_H_
+
+#include <ip_packet.h>
+
+typedef struct android_dns_proxy_t android_dns_proxy_t;
+
+/**
+ * Callback called to deliver a DNS response packet.
+ *
+ * @param data data supplied during registration of the callback
+ * @param packet DNS response packet (has to be destroyed)
+ */
+typedef void (*dns_proxy_response_cb_t)(void *data, ip_packet_t *packet);
+
+/**
+ * DNS proxy class
+ */
+struct android_dns_proxy_t {
+
+ /**
+ * Handle an outbound DNS packet (if the packet is one)
+ *
+ * @param packet packet to handle
+ * @return TRUE if handled, FALSE otherwise (no DNS)
+ */
+ bool (*handle)(android_dns_proxy_t *this, ip_packet_t *packet);
+
+ /**
+ * Register the callback used to deliver DNS response packets.
+ *
+ * @param cb the callback function
+ * @param data optional data provided to callback
+ */
+ void (*register_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb,
+ void *data);
+
+ /**
+ * Unregister the callback used to deliver DNS response packets.
+ *
+ * @param cb the callback function
+ * @param data optional data provided to callback
+ */
+ void (*unregister_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb);
+
+ /**
+ * Destroy an instance.
+ */
+ void (*destroy)(android_dns_proxy_t *this);
+};
+
+/**
+ * Create an instance.
+ */
+android_dns_proxy_t *android_dns_proxy_create();
+
+#endif /** ANDROID_DNS_PROXY_H_ @}*/
+