aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/frontends/android/jni/libandroidbridge/Android.mk1
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c417
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h86
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_service.c134
-rw-r--r--src/frontends/android/jni/libandroidbridge/vpnservice_builder.c23
-rw-r--r--src/frontends/android/jni/libandroidbridge/vpnservice_builder.h9
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java86
-rw-r--r--src/libcharon/bus/bus.c37
-rw-r--r--src/libcharon/bus/bus.h17
-rw-r--r--src/libcharon/bus/listeners/listener.h18
-rw-r--r--src/libcharon/sa/ike_sa.c6
-rw-r--r--src/libipsec/ip_packet.c271
-rw-r--r--src/libipsec/ip_packet.h35
-rw-r--r--src/libstrongswan/tests/suites/test_chunk.c49
-rw-r--r--src/libstrongswan/utils/chunk.c31
-rw-r--r--src/libstrongswan/utils/chunk.h25
16 files changed, 1215 insertions, 30 deletions
diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk
index fbe56d5b4..9c4561c3a 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 000000000..045f2c1d1
--- /dev/null
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c
@@ -0,0 +1,417 @@
+/*
+ * 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 <string.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;
+
+ /**
+ * Hostnames to filter queries by
+ */
+ hashtable_t *hostnames;
+};
+
+/**
+ * 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);
+}
+
+/**
+ * DNS header and masks to access flags
+ */
+typedef struct __attribute__((packed)) {
+ u_int16_t id;
+ u_int16_t flags;
+#define DNS_QR_MASK 0x8000
+#define DNS_OPCODE_MASK 0x7800
+ u_int16_t qdcount;
+ u_int16_t ancount;
+ u_int16_t nscount;
+ u_int16_t arcount;
+} dns_header_t;
+
+/**
+ * Extract the hostname in the question section data points to.
+ * Hostnames can be at most 255 characters (including dots separating labels),
+ * each label must be between 1 and 63 characters.
+ * The format is [len][label][len][label], followed by a null byte to indicate
+ * the null label of the root.
+ */
+static char *extract_hostname(chunk_t data)
+{
+ char *hostname, *pos, *end;
+ u_int8_t label;
+
+ if (!data.len || data.len > 255)
+ {
+ return NULL;
+ }
+ label = *data.ptr;
+ data = chunk_skip(data, 1);
+ hostname = strndup(data.ptr, data.len);
+ /* replace all label lengths with dots */
+ pos = hostname + label;
+ end = hostname + strlen(hostname);
+ while (pos < end)
+ {
+ label = *pos;
+ *pos++ = '.';
+ pos += label;
+ }
+ return hostname;
+}
+
+/**
+ * Check if the DNS query is for one of the allowed hostnames
+ */
+static bool check_hostname(private_android_dns_proxy_t *this, chunk_t data)
+{
+ dns_header_t *dns;
+ char *hostname;
+ bool success = FALSE;
+
+ this->lock->read_lock(this->lock);
+ if (!this->hostnames->get_count(this->hostnames))
+ {
+ this->lock->unlock(this->lock);
+ return TRUE;
+ }
+ if (data.len < sizeof(dns_header_t))
+ {
+ this->lock->unlock(this->lock);
+ return FALSE;
+ }
+ dns = (dns_header_t*)data.ptr;
+ if ((ntohs(dns->flags) & DNS_QR_MASK) == 0 &&
+ (ntohs(dns->flags) & DNS_OPCODE_MASK) == 0 &&
+ dns->qdcount)
+ {
+ data = chunk_skip(data, sizeof(dns_header_t));
+ hostname = extract_hostname(data);
+ if (hostname && this->hostnames->get(this->hostnames, hostname))
+ {
+ success = TRUE;
+ }
+ free(hostname);
+ }
+ this->lock->unlock(this->lock);
+ return success;
+}
+
+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;
+ }
+ data = packet->get_payload(packet);
+ /* remove UDP header */
+ data = chunk_skip(data, 8);
+ if (!check_hostname(this, data))
+ {
+ 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);
+ 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, add_hostname, void,
+ private_android_dns_proxy_t *this, char *hostname)
+{
+ char *existing;
+
+ hostname = strdup(hostname);
+ this->lock->write_lock(this->lock);
+ existing = this->hostnames->put(this->hostnames, hostname, hostname);
+ this->lock->unlock(this->lock);
+ free(existing);
+}
+
+METHOD(android_dns_proxy_t, destroy, void,
+ private_android_dns_proxy_t *this)
+{
+ this->hostnames->destroy_function(this->hostnames, (void*)free);
+ 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,
+ .add_hostname = _add_hostname,
+ .destroy = _destroy,
+ },
+ .sockets = hashtable_create((hashtable_hash_t)socket_hash,
+ (hashtable_equals_t)socket_equals, 4),
+ .hostnames = hashtable_create(hashtable_hash_str,
+ hashtable_equals_str, 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 000000000..481b060cf
--- /dev/null
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h
@@ -0,0 +1,86 @@
+/*
+ * 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);
+
+ /**
+ * Add a hostname for which queries are proxied. If at least one hostname
+ * is configured DNS queries for others will not be handled.
+ *
+ * @param hostname hostname to add (gets cloned)
+ */
+ void (*add_hostname)(android_dns_proxy_t *this, char *hostname);
+
+ /**
+ * 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_ @}*/
+
diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c
index d73dc4582..e60c491c1 100644
--- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2013 Tobias Brunner
+ * Copyright (C) 2010-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
@@ -19,6 +19,7 @@
#include <unistd.h>
#include "android_service.h"
+#include "android_dns_proxy.h"
#include "../charonservice.h"
#include "../vpnservice_builder.h"
@@ -83,6 +84,15 @@ struct private_android_service_t {
*/
int tunfd;
+ /**
+ * DNS proxy
+ */
+ android_dns_proxy_t *dns_proxy;
+
+ /**
+ * Whether to use the DNS proxy or not
+ */
+ bool use_dns_proxy;
};
/**
@@ -143,7 +153,7 @@ static job_requeue_t handle_plain(private_android_service_t *this)
fd_set set;
ssize_t len;
int tunfd;
- bool old;
+ bool old, dns_proxy;
timeval_t tv = {
/* check every second if tunfd is still valid */
.tv_sec = 1,
@@ -159,6 +169,8 @@ static job_requeue_t handle_plain(private_android_service_t *this)
}
tunfd = this->tunfd;
FD_SET(tunfd, &set);
+ /* cache this while we have the lock */
+ dns_proxy = this->use_dns_proxy;
this->lock->unlock(this->lock);
old = thread_cancelability(TRUE);
@@ -192,7 +204,10 @@ static job_requeue_t handle_plain(private_android_service_t *this)
packet = ip_packet_create(raw);
if (packet)
{
- ipsec->processor->queue_outbound(ipsec->processor, packet);
+ if (!dns_proxy || !this->dns_proxy->handle(this->dns_proxy, packet))
+ {
+ ipsec->processor->queue_outbound(ipsec->processor, packet);
+ }
}
else
{
@@ -324,6 +339,8 @@ static bool setup_tun_device(private_android_service_t *this,
(ipsec_inbound_cb_t)deliver_plain, this);
ipsec->processor->register_outbound(ipsec->processor,
(ipsec_outbound_cb_t)send_esp, NULL);
+ this->dns_proxy->register_cb(this->dns_proxy,
+ (dns_proxy_response_cb_t)deliver_plain, this);
lib->processor->queue_job(lib->processor,
(job_t*)callback_job_create((callback_job_cb_t)handle_plain, this,
@@ -333,6 +350,36 @@ static bool setup_tun_device(private_android_service_t *this,
}
/**
+ * Setup a new TUN device based on the existing one, but without DNS server.
+ */
+static bool setup_tun_device_without_dns(private_android_service_t *this)
+{
+ vpnservice_builder_t *builder;
+ int tunfd;
+
+ DBG1(DBG_DMN, "setting up TUN device without DNS");
+
+ builder = charonservice->get_vpnservice_builder(charonservice);
+
+ tunfd = builder->establish_no_dns(builder);
+ if (tunfd == -1)
+ {
+ return FALSE;
+ }
+
+ this->lock->write_lock(this->lock);
+ if (this->tunfd > 0)
+ { /* close previously opened TUN device, this should always be the case */
+ close(this->tunfd);
+ }
+ this->tunfd = tunfd;
+ this->lock->unlock(this->lock);
+
+ DBG1(DBG_DMN, "successfully created TUN device without DNS");
+ return TRUE;
+}
+
+/**
* Close the current tun device
*/
static void close_tun_device(private_android_service_t *this)
@@ -349,6 +396,8 @@ static void close_tun_device(private_android_service_t *this)
this->tunfd = -1;
this->lock->unlock(this->lock);
+ this->dns_proxy->unregister_cb(this->dns_proxy,
+ (dns_proxy_response_cb_t)deliver_plain);
ipsec->processor->unregister_outbound(ipsec->processor,
(ipsec_outbound_cb_t)send_esp);
ipsec->processor->unregister_inbound(ipsec->processor,
@@ -358,6 +407,17 @@ static void close_tun_device(private_android_service_t *this)
close(tunfd);
}
+/**
+ * Terminate the IKE_SA with the given unique ID
+ */
+CALLBACK(terminate, job_requeue_t,
+ u_int32_t *id)
+{
+ charon->controller->terminate_ike(charon->controller, *id,
+ controller_cb_empty, NULL, 0);
+ return JOB_REQUEUE_NONE;
+}
+
METHOD(listener_t, child_updown, bool,
private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
bool up)
@@ -368,6 +428,11 @@ METHOD(listener_t, child_updown, bool,
{
/* disable the hooks registered to catch initiation failures */
this->public.listener.ike_updown = NULL;
+ /* CHILD_SA is up so we can disable the DNS proxy we enabled to
+ * reestablish the SA */
+ this->lock->write_lock(this->lock);
+ this->use_dns_proxy = FALSE;
+ this->lock->unlock(this->lock);
if (!setup_tun_device(this, ike_sa, child_sa))
{
DBG1(DBG_DMN, "failed to setup TUN device");
@@ -422,9 +487,37 @@ METHOD(listener_t, alert, bool,
case ALERT_PEER_INIT_UNREACHABLE:
this->lock->read_lock(this->lock);
if (this->tunfd < 0)
- { /* only handle this if we are not reestablishing the SA */
+ {
+ u_int32_t *id = malloc_thing(u_int32_t);
+
+ /* always fail if we are not able to initiate the IKE_SA
+ * initially */
charonservice->update_status(charonservice,
CHARONSERVICE_UNREACHABLE_ERROR);
+ /* terminate the IKE_SA so no further keying tries are
+ * attempted */
+ *id = ike_sa->get_unique_id(ike_sa);
+ lib->processor->queue_job(lib->processor,
+ (job_t*)callback_job_create_with_prio(
+ (callback_job_cb_t)terminate, id, free,
+ (callback_job_cancel_t)return_false, JOB_PRIO_HIGH));
+ }
+ else
+ {
+ peer_cfg_t *peer_cfg;
+ u_int32_t tries, try;
+
+ /* when reestablishing and if keyingtries is not %forever
+ * the IKE_SA is destroyed after the set number of tries,
+ * so notify the GUI */
+ peer_cfg = ike_sa->get_peer_cfg(ike_sa);
+ tries = peer_cfg->get_keyingtries(peer_cfg);
+ try = va_arg(args, u_int32_t);
+ if (tries != 0 && try == tries-1)
+ {
+ charonservice->update_status(charonservice,
+ CHARONSERVICE_UNREACHABLE_ERROR);
+ }
}
this->lock->unlock(this->lock);
break;
@@ -445,11 +538,34 @@ METHOD(listener_t, ike_rekey, bool,
return TRUE;
}
-METHOD(listener_t, ike_reestablish, bool,
+METHOD(listener_t, ike_reestablish_pre, bool,
private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
{
if (this->ike_sa == old)
{
+ /* enable DNS proxy so hosts are properly resolved while the TUN device
+ * is still active */
+ this->lock->write_lock(this->lock);
+ this->use_dns_proxy = TRUE;
+ this->lock->unlock(this->lock);
+ /* if DNS servers are installed that are only reachable through the VPN
+ * the DNS proxy doesn't help, so uninstall DNS servers */
+ if (!setup_tun_device_without_dns(this))
+ {
+ DBG1(DBG_DMN, "failed to setup TUN device without DNS");
+ charonservice->update_status(charonservice,
+ CHARONSERVICE_GENERIC_ERROR);
+ }
+ }
+ return TRUE;
+}
+
+METHOD(listener_t, ike_reestablish_post, bool,
+ private_android_service_t *this, ike_sa_t *old, ike_sa_t *new,
+ bool initiated)
+{
+ if (this->ike_sa == old && initiated)
+ {
this->ike_sa = new;
/* re-register hook to detect initiation failures */
this->public.listener.ike_updown = _ike_updown;
@@ -458,7 +574,6 @@ METHOD(listener_t, ike_reestablish, bool,
* get ignored and thus we trigger the event here */
charonservice->update_status(charonservice,
CHARONSERVICE_CHILD_STATE_DOWN);
- /* the TUN device will be closed when the new CHILD_SA is established */
}
return TRUE;
}
@@ -630,6 +745,7 @@ METHOD(android_service_t, destroy, void,
charon->bus->remove_listener(charon->bus, &this->public.listener);
/* make sure the tun device is actually closed */
close_tun_device(this);
+ this->dns_proxy->destroy(this->dns_proxy);
this->lock->destroy(this->lock);
free(this->type);
free(this->gateway);
@@ -655,7 +771,8 @@ android_service_t *android_service_create(android_creds_t *creds, char *type,
.public = {
.listener = {
.ike_rekey = _ike_rekey,
- .ike_reestablish = _ike_reestablish,
+ .ike_reestablish_pre = _ike_reestablish_pre,
+ .ike_reestablish_post = _ike_reestablish_post,
.ike_updown = _ike_updown,
.child_updown = _child_updown,
.alert = _alert,
@@ -663,6 +780,7 @@ android_service_t *android_service_create(android_creds_t *creds, char *type,
.destroy = _destroy,
},
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
+ .dns_proxy = android_dns_proxy_create(),
.username = username,
.password = password,
.gateway = gateway,
@@ -670,6 +788,8 @@ android_service_t *android_service_create(android_creds_t *creds, char *type,
.type = type,
.tunfd = -1,
);
+ /* only allow queries for the VPN gateway */
+ this->dns_proxy->add_hostname(this->dns_proxy, gateway);
charon->bus->add_listener(charon->bus, &this->public.listener);
diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c
index 6b10228d0..c7a6eb6da 100644
--- a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c
+++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2013 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
@@ -197,8 +197,10 @@ failed:
return FALSE;
}
-METHOD(vpnservice_builder_t, establish, int,
- private_vpnservice_builder_t *this)
+/**
+ * Establish or reestablish the TUN device
+ */
+static int establish_internal(private_vpnservice_builder_t *this, char *method)
{
JNIEnv *env;
jmethodID method_id;
@@ -209,7 +211,7 @@ METHOD(vpnservice_builder_t, establish, int,
DBG2(DBG_LIB, "builder: building TUN device");
method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class,
- "establish", "()I");
+ method, "()I");
if (!method_id)
{
goto failed;
@@ -229,6 +231,18 @@ failed:
return -1;
}
+METHOD(vpnservice_builder_t, establish, int,
+ private_vpnservice_builder_t *this)
+{
+ return establish_internal(this, "establish");
+}
+
+METHOD(vpnservice_builder_t, establish_no_dns, int,
+ private_vpnservice_builder_t *this)
+{
+ return establish_internal(this, "establishNoDns");
+}
+
METHOD(vpnservice_builder_t, destroy, void,
private_vpnservice_builder_t *this)
{
@@ -252,6 +266,7 @@ vpnservice_builder_t *vpnservice_builder_create(jobject builder)
.add_dns = _add_dns,
.set_mtu = _set_mtu,
.establish = _establish,
+ .establish_no_dns = _establish_no_dns,
.destroy = _destroy,
},
);
diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h
index 209090896..08c436da6 100644
--- a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h
+++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
@@ -78,6 +78,13 @@ struct vpnservice_builder_t {
int (*establish)(vpnservice_builder_t *this);
/**
+ * Build the TUN device without DNS related data
+ *
+ * @return the TUN file descriptor, -1 if failed
+ */
+ int (*establish_no_dns)(vpnservice_builder_t *this);
+
+ /**
* Destroy a vpnservice_builder
*/
void (*destroy)(vpnservice_builder_t *this);
diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
index d53d478b2..5707f4fb6 100644
--- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
@@ -22,6 +22,7 @@ import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.List;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
@@ -526,11 +527,14 @@ public class CharonVpnService extends VpnService implements Runnable
{
private final String mName;
private VpnService.Builder mBuilder;
+ private BuilderCache mCache;
+ private BuilderCache mEstablishedCache;
public BuilderAdapter(String name)
{
mName = name;
mBuilder = createBuilder(name);
+ mCache = new BuilderCache();
}
private VpnService.Builder createBuilder(String name)
@@ -553,6 +557,7 @@ public class CharonVpnService extends VpnService implements Runnable
try
{
mBuilder.addAddress(address, prefixLength);
+ mCache.addAddress(address, prefixLength);
}
catch (IllegalArgumentException ex)
{
@@ -579,6 +584,7 @@ public class CharonVpnService extends VpnService implements Runnable
try
{
mBuilder.addRoute(address, prefixLength);
+ mCache.addRoute(address, prefixLength);
}
catch (IllegalArgumentException ex)
{
@@ -605,6 +611,7 @@ public class CharonVpnService extends VpnService implements Runnable
try
{
mBuilder.setMtu(mtu);
+ mCache.setMtu(mtu);
}
catch (IllegalArgumentException ex)
{
@@ -632,8 +639,87 @@ public class CharonVpnService extends VpnService implements Runnable
/* now that the TUN device is created we don't need the current
* builder anymore, but we might need another when reestablishing */
mBuilder = createBuilder(mName);
+ mEstablishedCache = mCache;
+ mCache = new BuilderCache();
return fd.detachFd();
}
+
+ public synchronized int establishNoDns()
+ {
+ ParcelFileDescriptor fd;
+
+ if (mEstablishedCache == null)
+ {
+ return -1;
+ }
+ try
+ {
+ Builder builder = createBuilder(mName);
+ mEstablishedCache.applyData(builder);
+ fd = builder.establish();
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ return -1;
+ }
+ if (fd == null)
+ {
+ return -1;
+ }
+ return fd.detachFd();
+ }
+ }
+
+ /**
+ * Cache non DNS related information so we can recreate the builder without
+ * that information when reestablishing IKE_SAs
+ */
+ public class BuilderCache
+ {
+ private final List<PrefixedAddress> mAddresses = new ArrayList<PrefixedAddress>();
+ private final List<PrefixedAddress> mRoutes = new ArrayList<PrefixedAddress>();
+ private int mMtu;
+
+ public void addAddress(String address, int prefixLength)
+ {
+ mAddresses.add(new PrefixedAddress(address, prefixLength));
+ }
+
+ public void addRoute(String address, int prefixLength)
+ {
+ mRoutes.add(new PrefixedAddress(address, prefixLength));
+ }
+
+ public void setMtu(int mtu)
+ {
+ mMtu = mtu;
+ }
+
+ public void applyData(VpnService.Builder builder)
+ {
+ for (PrefixedAddress address : mAddresses)
+ {
+ builder.addAddress(address.mAddress, address.mPrefix);
+ }
+ for (PrefixedAddress route : mRoutes)
+ {
+ builder.addRoute(route.mAddress, route.mPrefix);
+ }
+ builder.setMtu(mMtu);
+ }
+
+ private class PrefixedAddress
+ {
+ public String mAddress;
+ public int mPrefix;
+
+ public PrefixedAddress(String address, int prefix)
+ {
+ this.mAddress = address;
+ this.mPrefix = prefix;
+ }
+ }
}
/*
diff --git a/src/libcharon/bus/bus.c b/src/libcharon/bus/bus.c
index d1c138cd1..cb59f976b 100644
--- a/src/libcharon/bus/bus.c
+++ b/src/libcharon/bus/bus.c
@@ -755,7 +755,7 @@ METHOD(bus_t, ike_rekey, void,
this->mutex->unlock(this->mutex);
}
-METHOD(bus_t, ike_reestablish, void,
+METHOD(bus_t, ike_reestablish_pre, void,
private_bus_t *this, ike_sa_t *old, ike_sa_t *new)
{
enumerator_t *enumerator;
@@ -766,12 +766,40 @@ METHOD(bus_t, ike_reestablish, void,
enumerator = this->listeners->create_enumerator(this->listeners);
while (enumerator->enumerate(enumerator, &entry))
{
- if (entry->calling || !entry->listener->ike_reestablish)
+ if (entry->calling || !entry->listener->ike_reestablish_pre)
{
continue;
}
entry->calling++;
- keep = entry->listener->ike_reestablish(entry->listener, old, new);
+ keep = entry->listener->ike_reestablish_pre(entry->listener, old, new);
+ entry->calling--;
+ if (!keep)
+ {
+ unregister_listener(this, entry, enumerator);
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+}
+
+METHOD(bus_t, ike_reestablish_post, void,
+ private_bus_t *this, ike_sa_t *old, ike_sa_t *new, bool initiated)
+{
+ enumerator_t *enumerator;
+ entry_t *entry;
+ bool keep;
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->listeners->create_enumerator(this->listeners);
+ while (enumerator->enumerate(enumerator, &entry))
+ {
+ if (entry->calling || !entry->listener->ike_reestablish_post)
+ {
+ continue;
+ }
+ entry->calling++;
+ keep = entry->listener->ike_reestablish_post(entry->listener, old, new,
+ initiated);
entry->calling--;
if (!keep)
{
@@ -978,7 +1006,8 @@ bus_t *bus_create()
.child_keys = _child_keys,
.ike_updown = _ike_updown,
.ike_rekey = _ike_rekey,
- .ike_reestablish = _ike_reestablish,
+ .ike_reestablish_pre = _ike_reestablish_pre,
+ .ike_reestablish_post = _ike_reestablish_post,
.child_updown = _child_updown,
.child_rekey = _child_rekey,
.authorize = _authorize,
diff --git a/src/libcharon/bus/bus.h b/src/libcharon/bus/bus.h
index 1d708c5a5..1a6711a41 100644
--- a/src/libcharon/bus/bus.h
+++ b/src/libcharon/bus/bus.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2006-2009 Martin Willi
* Hochschule fuer Technik Rapperswil
*
@@ -380,12 +380,23 @@ struct bus_t {
void (*ike_rekey)(bus_t *this, ike_sa_t *old, ike_sa_t *new);
/**
- * IKE_SA reestablishing hook.
+ * IKE_SA reestablishing hook (before resolving hosts).
*
* @param old reestablished and obsolete IKE_SA
* @param new new IKE_SA replacing old
*/
- void (*ike_reestablish)(bus_t *this, ike_sa_t *old, ike_sa_t *new);
+ void (*ike_reestablish_pre)(bus_t *this, ike_sa_t *old, ike_sa_t *new);
+
+ /**
+ * IKE_SA reestablishing hook (after configuring and initiating the new
+ * IKE_SA).
+ *
+ * @param old reestablished and obsolete IKE_SA
+ * @param new new IKE_SA replacing old
+ * @param initiated TRUE if initiated successfully, FALSE otherwise
+ */
+ void (*ike_reestablish_post)(bus_t *this, ike_sa_t *old, ike_sa_t *new,
+ bool initiated);
/**
* CHILD_SA up/down hook.
diff --git a/src/libcharon/bus/listeners/listener.h b/src/libcharon/bus/listeners/listener.h
index abcc765e5..0910cb361 100644
--- a/src/libcharon/bus/listeners/listener.h
+++ b/src/libcharon/bus/listeners/listener.h
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2011-2014 Tobias Brunner
* Copyright (C) 2009 Martin Willi
* Hochschule fuer Technik Rapperswil
*
@@ -129,14 +130,29 @@ struct listener_t {
/**
* Hook called when an initiator reestablishes an IKE_SA.
*
+ * This is invoked right after creating the new IKE_SA and setting the
+ * peer_cfg (and the old hosts), but before resolving the hosts anew.
+ * It is not invoked on the responder.
+ *
+ * @param old IKE_SA getting reestablished (is destroyed)
+ * @param new new IKE_SA replacing old (gets established)
+ * @return TRUE to stay registered, FALSE to unregister
+ */
+ bool (*ike_reestablish_pre)(listener_t *this, ike_sa_t *old, ike_sa_t *new);
+
+ /**
+ * Hook called when an initiator reestablishes an IKE_SA.
+ *
* This is invoked right before the new IKE_SA is checked in after
* initiating it. It is not invoked on the responder.
*
* @param old IKE_SA getting reestablished (is destroyed)
* @param new new IKE_SA replacing old (gets established)
+ * @param initiated TRUE if initiation was successful, FALSE otherwise
* @return TRUE to stay registered, FALSE to unregister
*/
- bool (*ike_reestablish)(listener_t *this, ike_sa_t *old, ike_sa_t *new);
+ bool (*ike_reestablish_post)(listener_t *this, ike_sa_t *old,
+ ike_sa_t *new, bool initiated);
/**
* Hook called when a CHILD_SA gets up or down.
diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c
index c338cdaef..fddd83c63 100644
--- a/src/libcharon/sa/ike_sa.c
+++ b/src/libcharon/sa/ike_sa.c
@@ -1650,6 +1650,7 @@ METHOD(ike_sa_t, reestablish, status_t,
new->set_other_host(new, host->clone(host));
host = this->my_host;
new->set_my_host(new, host->clone(host));
+ charon->bus->ike_reestablish_pre(charon->bus, &this->public, new);
/* resolve hosts but use the old addresses above as fallback */
resolve_hosts((private_ike_sa_t*)new);
/* if we already have a virtual IP, we reuse it */
@@ -1734,12 +1735,15 @@ METHOD(ike_sa_t, reestablish, status_t,
if (status == DESTROY_ME)
{
+ charon->bus->ike_reestablish_post(charon->bus, &this->public, new,
+ FALSE);
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new);
status = FAILED;
}
else
{
- charon->bus->ike_reestablish(charon->bus, &this->public, new);
+ charon->bus->ike_reestablish_post(charon->bus, &this->public, new,
+ TRUE);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, new);
status = SUCCESS;
}
diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c
index 181cb88db..f6e08c0cb 100644
--- a/src/libipsec/ip_packet.c
+++ b/src/libipsec/ip_packet.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -22,6 +22,8 @@
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
#ifdef HAVE_NETINET_IP6_H
#include <netinet/ip6.h>
#endif
@@ -54,6 +56,11 @@ struct private_ip_packet_t {
chunk_t packet;
/**
+ * IP payload (points into packet)
+ */
+ chunk_t payload;
+
+ /**
* IP version
*/
u_int8_t version;
@@ -89,6 +96,12 @@ METHOD(ip_packet_t, get_encoding, chunk_t,
return this->packet;
}
+METHOD(ip_packet_t, get_payload, chunk_t,
+ private_ip_packet_t *this)
+{
+ return this->payload;
+}
+
METHOD(ip_packet_t, get_next_header, u_int8_t,
private_ip_packet_t *this)
{
@@ -111,13 +124,57 @@ METHOD(ip_packet_t, destroy, void,
}
/**
+ * Parse transport protocol header
+ */
+static bool parse_transport_header(chunk_t packet, u_int8_t proto,
+ u_int16_t *sport, u_int16_t *dport)
+{
+ switch (proto)
+ {
+ case IPPROTO_UDP:
+ {
+ struct udphdr *udp;
+
+ if (packet.len < sizeof(*udp))
+ {
+ DBG1(DBG_ESP, "UDP packet too short");
+ return FALSE;
+ }
+ udp = (struct udphdr*)packet.ptr;
+ *sport = ntohs(udp->source);
+ *dport = ntohs(udp->dest);
+ break;
+ }
+ case IPPROTO_TCP:
+ {
+ struct tcphdr *tcp;
+
+ if (packet.len < sizeof(*tcp))
+ {
+ DBG1(DBG_ESP, "TCP packet too short");
+ return FALSE;
+ }
+ tcp = (struct tcphdr*)packet.ptr;
+ *sport = ntohs(tcp->source);
+ *dport = ntohs(tcp->dest);
+ break;
+ }
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+/**
* Described in header.
*/
ip_packet_t *ip_packet_create(chunk_t packet)
{
private_ip_packet_t *this;
u_int8_t version, next_header;
+ u_int16_t sport = 0, dport = 0;
host_t *src, *dst;
+ chunk_t payload;
if (packet.len < 1)
{
@@ -141,11 +198,15 @@ ip_packet_t *ip_packet_create(chunk_t packet)
ip = (struct ip*)packet.ptr;
/* remove any RFC 4303 TFC extra padding */
packet.len = min(packet.len, untoh16(&ip->ip_len));
-
+ payload = chunk_skip(packet, ip->ip_hl * 4);
+ if (!parse_transport_header(payload, ip->ip_p, &sport, &dport))
+ {
+ goto failed;
+ }
src = host_create_from_chunk(AF_INET,
- chunk_from_thing(ip->ip_src), 0);
+ chunk_from_thing(ip->ip_src), sport);
dst = host_create_from_chunk(AF_INET,
- chunk_from_thing(ip->ip_dst), 0);
+ chunk_from_thing(ip->ip_dst), dport);
next_header = ip->ip_p;
break;
}
@@ -154,7 +215,7 @@ ip_packet_t *ip_packet_create(chunk_t packet)
{
struct ip6_hdr *ip;
- if (packet.len < sizeof(struct ip6_hdr))
+ if (packet.len < sizeof(*ip))
{
DBG1(DBG_ESP, "IPv6 packet too short");
goto failed;
@@ -162,11 +223,17 @@ ip_packet_t *ip_packet_create(chunk_t packet)
ip = (struct ip6_hdr*)packet.ptr;
/* remove any RFC 4303 TFC extra padding */
packet.len = min(packet.len, untoh16(&ip->ip6_plen));
-
+ /* we only handle packets without extension headers, just skip the
+ * basic IPv6 header */
+ payload = chunk_skip(packet, 40);
+ if (!parse_transport_header(payload, ip->ip6_nxt, &sport, &dport))
+ {
+ goto failed;
+ }
src = host_create_from_chunk(AF_INET6,
- chunk_from_thing(ip->ip6_src), 0);
+ chunk_from_thing(ip->ip6_src), sport);
dst = host_create_from_chunk(AF_INET6,
- chunk_from_thing(ip->ip6_dst), 0);
+ chunk_from_thing(ip->ip6_dst), dport);
next_header = ip->ip6_nxt;
break;
}
@@ -183,12 +250,14 @@ ip_packet_t *ip_packet_create(chunk_t packet)
.get_destination = _get_destination,
.get_next_header = _get_next_header,
.get_encoding = _get_encoding,
+ .get_payload = _get_payload,
.clone = _clone_,
.destroy = _destroy,
},
.src = src,
.dst = dst,
.packet = packet,
+ .payload = payload,
.version = version,
.next_header = next_header,
);
@@ -198,3 +267,189 @@ failed:
chunk_free(&packet);
return NULL;
}
+
+/**
+ * Calculate the checksum for the pseudo IP header
+ */
+static u_int16_t pseudo_header_checksum(host_t *src, host_t *dst,
+ u_int8_t proto, chunk_t payload)
+{
+ switch (src->get_family(src))
+ {
+ case AF_INET:
+ {
+ struct __attribute__((packed)) {
+ u_int32_t src;
+ u_int32_t dst;
+ u_char zero;
+ u_char proto;
+ u_int16_t len;
+ } pseudo = {
+ .proto = proto,
+ .len = htons(payload.len),
+ };
+ memcpy(&pseudo.src, src->get_address(src).ptr,
+ sizeof(pseudo.src));
+ memcpy(&pseudo.dst, dst->get_address(dst).ptr,
+ sizeof(pseudo.dst));
+ return chunk_internet_checksum(chunk_from_thing(pseudo));
+ }
+ case AF_INET6:
+ {
+ struct __attribute__((packed)) {
+ u_char src[16];
+ u_char dst[16];
+ u_int32_t len;
+ u_char zero[3];
+ u_char next_header;
+ } pseudo = {
+ .next_header = proto,
+ .len = htons(payload.len),
+ };
+ memcpy(&pseudo.src, src->get_address(src).ptr,
+ sizeof(pseudo.src));
+ memcpy(&pseudo.dst, dst->get_address(dst).ptr,
+ sizeof(pseudo.dst));
+ return chunk_internet_checksum(chunk_from_thing(pseudo));
+ }
+ }
+ return 0xffff;
+}
+
+/**
+ * Apply transport ports and calculate header checksums
+ */
+static void fix_transport_header(host_t *src, host_t *dst, u_int8_t proto,
+ chunk_t payload)
+{
+ u_int16_t sum = 0, sport, dport;
+
+ sport = src->get_port(src);
+ dport = dst->get_port(dst);
+
+ switch (proto)
+ {
+ case IPPROTO_UDP:
+ {
+ struct udphdr *udp;
+
+ if (payload.len < sizeof(*udp))
+ {
+ return;
+ }
+ udp = (struct udphdr*)payload.ptr;
+ if (sport != 0)
+ {
+ udp->source = htons(sport);
+ }
+ if (dport != 0)
+ {
+ udp->dest = htons(dport);
+ }
+ udp->check = 0;
+ sum = pseudo_header_checksum(src, dst, proto, payload);
+ udp->check = chunk_internet_checksum_inc(payload, sum);
+ break;
+ }
+ case IPPROTO_TCP:
+ {
+ struct tcphdr *tcp;
+
+ if (payload.len < sizeof(*tcp))
+ {
+ return;
+ }
+ tcp = (struct tcphdr*)payload.ptr;
+ if (sport != 0)
+ {
+ tcp->source = htons(sport);
+ }
+ if (dport != 0)
+ {
+ tcp->dest = htons(dport);
+ }
+ tcp->check = 0;
+ sum = pseudo_header_checksum(src, dst, proto, payload);
+ tcp->check = chunk_internet_checksum_inc(payload, sum);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/**
+ * Described in header.
+ */
+ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst,
+ u_int8_t next_header, chunk_t data)
+{
+ chunk_t packet;
+ int family;
+
+ family = src->get_family(src);
+ if (family != dst->get_family(dst))
+ {
+ DBG1(DBG_ESP, "address family does not match");
+ return NULL;
+ }
+
+ switch (family)
+ {
+ case AF_INET:
+ {
+ struct ip ip = {
+ .ip_v = 4,
+ .ip_hl = 5,
+ .ip_len = htons(20 + data.len),
+ .ip_ttl = 0x80,
+ .ip_p = next_header,
+ };
+ memcpy(&ip.ip_src, src->get_address(src).ptr, sizeof(ip.ip_src));
+ memcpy(&ip.ip_dst, dst->get_address(dst).ptr, sizeof(ip.ip_dst));
+ ip.ip_sum = chunk_internet_checksum(chunk_from_thing(ip));
+
+ packet = chunk_cat("cc", chunk_from_thing(ip), data);
+ fix_transport_header(src, dst, next_header, chunk_skip(packet, 20));
+ return ip_packet_create(packet);
+ }
+#ifdef HAVE_NETINET_IP6_H
+ case AF_INET6:
+ {
+ struct ip6_hdr ip = {
+ .ip6_flow = htonl(6),
+ .ip6_plen = htons(40 + data.len),
+ .ip6_nxt = next_header,
+ .ip6_hlim = 0x80,
+ };
+ memcpy(&ip.ip6_src, src->get_address(src).ptr, sizeof(ip.ip6_src));
+ memcpy(&ip.ip6_dst, dst->get_address(dst).ptr, sizeof(ip.ip6_dst));
+
+ packet = chunk_cat("cc", chunk_from_thing(ip), data);
+ fix_transport_header(src, dst, next_header, chunk_skip(packet, 40));
+ return ip_packet_create(packet);
+ }
+#endif /* HAVE_NETINET_IP6_H */
+ default:
+ DBG1(DBG_ESP, "unsupported address family");
+ return NULL;
+ }
+}
+
+/**
+ * Described in header.
+ */
+ip_packet_t *ip_packet_create_udp_from_data(host_t *src, host_t *dst,
+ chunk_t data)
+{
+ struct udphdr udp = {
+ .len = htons(8 + data.len),
+ .check = 0,
+ };
+ ip_packet_t *packet;
+
+ data = chunk_cat("cc", chunk_from_thing(udp), data);
+ packet = ip_packet_create_from_data(src, dst, IPPROTO_UDP, data);
+ chunk_free(&data);
+ return packet;
+}
diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h
index de817e23e..fa38eac2c 100644
--- a/src/libipsec/ip_packet.h
+++ b/src/libipsec/ip_packet.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -68,6 +68,13 @@ struct ip_packet_t {
chunk_t (*get_encoding)(ip_packet_t *this);
/**
+ * Get only the payload
+ *
+ * @return IP payload (internal data)
+ */
+ chunk_t (*get_payload)(ip_packet_t *this);
+
+ /**
* Clone the IP packet
*
* @return clone of the packet
@@ -93,4 +100,30 @@ struct ip_packet_t {
*/
ip_packet_t *ip_packet_create(chunk_t packet);
+/**
+ * Encode an IP packet from the given data.
+ *
+ * If src and/or dst have ports set they are applied to UDP/TCP headers found
+ * in the packet.
+ *
+ * @param src source address and optional port (cloned)
+ * @param dst destination address and optional port (cloned)
+ * @param next_header the protocol (IPv4) or next header (IPv6)
+ * @param data complete data after basic IP header (cloned)
+ * @return ip_packet_t instance, or NULL if invalid
+ */
+ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst,
+ u_int8_t next_header, chunk_t data);
+
+/**
+ * Encode a UDP packet from the given data.
+ *
+ * @param src source address and port (cloned)
+ * @param dst destination address and port (cloned)
+ * @param data UDP data (cloned)
+ * @return ip_packet_t instance, or NULL if invalid
+ */
+ip_packet_t *ip_packet_create_udp_from_data(host_t *src, host_t *dst,
+ chunk_t data);
+
#endif /** IP_PACKET_H_ @}*/
diff --git a/src/libstrongswan/tests/suites/test_chunk.c b/src/libstrongswan/tests/suites/test_chunk.c
index b33d70ec7..d71e010a2 100644
--- a/src/libstrongswan/tests/suites/test_chunk.c
+++ b/src/libstrongswan/tests/suites/test_chunk.c
@@ -784,6 +784,51 @@ START_TEST(test_chunk_hash_static)
END_TEST
/*******************************************************************************
+ * test for chunk_internet_checksum[_inc]()
+ */
+
+START_TEST(test_chunk_internet_checksum)
+{
+ chunk_t chunk;
+ u_int16_t sum;
+
+ chunk = chunk_from_chars(0x45,0x00,0x00,0x30,0x44,0x22,0x40,0x00,0x80,0x06,
+ 0x00,0x00,0x8c,0x7c,0x19,0xac,0xae,0x24,0x1e,0x2b);
+
+ sum = chunk_internet_checksum(chunk);
+ ck_assert_int_eq(0x442e, ntohs(sum));
+
+ sum = chunk_internet_checksum(chunk_create(chunk.ptr, 10));
+ sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+10, 10), sum);
+ ck_assert_int_eq(0x442e, ntohs(sum));
+
+ /* need to compensate for even/odd alignment */
+ sum = chunk_internet_checksum(chunk_create(chunk.ptr, 9));
+ sum = ntohs(sum);
+ sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+9, 11), sum);
+ sum = ntohs(sum);
+ ck_assert_int_eq(0x442e, ntohs(sum));
+
+ chunk = chunk_from_chars(0x45,0x00,0x00,0x30,0x44,0x22,0x40,0x00,0x80,0x06,
+ 0x00,0x00,0x8c,0x7c,0x19,0xac,0xae,0x24,0x1e);
+
+ sum = chunk_internet_checksum(chunk);
+ ck_assert_int_eq(0x4459, ntohs(sum));
+
+ sum = chunk_internet_checksum(chunk_create(chunk.ptr, 10));
+ sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+10, 9), sum);
+ ck_assert_int_eq(0x4459, ntohs(sum));
+
+ /* need to compensate for even/odd alignment */
+ sum = chunk_internet_checksum(chunk_create(chunk.ptr, 9));
+ sum = ntohs(sum);
+ sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+9, 10), sum);
+ sum = ntohs(sum);
+ ck_assert_int_eq(0x4459, ntohs(sum));
+}
+END_TEST
+
+/*******************************************************************************
* test for chunk_map and friends
*/
@@ -1018,6 +1063,10 @@ Suite *chunk_suite_create()
tcase_add_test(tc, test_chunk_hash_static);
suite_add_tcase(s, tc);
+ tc = tcase_create("chunk_internet_checksum");
+ tcase_add_test(tc, test_chunk_internet_checksum);
+ suite_add_tcase(s, tc);
+
tc = tcase_create("chunk_map");
tcase_add_test(tc, test_chunk_map);
suite_add_tcase(s, tc);
diff --git a/src/libstrongswan/utils/chunk.c b/src/libstrongswan/utils/chunk.c
index 1a9674f4d..4b24b37c2 100644
--- a/src/libstrongswan/utils/chunk.c
+++ b/src/libstrongswan/utils/chunk.c
@@ -990,6 +990,37 @@ u_int32_t chunk_hash_static(chunk_t chunk)
/**
* Described in header.
*/
+u_int16_t chunk_internet_checksum_inc(chunk_t data, u_int16_t checksum)
+{
+ u_int32_t sum = ntohs(~checksum);
+
+ while (data.len > 1)
+ {
+ sum += untoh16(data.ptr);
+ data = chunk_skip(data, 2);
+ }
+ if (data.len)
+ {
+ sum += (u_int16_t)*data.ptr << 8;
+ }
+ while (sum >> 16)
+ {
+ sum = (sum & 0xffff) + (sum >> 16);
+ }
+ return htons(~sum);
+}
+
+/**
+ * Described in header.
+ */
+u_int16_t chunk_internet_checksum(chunk_t data)
+{
+ return chunk_internet_checksum_inc(data, 0xffff);
+}
+
+/**
+ * Described in header.
+ */
int chunk_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec,
const void *const *args)
{
diff --git a/src/libstrongswan/utils/chunk.h b/src/libstrongswan/utils/chunk.h
index 9951ff31f..0daa2e1d0 100644
--- a/src/libstrongswan/utils/chunk.h
+++ b/src/libstrongswan/utils/chunk.h
@@ -412,6 +412,31 @@ u_int32_t chunk_hash_static_inc(chunk_t chunk, u_int32_t hash);
u_int64_t chunk_mac(chunk_t chunk, u_char *key);
/**
+ * Calculate the Internet Checksum according to RFC 1071 for the given chunk.
+ *
+ * If the result is used with chunk_internet_checksum_inc() and the data length
+ * is not a multiple of 16 bit the checksum bytes have to be swapped to
+ * compensate the even/odd alignment.
+ *
+ * @param chunk data to process
+ * @return checksum (one's complement, network order)
+ */
+u_int16_t chunk_internet_checksum(chunk_t data);
+
+/**
+ * Extend the given Internet Checksum (one's complement, in network byte order)
+ * with the given data.
+ *
+ * If data is not a multiple of 16 bits the checksum may have to be swapped to
+ * compensate even/odd alignment (see chunk_internet_checksum()).
+ *
+ * @param chunk data to process
+ * @param checksum previous checksum (one's complement, network order)
+ * @return checksum (one's complement, network order)
+ */
+u_int16_t chunk_internet_checksum_inc(chunk_t data, u_int16_t checksum);
+
+/**
* printf hook function for chunk_t.
*
* Arguments are: