diff options
author | Tobias Brunner <tobias@strongswan.org> | 2014-07-22 11:10:59 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2014-07-22 11:14:00 +0200 |
commit | 1ddc1ec0b37355be22d55728557b88cde83292e6 (patch) | |
tree | 0c2eb284b44c121ca1b49d3d30dc71f9f42bf412 /src | |
parent | 32109a535f3f0ae3e234ebfefc7c69dfc2327c67 (diff) | |
parent | ffff7219ef6af21c9497af8db49bfb3c1c9a3036 (diff) | |
download | strongswan-1ddc1ec0b37355be22d55728557b88cde83292e6.tar.bz2 strongswan-1ddc1ec0b37355be22d55728557b88cde83292e6.tar.xz |
Merge branch 'android-dns-proxy'
Adds a DNS proxy feature that uses VPN-protected sockets to resolve the
VPN gateway's hostname while reestablishing the IKE_SA, which is
required because we keep the TUN device up to avoid leaking plaintext
traffic.
The TUN device is recreated without DNS servers before reestablishing in
case the VPN server pushed DNS servers to the client that are only
reachable via VPN.
Fixes #622.
Diffstat (limited to 'src')
-rw-r--r-- | src/frontends/android/jni/libandroidbridge/Android.mk | 1 | ||||
-rw-r--r-- | src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c | 417 | ||||
-rw-r--r-- | src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h | 86 | ||||
-rw-r--r-- | src/frontends/android/jni/libandroidbridge/backend/android_service.c | 134 | ||||
-rw-r--r-- | src/frontends/android/jni/libandroidbridge/vpnservice_builder.c | 23 | ||||
-rw-r--r-- | src/frontends/android/jni/libandroidbridge/vpnservice_builder.h | 9 | ||||
-rw-r--r-- | src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java | 86 | ||||
-rw-r--r-- | src/libcharon/bus/bus.c | 37 | ||||
-rw-r--r-- | src/libcharon/bus/bus.h | 17 | ||||
-rw-r--r-- | src/libcharon/bus/listeners/listener.h | 18 | ||||
-rw-r--r-- | src/libcharon/sa/ike_sa.c | 6 | ||||
-rw-r--r-- | src/libipsec/ip_packet.c | 271 | ||||
-rw-r--r-- | src/libipsec/ip_packet.h | 35 | ||||
-rw-r--r-- | src/libstrongswan/tests/suites/test_chunk.c | 49 | ||||
-rw-r--r-- | src/libstrongswan/utils/chunk.c | 31 | ||||
-rw-r--r-- | src/libstrongswan/utils/chunk.h | 25 |
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: |