android: Add DNS proxy implementation

This class proxies DNS requests over VPN-protected UDP sockets.
It is not really Android specific and might be useful for
kernel-libipsec or libipsec in general too, so we could maybe move it later
to libipsec (might need some portability work).
This commit is contained in:
Tobias Brunner 2014-07-15 17:52:43 +02:00
parent 16e519d42c
commit 2dc26c557e
3 changed files with 388 additions and 0 deletions

View File

@ -6,6 +6,7 @@ LOCAL_SRC_FILES := \
android_jni.c \
backend/android_attr.c \
backend/android_creds.c \
backend/android_dns_proxy.c \
backend/android_private_key.c \
backend/android_service.c \
charonservice.c \

View File

@ -0,0 +1,309 @@
/*
* 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;
}

View File

@ -0,0 +1,78 @@
/*
* 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_ @}*/