From: Tobias Brunner Date: Tue, 15 Jul 2014 15:52:43 +0000 (+0200) Subject: android: Add DNS proxy implementation X-Git-Tag: 5.2.1dr1~115^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2dc26c557e6c8da38b60f8154acf0b007747253c;p=thirdparty%2Fstrongswan.git 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). --- diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index fbe56d5b41..9c4561c3a4 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -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 \ diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c new file mode 100644 index 0000000000..9f5170b1df --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c @@ -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 . + * + * 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 +#include +#include +#include +#include + +#include "android_dns_proxy.h" + +#include +#include +#include +#include + +/** + * 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; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h new file mode 100644 index 0000000000..a906f7c71f --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h @@ -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 . + * + * 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 + +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_ @}*/ +