diff options
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: |